From 48b9d789fde7e0002ba09986babb774f0eeca4a4 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Tue, 27 Aug 2024 14:38:49 +0200 Subject: [PATCH 01/49] added: additional field service integration type --- .../FSIntTableSubscriber.Codeunit.al | 647 +++++++++++++++++- .../Codeunits/FSIntegrationMgt.Codeunit.al | 68 ++ .../Codeunits/FSLookupFSTables.Codeunit.al | 56 ++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 520 +++++++++++++- .../app/src/Enums/FSIntegrationType.Enum.al | 23 + .../Page Extensions/FSServiceOrder.PageExt.al | 224 ++++++ .../FSServiceOrderTypes.PageExt.al | 194 ++++++ .../FSServiceOrders.PageExt.al | 206 ++++++ .../app/src/Pages/FSConnectionSetup.Page.al | 11 + .../src/Pages/FSConnectionSetupWizard.Page.al | 13 + .../app/src/Pages/FSWorkOrderTypes.Page.al | 156 +++++ .../app/src/Pages/FSWorkOrders.Page.al | 261 +++++++ .../FSServiceHeader.TableExt.al | 49 ++ .../FSServiceLine.TableExt.al | 21 + .../app/src/Tables/FSConnectionSetup.Table.al | 18 + .../app/src/Tables/FSIncidentType.Table.al | 90 +++ .../app/src/Tables/FSWorkOrder.Table.al | 9 +- .../src/Tables/FSWorkOrderIncident.Table.al | 8 + .../app/src/Tables/FSWorkOrderType.Table.al | 8 + 19 files changed, 2568 insertions(+), 14 deletions(-) create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Enums/FSIntegrationType.Enum.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrderTypes.Page.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSIncidentType.Table.al diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index b8c0afcda7..134e4404c4 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -8,17 +8,22 @@ using Microsoft.Integration.Dataverse; using Microsoft.Projects.Project.Job; using Microsoft.Foundation.NoSeries; using Microsoft.Projects.Project.Setup; +using Microsoft.Service.Item; using Microsoft.Integration.SyncEngine; using Microsoft.Sales.Customer; using System.Telemetry; +using Microsoft.Integration.FieldService; using Microsoft.Projects.Project.Posting; using Microsoft.Projects.Project.Journal; using Microsoft.Projects.Resources.Resource; using Microsoft.Integration.D365Sales; using Microsoft.Inventory.Item; +using Microsoft.Service.Document; using Microsoft.Projects.Project.Planning; using Microsoft.Projects.Project.Ledger; using Microsoft.Sales.History; +using Microsoft.Sales.Document; +using Microsoft.Service.Setup; codeunit 6610 "FS Int. Table Subscriber" { @@ -116,6 +121,25 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + [EventSubscriber(ObjectType::Table, Database::"Integration Table Mapping", 'OnBeforeModifyEvent', '', false, false)] + local procedure IntegrationTableMappingOnBeforeModifyEvent(var Rec: Record "Integration Table Mapping"; RunTrigger: Boolean) + var + ServiceItem: Record "Service Item"; + MandatoryFilterErr: Label '"%1" must be included in the filter. If you need this behavior, please contact your partner for assistance.', Comment = '%1 = a field caption'; + begin + if not RunTrigger then + exit; + if Rec.IsTemporary() then + exit; + + if Rec."Table ID" <> Database::"Service Item" then + exit; + + ServiceItem.SetView(Rec.GetTableFilter()); + if ServiceItem.GetFilter("Service Item Components") = '' then + Error(MandatoryFilterErr, ServiceItem.FieldCaption("Service Item Components")); + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeTransferRecordFields', '', false, false)] local procedure OnBeforeTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) var @@ -124,13 +148,20 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; CRMIntegrationRecord: Record "CRM Integration Record"; + FSWorkOrderIncident: Record "FS Work Order Incident"; JobJournalLine: Record "Job Journal Line"; JobTask: Record "Job Task"; + ServiceHeader: Record "Service Header"; + ServiceItemLine: Record "Service Item Line"; + ServiceLine: Record "Service Line"; + SourceDestCode: Text; begin if not FSConnectionSetup.IsEnabled() then exit; - case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of 'FS Work Order Product-Job Journal Line', 'FS Work Order Service-Job Journal Line': begin @@ -154,6 +185,67 @@ codeunit 6610 "FS Int. Table Subscriber" JobJournalLine."Job Task No." := JobTask."Job Task No."; DestinationRecordRef.GetTable(JobJournalLine); end; + 'FS Work Order Incident-Service Item Line': + begin + SourceRecordRef.SetTable(FSWorkOrderIncident); + DestinationRecordRef.SetTable(ServiceItemLine); + + if ServiceItemLine."Document No." <> '' then + exit; + + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderIncident.WorkOrder) then + ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID"); + + ServiceItemLine."Document Type" := ServiceItemLine."Document Type"::Order; + ServiceItemLine."Document No." := ServiceHeader."No."; + ServiceItemLine."Line No." := GetNextLineNo(ServiceItemLine); + + DestinationRecordRef.GetTable(ServiceItemLine); + end; + 'FS Work Order Product-Service Line': + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + DestinationRecordRef.SetTable(ServiceLine); + + if ServiceLine."Document No." <> '' then + exit; + + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrder) then + ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID"); + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrderIncident) then + ServiceItemLine.GetBySystemId(CRMIntegrationRecord."Integration ID"); + + ServiceLine."Document Type" := ServiceLine."Document Type"::Order; + ServiceLine."Document No." := ServiceHeader."No."; + ServiceLine."Line No." := GetNextLineNo(ServiceLine); + ServiceLine."Service Item Line No." := ServiceItemLine."Line No."; + ServiceLine."Service Item No." := ServiceItemLine."Service Item No."; + ServiceLine.Type := ServiceLine.Type::Item; + + DestinationRecordRef.GetTable(ServiceLine); + end; + 'FS Work Order Service-Service Line': + begin + SourceRecordRef.SetTable(FSWorkOrderService); + DestinationRecordRef.SetTable(ServiceLine); + + if ServiceLine."Document No." <> '' then + exit; + + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.WorkOrder) then + ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID"); + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.WorkOrderIncident) then + ServiceItemLine.GetBySystemId(CRMIntegrationRecord."Integration ID"); + + ServiceLine."Document Type" := ServiceLine."Document Type"::Order; + ServiceLine."Document No." := ServiceHeader."No."; + ServiceLine."Line No." := GetNextLineNo(ServiceLine); + ServiceLine."Service Item Line No." := ServiceItemLine."Line No."; + ServiceLine."Service Item No." := ServiceItemLine."Service Item No."; + ServiceLine.Type := ServiceLine.Type::Item; + + DestinationRecordRef.GetTable(ServiceLine); + end; end; end; @@ -161,21 +253,27 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure OnTransferFieldData(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean; var NeedsConversion: Boolean) var FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationRecord: Record "CRM Integration Record"; FSWorkOrderService: Record "FS Work Order Service"; FSWorkOrderProduct: Record "FS Work Order Product"; FSBookableResourceBooking: Record "FS Bookable Resource Booking"; JobJournalLine: Record "Job Journal Line"; + ServiceLine: Record "Service Line"; + ItemUnitOfMeasure: Record "Item Unit of Measure"; SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; + NAVItemUomRecordId: RecordId; DurationInHours: Decimal; DurationInMinutes: Decimal; Quantity: Decimal; QuantityToTransferToInvoice: Decimal; QuantityCurrentlyConsumed: Decimal; QuantityCurrentlyInvoiced: Decimal; + NotCoupledCRMUomErr: Label 'The unit is not coupled to a unit of measure.'; begin if not FSConnectionSetup.IsEnabled() then exit; + if IsValueFound then exit; @@ -183,17 +281,40 @@ codeunit 6610 "FS Int. Table Subscriber" if SourceFieldRef.Record().Number() = DestinationFieldRef.Record().Number() then exit; - if SourceFieldRef.Record().Number = Database::"FS Work Order Service" then + if (SourceFieldRef.Record().Number = Database::"Service Line") and + (DestinationFieldRef.Record().Number = Database::"FS Work Order Service") then + case DestinationFieldRef.Name() of + FSWorkOrderService.FieldName(EstimateDuration): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(ServiceLine); + DurationInHours := ServiceLine.Quantity; + DurationInMinutes := DurationInHours * 60; + NewValue := DurationInMinutes; + IsValueFound := true; + NeedsConversion := false; + end; + end; + + if (SourceFieldRef.Record().Number = Database::"FS Work Order Service") then case SourceFieldRef.Name() of + FSWorkOrderService.FieldName(EstimateDuration): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrderService); + DurationInMinutes := FSWorkOrderService.EstimateDuration; + DurationInHours := (DurationInMinutes / 60); + NewValue := DurationInHours; + IsValueFound := true; + NeedsConversion := false; + end; FSWorkOrderService.FieldName(Duration), FSWorkOrderService.FieldName(DurationToBill): begin SourceRecordRef := SourceFieldRef.Record(); SourceRecordRef.SetTable(FSWorkOrderService); SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); - DestinationRecordRef := DestinationFieldRef.Record(); - DestinationRecordRef.SetTable(JobJournalLine); - if SourceFieldRef.Name() = FSWorkOrderService.FieldName(Duration) then begin + if SourceFieldRef.Name() in [FSWorkOrderService.FieldName(Duration)] then begin DurationInMinutes := FSWorkOrderService.Duration; DurationInHours := (DurationInMinutes / 60); NewValue := DurationInHours - QuantityCurrentlyConsumed; @@ -201,10 +322,14 @@ codeunit 6610 "FS Int. Table Subscriber" if SourceFieldRef.Name() = FSWorkOrderService.FieldName(DurationToBill) then begin DurationInMinutes := FSWorkOrderService.DurationToBill; DurationInHours := (DurationInMinutes / 60); - if JobJournalLine."Line Type" in [JobJournalLine."Line Type"::Budget, JobJournalLine."Line Type"::" "] then - NewValue := 0 - else - NewValue := DurationInHours - QuantityCurrentlyInvoiced; + NewValue := DurationInHours - QuantityCurrentlyInvoiced; + + if (DestinationFieldRef.Record().Number = Database::"Job Journal Line") then begin + DestinationRecordRef := DestinationFieldRef.Record(); + DestinationRecordRef.SetTable(JobJournalLine); + if JobJournalLine."Line Type" in [JobJournalLine."Line Type"::Budget, JobJournalLine."Line Type"::" "] then + NewValue := 0; + end; end; IsValueFound := true; NeedsConversion := false; @@ -212,6 +337,9 @@ codeunit 6610 "FS Int. Table Subscriber" end; FSWorkOrderService.FieldName(Description): begin + if (DestinationFieldRef.Record().Number <> Database::"Job Journal Line") then + exit; + SourceRecordRef := SourceFieldRef.Record(); SourceRecordRef.SetTable(FSWorkOrderService); DestinationRecordRef := DestinationFieldRef.Record(); @@ -236,7 +364,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - if SourceFieldRef.Record().Number = Database::"FS Work Order Product" then + if (SourceFieldRef.Record().Number = Database::"FS Work Order Product") then case SourceFieldRef.Name() of FSWorkOrderProduct.FieldName(Quantity), FSWorkOrderProduct.FieldName(QtyToBill): @@ -266,6 +394,95 @@ codeunit 6610 "FS Int. Table Subscriber" NeedsConversion := false; exit; end; + FSWorkOrderProduct.FieldName(Unit): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrderProduct); + + if not CRMIntegrationRecord.FindRecordIDFromID(FSWorkOrderProduct.Unit, Database::"Item Unit of Measure", NAVItemUomRecordId) then + Error(NotCoupledCRMUomErr); + + if not ItemUnitOfMeasure.Get(NAVItemUomRecordId) then + Error(NotCoupledCRMUomErr); + + NewValue := ItemUnitOfMeasure.Code; + IsValueFound := true; + NeedsConversion := false; + exit; + end; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Int. Table. Subscriber", 'OnFindNewValueForCoupledRecordPK', '', false, false)] + local procedure OnFindNewValueForCoupledRecordPK(IntegrationTableMapping: Record "Integration Table Mapping"; SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean) + var + CRMIntegrationRecord: Record "CRM Integration Record"; + FSWorkOrderService: Record "FS Work Order Service"; + FSWorkOrderProduct: Record "FS Work Order Product"; + ServiceLine: Record "Service Line"; + ItemUnitOfMeasure: Record "Item Unit of Measure"; + CRMUom: Record "CRM Uom"; + SourceRecordRef: RecordRef; + NAVItemUomRecordId: RecordId; + NAVItemUomId: Guid; + NotCoupledCRMUomErr: Label 'The unit is not coupled to a unit of measure.'; + begin + if (SourceFieldRef.Record().Number = Database::"FS Work Order Product") then + case SourceFieldRef.Name() of + FSWorkOrderProduct.FieldName(Unit): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrderProduct); + + if not CRMIntegrationRecord.FindRecordIDFromID(FSWorkOrderProduct.Unit, Database::"Item Unit of Measure", NAVItemUomRecordId) then + Error(NotCoupledCRMUomErr); + + if not ItemUnitOfMeasure.Get(NAVItemUomRecordId) then + Error(NotCoupledCRMUomErr); + + NewValue := ItemUnitOfMeasure.Code; + IsValueFound := true; + exit; + end; + end; + if (SourceFieldRef.Record().Number = Database::"FS Work Order Service") then + case SourceFieldRef.Name() of + FSWorkOrderService.FieldName(Unit): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrderService); + + if not CRMIntegrationRecord.FindRecordIDFromID(FSWorkOrderService.Unit, Database::"Item Unit of Measure", NAVItemUomRecordId) then + Error(NotCoupledCRMUomErr); + + if not ItemUnitOfMeasure.Get(NAVItemUomRecordId) then + Error(NotCoupledCRMUomErr); + + NewValue := ItemUnitOfMeasure.Code; + IsValueFound := true; + exit; + end; + end; + if (SourceFieldRef.Record().Number = Database::"Service Line") then + case SourceFieldRef.Name() of + ServiceLine.FieldName("Unit of Measure Code"): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(ServiceLine); + + if not ItemUnitOfMeasure.Get(ServiceLine."No.", ServiceLine."Unit of Measure Code") then + Error(NotCoupledCRMUomErr); + + if not CRMIntegrationRecord.FindIDFromRecordID(ItemUnitOfMeasure.RecordId, NAVItemUomId) then + Error(NotCoupledCRMUomErr); + + if not CRMUom.Get(NAVItemUomId) then + Error(NotCoupledCRMUomErr); + + NewValue := CRMUom.UoMId; + IsValueFound := true; + exit; + end; end; end; @@ -341,6 +558,18 @@ codeunit 6610 "FS Int. Table Subscriber" end; until JobPlanningLineInvoice.Next() = 0; end; + 'FS Work Order-Service Header': + begin + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + end; + 'Service Header-FS Work Order': + begin + ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderProductFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderServiceFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + end; end; end; @@ -380,6 +609,300 @@ codeunit 6610 "FS Int. Table Subscriber" UpdateCorrelatedJobJournalLine(SourceRecordRef, DestinationRecordRef); ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderService, JobJournalLine); end; + 'FS Work Order-Service Header': + begin + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + end; + 'Service Header-FS Work Order': + begin + ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderProductFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderServiceFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + end; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterUnchangedRecordHandled', '', false, false)] + local procedure HandleOnAfterUnchangedRecordHandled(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + SourceDestCode: Text; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of + 'FS Work Order-Service Header': + begin + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + end; + 'Service Header-FS Work Order': + begin + ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderProductFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderServiceFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + end; + end; + end; + + local procedure ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + ServiceHeader: Record "Service Header"; + ServiceItemLine: Record "Service Item Line"; + ServiceItemLineToDelete: Record "Service Item Line"; + CRMIntegrationRecord: Record "CRM Integration Record"; + FSWorkOrder: Record "FS Work Order"; + FSWorkOrderIncident: Record "FS Work Order Incident"; + FSWorkOrderIncident2: Record "FS Work Order Incident"; + CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; + CRMSalesorderdetailRecordRef: RecordRef; + CRMSalesorderdetailId: Guid; + FSWorkOrderIncidentIdList: List of [Guid]; + FSWorkOrderIncidentIdFilter: Text; + begin + SourceRecordRef.SetTable(FSWorkOrder); + DestinationRecordRef.SetTable(ServiceHeader); + + ServiceItemLine.SetRange("Document Type", ServiceItemLine."Document Type"::Order); + ServiceItemLine.SetRange("Document No.", ServiceHeader."No."); + if ServiceItemLine.FindSet() then + repeat + CRMIntegrationRecord.SetRange("Integration ID", ServiceItemLine.SystemId); + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Item Line"); + if CRMIntegrationRecord.FindFirst() then begin + FSWorkOrderIncident.SetRange(WorkOrderIncidentId, CRMIntegrationRecord."CRM ID"); + if FSWorkOrderIncident.IsEmpty() then begin + CRMIntegrationRecord.Delete(); + if ServiceItemLineToDelete.GetBySystemId(ServiceItemLine.SystemId) then begin + DeleteServiceLines(ServiceItemLine); + ServiceItemLineToDelete.Delete(true); + end; + end; + end; + until ServiceItemLine.Next() = 0; + + FSWorkOrderIncident.Reset(); + FSWorkOrderIncident.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + if FSWorkOrderIncident.FindSet() then begin + repeat + FSWorkOrderIncidentIdList.Add(FSWorkOrderIncident.WorkOrderIncidentId) + until FSWorkOrderIncident.Next() = 0; + + foreach CRMSalesorderdetailId in FSWorkOrderIncidentIdList do + FSWorkOrderIncidentIdFilter += CRMSalesorderdetailId + '|'; + FSWorkOrderIncidentIdFilter := FSWorkOrderIncidentIdFilter.TrimEnd('|'); + + FSWorkOrderIncident2.SetFilter(WorkOrderIncidentId, FSWorkOrderIncidentIdFilter); + CRMSalesorderdetailRecordRef.GetTable(FSWorkOrderIncident2); + CRMIntegrationTableSynch.SynchRecordsFromIntegrationTable(CRMSalesorderdetailRecordRef, Database::"Service Item Line", false, false); + end; + end; + + local procedure DeleteServiceLines(ServiceItemLine: Record "Service Item Line") + var + ServiceLine: Record "Service Line"; + begin + ServiceLine.SetRange("Document Type", ServiceItemLine."Document Type"); + ServiceLine.SetRange("Document No.", ServiceItemLine."Document No."); + ServiceLine.SetRange("Service Item Line No.", ServiceItemLine."Line No."); + if not ServiceLine.IsEmpty() then + ServiceLine.DeleteAll(true); + end; + + local procedure ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + ServiceLineToDelete: Record "Service Line"; + CRMIntegrationRecord: Record "CRM Integration Record"; + FSWorkOrder: Record "FS Work Order"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderProduct2: Record "FS Work Order Product"; + CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; + FSWorkOrderProductRecordRef: RecordRef; + CRMSalesorderdetailId: Guid; + CRMSalesorderdetailIdList: List of [Guid]; + CRMSalesorderdetailIdFilter: Text; + begin + SourceRecordRef.SetTable(FSWorkOrder); + DestinationRecordRef.SetTable(ServiceHeader); + + ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); + ServiceLine.SetRange("Document No.", ServiceHeader."No."); + if ServiceLine.FindSet() then + repeat + CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Line"); + if CRMIntegrationRecord.FindFirst() then begin + FSWorkOrderProduct.SetRange(WorkOrderProductId, CRMIntegrationRecord."CRM ID"); + if FSWorkOrderProduct.IsEmpty() then begin + CRMIntegrationRecord.Delete(); + if ServiceLineToDelete.GetBySystemId(ServiceLine.SystemId) then + ServiceLineToDelete.Delete(true); + end; + end; + until ServiceLine.Next() = 0; + + FSWorkOrderProduct.Reset(); + FSWorkOrderProduct.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + if FSWorkOrderProduct.FindSet() then begin + repeat + CRMSalesorderdetailIdList.Add(FSWorkOrderProduct.WorkOrderProductId) + until FSWorkOrderProduct.Next() = 0; + + foreach CRMSalesorderdetailId in CRMSalesorderdetailIdList do + CRMSalesorderdetailIdFilter += CRMSalesorderdetailId + '|'; + CRMSalesorderdetailIdFilter := CRMSalesorderdetailIdFilter.TrimEnd('|'); + + FSWorkOrderProduct2.SetFilter(WorkOrderProductId, CRMSalesorderdetailIdFilter); + FSWorkOrderProductRecordRef.GetTable(FSWorkOrderProduct2); + CRMIntegrationTableSynch.SynchRecordsFromIntegrationTable(FSWorkOrderProductRecordRef, Database::"Service Line", false, false); + end; + end; + + local procedure ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Item Line"; + ServiceItemLineToDelete: Record "Service Item Line"; + CRMIntegrationRecord: Record "CRM Integration Record"; + FSWorkOrder: Record "FS Work Order"; + FSWorkOrderService: Record "FS Work Order Service"; + FSWorkOrderService2: Record "FS Work Order Service"; + CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; + FSWorkOrderServiceRecordRef: RecordRef; + CRMSalesorderdetailId: Guid; + CRMSalesorderdetailIdList: List of [Guid]; + CRMSalesorderdetailIdFilter: Text; + begin + SourceRecordRef.SetTable(FSWorkOrder); + DestinationRecordRef.SetTable(ServiceHeader); + + ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); + ServiceLine.SetRange("Document No.", ServiceHeader."No."); + if ServiceLine.FindSet() then + repeat + CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Line"); + if CRMIntegrationRecord.FindFirst() then begin + FSWorkOrderService.SetRange(WorkOrderServiceId, CRMIntegrationRecord."CRM ID"); + if FSWorkOrderService.IsEmpty() then begin + CRMIntegrationRecord.Delete(); + if ServiceItemLineToDelete.GetBySystemId(ServiceLine.SystemId) then + ServiceItemLineToDelete.Delete(true); + end; + end; + until ServiceLine.Next() = 0; + + FSWorkOrderService.Reset(); + FSWorkOrderService.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + if FSWorkOrderService.FindSet() then begin + repeat + CRMSalesorderdetailIdList.Add(FSWorkOrderService.WorkOrderServiceId) + until FSWorkOrderService.Next() = 0; + + foreach CRMSalesorderdetailId in CRMSalesorderdetailIdList do + CRMSalesorderdetailIdFilter += CRMSalesorderdetailId + '|'; + CRMSalesorderdetailIdFilter := CRMSalesorderdetailIdFilter.TrimEnd('|'); + + FSWorkOrderService2.SetFilter(WorkOrderServiceId, CRMSalesorderdetailIdFilter); + FSWorkOrderServiceRecordRef.GetTable(FSWorkOrderService2); + CRMIntegrationTableSynch.SynchRecordsFromIntegrationTable(FSWorkOrderServiceRecordRef, Database::"Service Line", false, false); + end; + end; + + local procedure ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + ServiceHeader: Record "Service Header"; + FSWorkOrder: Record "FS Work Order"; + ServiceItemLine: Record "Service Item Line"; + CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; + ServiceItemLineRecordRef: RecordRef; + begin + SourceRecordRef.SetTable(ServiceHeader); + DestinationRecordRef.SetTable(FSWorkOrder); + + ServiceItemLine.SetRange("Document Type", ServiceHeader."Document Type"); + ServiceItemLine.SetRange("Document No.", ServiceHeader."No."); + if not ServiceItemLine.IsEmpty() then begin + ServiceItemLineRecordRef.GetTable(ServiceItemLine); + CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(ServiceItemLineRecordRef, false, false); + end; + end; + + local procedure ResetFSWorkOrderProductFromServiceOrderLine(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + ServiceHeader: Record "Service Header"; + FSWorkOrder: Record "FS Work Order"; + ServiceLine: Record "Service Line"; + ServiceLineRecordRef: RecordRef; + begin + SourceRecordRef.SetTable(ServiceHeader); + DestinationRecordRef.SetTable(FSWorkOrder); + + ServiceLine.Reset(); + ServiceLine.SetRange("Document Type", ServiceHeader."Document Type"); + ServiceLine.SetRange("Document No.", ServiceHeader."No."); + ServiceLine.SetRange("Type", ServiceLine.Type::Item); + ServiceLine.SetFilter("Item Type", '%1|%2', ServiceLine."Item Type"::Inventory, ServiceLine."Item Type"::"Non-Inventory"); + if not ServiceLine.IsEmpty() then begin + ServiceLineRecordRef.GetTable(ServiceLine); + SynchRecordsToIntegrationTable(ServiceLineRecordRef, Database::"FS Work Order Product", false, false); + end; + end; + + local procedure ResetFSWorkOrderServiceFromServiceOrderLine(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + ServiceHeader: Record "Service Header"; + FSWorkOrder: Record "FS Work Order"; + ServiceLine: Record "Service Line"; + ServiceLineRecordRef: RecordRef; + begin + SourceRecordRef.SetTable(ServiceHeader); + DestinationRecordRef.SetTable(FSWorkOrder); + + ServiceLine.Reset(); + ServiceLine.SetRange("Document Type", ServiceHeader."Document Type"); + ServiceLine.SetRange("Document No.", ServiceHeader."No."); + ServiceLine.SetRange("Type", ServiceLine.Type::Item); + ServiceLine.SetRange("Item Type", ServiceLine."Item Type"::Service); + if not ServiceLine.IsEmpty() then begin + ServiceLineRecordRef.GetTable(ServiceLine); + SynchRecordsToIntegrationTable(ServiceLineRecordRef, Database::"FS Work Order Service", false, false); + end; + end; + + procedure SynchRecordsToIntegrationTable(var RecordsToSynchRecordRef: RecordRef; TargetTable: Integer; IgnoreChanges: Boolean; IgnoreSynchOnlyCoupledRecords: Boolean) JobID: Guid + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationTableSynch: Codeunit "Integration Table Synch."; + IntegrationRecordRef: RecordRef; + SynchronizeEmptySetErr: Label 'Attempted to synchronize an empty set of records.'; + begin + IntegrationTableMapping.SetRange("Table ID", RecordsToSynchRecordRef.Number); + IntegrationTableMapping.SetRange("Integration Table ID", TargetTable); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + if not IntegrationTableMapping.FindFirst() then + Error(SynchronizeEmptySetErr); + + RecordsToSynchRecordRef.Ascending(false); + if not RecordsToSynchRecordRef.FindSet() then + Error(SynchronizeEmptySetErr); + + JobID := + IntegrationTableSynch.BeginIntegrationSynchJob( + TableConnectionType::CRM, IntegrationTableMapping, RecordsToSynchRecordRef.Number); + if not IsNullGuid(JobID) then begin + repeat + IntegrationTableSynch.Synchronize(RecordsToSynchRecordRef, IntegrationRecordRef, IgnoreChanges, IgnoreSynchOnlyCoupledRecords) + until RecordsToSynchRecordRef.Next() = 0; + IntegrationTableSynch.EndIntegrationSynchJob(); end; end; @@ -487,6 +1010,18 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(JobJournalLine); ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderService, JobJournalLine); end; + 'FS Work Order-Service Header': + begin + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + end; + 'Service Header-FS Work Order': + begin + ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderProductFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + ResetFSWorkOrderServiceFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); + end; end; end; @@ -558,12 +1093,21 @@ codeunit 6610 "FS Int. Table Subscriber" Resource: Record Resource; FSBookableResource: Record "FS Bookable Resource"; LastJobJournalLine: Record "Job Journal Line"; + FSWorkOrderIncident: Record "FS Work Order Incident"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + ServiceItemLine: Record "Service Item Line"; + ServiceLine: Record "Service Line"; CRMProductName: Codeunit "CRM Product Name"; NoSeries: Codeunit "No. Series"; + FSIntegrationMgt: Codeunit "FS Integration Mgt."; RecID: RecordId; SourceDestCode: Text; BillingAccId: Guid; ServiceAccId: Guid; + WorkOrderId: Guid; + WorkOrderIncidentId: Guid; Handled: Boolean; begin if not FSConnectionSetup.IsEnabled() then @@ -680,9 +1224,84 @@ codeunit 6610 "FS Int. Table Subscriber" end; DestinationRecordRef.GetTable(JobJournalLine); end; + 'Service Item Line-FS Work Order Incident': + begin + SourceRecordRef.SetTable(ServiceItemLine); + DestinationRecordRef.SetTable(FSWorkOrderIncident); + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceItemLine."Document No."), WorkOrderId) then + FSWorkOrderIncident.WorkOrder := WorkOrderId; + FSWorkOrderIncident.IncidentType := FSIntegrationMgt.GetDefaultWorkOrderIncident(); + DestinationRecordRef.GetTable(FSWorkOrderIncident); + end; + 'Service Line-FS Work Order Product': + begin + SourceRecordRef.SetTable(ServiceLine); + DestinationRecordRef.SetTable(FSWorkOrderProduct); + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceLine."Document No."), WorkOrderId) then + FSWorkOrderProduct.WorkOrder := WorkOrderId; + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderItemLineRecordId(ServiceLine."Document No.", ServiceLine."Service Item Line No."), WorkOrderIncidentId) then + FSWorkOrderProduct.WorkOrderIncident := WorkOrderIncidentId; + DestinationRecordRef.GetTable(FSWorkOrderProduct); + end; + 'Service Line-FS Work Order Service': + begin + SourceRecordRef.SetTable(ServiceLine); + DestinationRecordRef.SetTable(FSWorkOrderService); + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceLine."Document No."), WorkOrderId) then + FSWorkOrderService.WorkOrder := WorkOrderId; + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderItemLineRecordId(ServiceLine."Document No.", ServiceLine."Service Item Line No."), WorkOrderIncidentId) then + FSWorkOrderService.WorkOrderIncident := WorkOrderIncidentId; + DestinationRecordRef.GetTable(FSWorkOrderService); + end; + 'Service Line-FS Bookable Resource Booking': + begin + SourceRecordRef.SetTable(ServiceLine); + DestinationRecordRef.SetTable(FSBookableResourceBooking); + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceItemLine."Document No."), WorkOrderId) then + FSBookableResourceBooking.WorkOrder := WorkOrderId; + DestinationRecordRef.GetTable(ServiceLine); + end; end; end; + local procedure GetNextLineNo(ServiceItemLine: Record "Service Item Line"): Integer + var + ServiceItemLineSearch: Record "Service Item Line"; + begin + ServiceItemLineSearch.SetRange("Document Type", ServiceItemLine."Document Type"); + ServiceItemLineSearch.SetRange("Document No.", ServiceItemLine."Document No."); + if ServiceItemLineSearch.FindLast() then + exit(ServiceItemLineSearch."Line No." + 10000); + exit(10000); + end; + + local procedure GetNextLineNo(ServiceLine: Record "Service Line"): Integer + var + ServiceLineSearch: Record "Service Line"; + begin + ServiceLineSearch.SetRange("Document Type", ServiceLine."Document Type"); + ServiceLineSearch.SetRange("Document No.", ServiceLine."Document No."); + if ServiceLineSearch.FindLast() then + exit(ServiceLineSearch."Line No." + 10000); + exit(10000); + end; + + local procedure GetServiceOrderRecordId(DocumentNo: Code[20]): RecordId + var + ServiceHeader: Record "Service Header"; + begin + if ServiceHeader.Get(ServiceHeader."Document Type"::Order, DocumentNo) then + exit(ServiceHeader.RecordId); + end; + + local procedure GetServiceOrderItemLineRecordId(DocumentNo: Code[20]; LineNo: Integer): RecordId + var + ServiceItemLine: Record "Service Item Line"; + begin + if ServiceItemLine.Get(ServiceItemLine."Document Type"::Order, DocumentNo, LineNo) then + exit(ServiceItemLine.RecordId); + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnDeletionConflictDetectedSetRecordStateAndSynchAction', '', false, false)] local procedure HandleOnDeletionConflictDetectedSetRecordStateAndSynchAction(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var CoupledRecordRef: RecordRef; var RecordState: Option NotFound,Coupled,Decoupled; var SynchAction: Option "None",Insert,Modify,ForceModify,IgnoreUnchanged,Fail,Skip,Delete,Uncouple,Couple; var DeletionConflictHandled: Boolean) var @@ -1194,6 +1813,14 @@ codeunit 6610 "FS Int. Table Subscriber" exit(''); end; + [EventSubscriber(ObjectType::Table, Database::"Service Mgt. Setup", 'OnBeforeValidateEvent', 'One Service Item Line/Order', true, false)] + local procedure ServiceMgtSetupOnBeforeValidateOneServiceItemLinePerOrder(var Rec: Record "Service Mgt. Setup") + var + IntegrationMgt: Codeunit "FS Integration Mgt."; + begin + IntegrationMgt.TestOneServiceItemLinePerOrderModificationIsAllowed(Rec); + end; + [IntegrationEvent(false, false)] local procedure OnSetUpNewLineOnNewLine(var JobJournalLine: Record "Job Journal Line"; var JobJournalTemplate: Record "Job Journal Template"; var JobJournalBatch: Record "Job Journal Batch"; var Handled: Boolean); begin diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al index 10eeca4245..a074a36db5 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al @@ -6,8 +6,10 @@ namespace Microsoft.Integration.DynamicsFieldService; using Microsoft.Integration.Dataverse; using Microsoft.Integration.D365Sales; +using Microsoft.Service.Setup; using System; using Microsoft.Utilities; +using Microsoft.Foundation.NoSeries; codeunit 6615 "FS Integration Mgt." { @@ -187,6 +189,72 @@ codeunit 6615 "FS Integration Mgt." exit(GuidVar); end; + internal procedure TestManualNoSeriesFlag(IntegrationType: Enum "FS Integration Type") + var + ServiceMgtSetup: Record "Service Mgt. Setup"; + NoSeries: Codeunit "No. Series"; + NoManualNoSeriesErr: Label 'Manual No. Series is not supported for Service Order Nos. Please make sure that the No. Series setup is correct.'; + begin + if not (IntegrationType in [IntegrationType::Service, IntegrationType::Both]) then + exit; + + ServiceMgtSetup.Get(); + if not NoSeries.IsManual(ServiceMgtSetup."Service Order Nos.") then + Error(NoManualNoSeriesErr); + end; + + internal procedure TestOneServiceItemLinePerOrder(IntegrationType: Enum "FS Integration Type") + var + ServiceMgtSetup: Record "Service Mgt. Setup"; + begin + if not (IntegrationType in [IntegrationType::Service, IntegrationType::Both]) then + exit; + + ServiceMgtSetup.Get(); + ServiceMgtSetup.TestField("One Service Item Line/Order", false); + end; + + internal procedure TestOneServiceItemLinePerOrderModificationIsAllowed(ServiceMgtSetup: Record "Service Mgt. Setup") + var + ConnectionSetup: Record "FS Connection Setup"; + begin + if not ServiceMgtSetup."One Service Item Line/Order" then + exit; + + ConnectionSetup.Get(); + if not (ConnectionSetup."Integration Type" in [ConnectionSetup."Integration Type"::Service, ConnectionSetup."Integration Type"::Both]) then + exit; + + ConnectionSetup.TestField("Is Enabled", false); + end; + + internal procedure GetDefaultWorkOrderIncident(): Guid + var + ConnectionSetup: Record "FS Connection Setup"; + begin + ConnectionSetup.Get(); + + if IsNullGuid(ConnectionSetup."Default Work Order Incident ID") then begin + ConnectionSetup."Default Work Order Incident ID" := GenerateDefaultWorkOrderIncident(); + ConnectionSetup.Modify(); + end; + + exit(ConnectionSetup."Default Work Order Incident ID"); + end; + + internal procedure GenerateDefaultWorkOrderIncident(): Guid + var + IncidentType: Record "FS Incident Type"; + IncidentTypeNameLbl: Label 'Business Central - Default Incident Type'; + begin + IncidentType.IncidentTypeId := CreateGuid(); + IncidentType.Name := IncidentTypeNameLbl; + IncidentType.Insert(); + + exit(IncidentType.IncidentTypeId); + end; + + [EventSubscriber(ObjectType::Table, Database::"Service Connection", 'OnRegisterServiceConnection', '', false, false)] local procedure RegisterFSConnectionOnRegisterServiceConnection(var ServiceConnection: Record "Service Connection") var diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al index 85d78bb769..83e33bc666 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al @@ -26,6 +26,12 @@ codeunit 6612 "FS Lookup FS Tables" Database::"FS Customer Asset": if LookupFSCustomerAsset(SavedCRMId, CRMId, IntTableFilter) then Handled := true; + Database::"FS Work Order": + if LookupFSWorkOrder(SavedCRMId, CRMId, IntTableFilter) then + Handled := true; + Database::"FS Work Order Type": + if LookupFSWorkOrderType(SavedCRMId, CRMId, IntTableFilter) then + Handled := true; end; end; @@ -78,4 +84,54 @@ codeunit 6612 "FS Lookup FS Tables" end; exit(false); end; + + local procedure LookupFSWorkOrderType(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean + var + FSWorkOrderType: Record "FS Work Order Type"; + OriginalFSWorkOrderType: Record "FS Work Order Type"; + FSWorkOrderTypes: Page "FS Work Order Types"; + begin + if not IsNullGuid(CRMId) then begin + if FSWorkOrderType.Get(CRMId) then + FSWorkOrderTypes.SetRecord(FSWorkOrderType); + if not IsNullGuid(SavedCRMId) then + if OriginalFSWorkOrderType.Get(SavedCRMId) then + FSWorkOrderTypes.SetCurrentlyCoupledFSWorkOrderType(OriginalFSWorkOrderType); + end; + FSWorkOrderType.SetView(IntTableFilter); + FSWorkOrderTypes.SetTableView(FSWorkOrderType); + FSWorkOrderTypes.LookupMode(true); + Commit(); + if FSWorkOrderTypes.RunModal() = Action::LookupOK then begin + FSWorkOrderTypes.GetRecord(FSWorkOrderType); + CRMId := FSWorkOrderType.WorkOrderTypeId; + exit(true); + end; + exit(false); + end; + + local procedure LookupFSWorkOrder(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean + var + FSWorkOrderType: Record "FS Work Order"; + OriginalFSWorkOrder: Record "FS Work Order"; + FSWorkOrders: Page "FS Work Orders"; + begin + if not IsNullGuid(CRMId) then begin + if FSWorkOrderType.Get(CRMId) then + FSWorkOrders.SetRecord(FSWorkOrderType); + if not IsNullGuid(SavedCRMId) then + if OriginalFSWorkOrder.Get(SavedCRMId) then + FSWorkOrders.SetCurrentlyCoupledFSWorkOrder(OriginalFSWorkOrder); + end; + FSWorkOrderType.SetView(IntTableFilter); + FSWorkOrders.SetTableView(FSWorkOrderType); + FSWorkOrders.LookupMode(true); + Commit(); + if FSWorkOrders.RunModal() = Action::LookupOK then begin + FSWorkOrders.GetRecord(FSWorkOrderType); + CRMId := FSWorkOrderType.WorkOrderId; + exit(true); + end; + exit(false); + end; } \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index f702e67cae..22c34634a5 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -6,10 +6,13 @@ namespace Microsoft.Integration.DynamicsFieldService; using Microsoft.Integration.Dataverse; using Microsoft.Integration.D365Sales; +using Microsoft.Service.Setup; using Microsoft.Integration.SyncEngine; using Microsoft.Inventory.Location; +using Microsoft.Service.Document; using Microsoft.Inventory.Setup; using Microsoft.Utilities; +using Microsoft.Inventory.Item; using System.Threading; using Microsoft.Projects.Project.Job; using Microsoft.Service.Item; @@ -30,6 +33,7 @@ codeunit 6611 "FS Setup Defaults" internal procedure ResetConfiguration(var FSConnectionSetup: Record "FS Connection Setup") var CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + FSIntegrationMgt: Codeunit "FS Integration Mgt."; IsHandled: Boolean; begin IsHandled := false; @@ -40,12 +44,30 @@ codeunit 6611 "FS Setup Defaults" CDSIntegrationMgt.RegisterConnection(); CDSIntegrationMgt.ActivateConnection(); - ResetProjectTaskMapping(FSConnectionSetup, 'PROJECTTASK', true); - ResetProjectJournalLineWOProductMapping(FSConnectionSetup, 'PJLINE-WORDERPRODUCT', true); - ResetProjectJournalLineWOServiceMapping(FSConnectionSetup, 'PJLINE-WORDERSERVICE', true); + if FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Project, + FSConnectionSetup."Integration Type"::Both] then begin + ResetProjectTaskMapping(FSConnectionSetup, 'PROJECTTASK', true); + ResetProjectJournalLineWOProductMapping(FSConnectionSetup, 'PJLINE-WORDERPRODUCT', true); + ResetProjectJournalLineWOServiceMapping(FSConnectionSetup, 'PJLINE-WORDERSERVICE', true); + end; + ResetServiceItemCustomerAssetMapping(FSConnectionSetup, 'SVCITEM-CUSTASSET', true); ResetResourceBookableResourceMapping(FSConnectionSetup, 'RESOURCE-BOOKABLERSC', true); ResetLocationMapping(FSConnectionSetup, 'LOCATION', true); + + if FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Service, + FSConnectionSetup."Integration Type"::Both] then begin + ResetServiceOrderTypeMapping(FSConnectionSetup, 'SRVORDERTYPE', true); + ResetServiceOrderMapping(FSConnectionSetup, 'SRVORDER', true); + ResetServiceOrderItemLineMapping(FSConnectionSetup, 'SRVORDERITEMLINE', true); + ResetServiceOrderLineItemMapping(FSConnectionSetup, 'SRVORDERLINE-ITEM', true); + ResetServiceOrderLineServiceItemMapping(FSConnectionSetup, 'SRVORDERLINE-SERVICE', true); + ResetServiceOrderLineResourceMapping(FSConnectionSetup, 'SRVORDERLINE-RESOURC', true); + + if IsNullGuid(FSConnectionSetup."Default Work Order Incident ID") then + FSConnectionSetup."Default Work Order Incident ID" := FSIntegrationMgt.GenerateDefaultWorkOrderIncident(); + end; + SetCustomIntegrationsTableMappings(FSConnectionSetup); end; @@ -360,6 +382,7 @@ codeunit 6611 "FS Setup Defaults" ServiceItem.Reset(); ServiceItem.SetRange(Blocked, ServiceItem.Blocked::" "); + ServiceItem.SetRange("Service Item Components", false); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, @@ -453,6 +476,448 @@ codeunit 6611 "FS Setup Defaults" RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 5); end; + local procedure ResetServiceOrderTypeMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + ServiceOrderType: Record "Service Order Type"; + FSWorkOrderType: Record "FS Work Order Type"; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeResetServiceOrderTypeMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + if not (FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Service, + FSConnectionSetup."Integration Type"::Both]) then + exit; + + FSWorkOrderType.Reset(); + FSWorkOrderType.SetRange(StateCode, FSWorkOrderType.StateCode::Active); + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Order Type", Database::"FS Work Order Type", + FSWorkOrderType.FieldNo(WorkOrderTypeId), FSWorkOrderType.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Work Order Type", FSWorkOrderType.TableCaption(), FSWorkOrderType.GetView())); + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrderType.FieldNo(Code), + FSWorkOrderType.FieldNo(Code), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrderType.FieldNo(Description), + FSWorkOrderType.FieldNo(Name), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 30); + end; + + local procedure ResetServiceOrderMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + ServiceOrder: Record "Service Header"; + FSWorkOrder: Record "FS Work Order"; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeResetServiceOrderMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + if not (FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Service, + FSConnectionSetup."Integration Type"::Both]) then + exit; + + ServiceOrder.Reset(); + ServiceOrder.SetRange("Document Type", ServiceOrder."Document Type"::Order); + FSWorkOrder.Reset(); + FSWorkOrder.SetRange("Integrate to Service", true); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Header", Database::"FS Work Order", + FSWorkOrder.FieldNo(WorkOrderId), FSWorkOrder.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::"Service Header", ServiceOrder.TableCaption(), ServiceOrder.GetView())); + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Work Order", FSWorkOrder.TableCaption(), FSWorkOrder.GetView())); + IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|SRVORDERTYPE'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrder.FieldNo("Document Type"), + 0, IntegrationFieldMapping.Direction::FromIntegrationTable, + 'Order', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrder.FieldNo("No."), + FSWorkOrder.FieldNo(Name), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrder.FieldNo("Customer No."), + FSWorkOrder.FieldNo(ServiceAccount), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrder.FieldNo("Order Date"), + FSWorkOrder.FieldNo(CreatedOn), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrder.FieldNo("Service Order Type"), + FSWorkOrder.FieldNo(WorkOrderType), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrder.FieldNo("Work Description"), + FSWorkOrder.FieldNo(WorkOrderSummary), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 30); + end; + + local procedure ResetServiceOrderItemLineMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + ServiceItemLine: Record "Service Item Line"; + FSWorkOrderIncident: Record "FS Work Order Incident"; + EmptyGuid: Guid; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeResetServiceOrderItemLineMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + if not (FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Service, + FSConnectionSetup."Integration Type"::Both]) then + exit; + + ServiceItemLine.Reset(); + ServiceItemLine.SetRange("Document Type", ServiceItemLine."Document Type"::Order); + FSWorkOrderIncident.Reset(); + FSWorkOrderIncident.SetFilter(CustomerAsset, '<>%1', EmptyGuid); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Item Line", Database::"FS Work Order Incident", + FSWorkOrderIncident.FieldNo(WorkOrderIncidentId), FSWorkOrderIncident.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::"Service Item Line", ServiceItemLine.TableCaption(), ServiceItemLine.GetView())); + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Work Order Incident", FSWorkOrderIncident.TableCaption(), FSWorkOrderIncident.GetView())); + IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SVCITEM-CUSTASSET'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceItemLine.FieldNo("Document Type"), + 0, IntegrationFieldMapping.Direction::FromIntegrationTable, + 'Order', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceItemLine.FieldNo("Service Item No."), + FSWorkOrderIncident.FieldNo(CustomerAsset), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + end; + + local procedure ResetServiceOrderLineItemMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + ServiceLine: Record "Service Line"; + FSWorkOrderProduct: Record "FS Work Order Product"; + EmptyGuid: Guid; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeResetServiceOrderLineItemMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + if not (FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Service, + FSConnectionSetup."Integration Type"::Both]) then + exit; + + ServiceLine.Reset(); + ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); + ServiceLine.SetRange(Type, ServiceLine.Type::Item); + ServiceLine.SetFilter("Item Type", '%1|%2', ServiceLine."Item Type"::Inventory, ServiceLine."Item Type"::"Non-Inventory"); + FSWorkOrderProduct.Reset(); + FSWorkOrderProduct.SetFilter(WorkOrderIncident, '<>%1', EmptyGuid); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Line", Database::"FS Work Order Product", + FSWorkOrderProduct.FieldNo(WorkOrderProductId), FSWorkOrderProduct.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::"Service Line", ServiceLine.TableCaption(), ServiceLine.GetView())); + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Work Order Product", FSWorkOrderProduct.TableCaption(), FSWorkOrderProduct.GetView())); + IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SRVORDERITEMLINE'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Document Type"), + 0, IntegrationFieldMapping.Direction::FromIntegrationTable, + 'Order', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("No."), + FSWorkOrderProduct.FieldNo(Product), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo(Description), + FSWorkOrderProduct.FieldNo(Name), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Location Code"), + FSWorkOrderProduct.FieldNo(WarehouseId), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Unit of Measure Code"), + FSWorkOrderProduct.FieldNo(Unit), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo(Quantity), + FSWorkOrderProduct.FieldNo(EstimateQuantity), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Ship"), + FSWorkOrderProduct.FieldNo(Quantity), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Consume"), + FSWorkOrderProduct.FieldNo(QtyToBill), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Invoice"), + FSWorkOrderProduct.FieldNo(QtyToBill), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Invoice"), + FSWorkOrderProduct.FieldNo(QtyToBill), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + end; + + local procedure ResetServiceOrderLineServiceItemMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + ServiceLine: Record "Service Line"; + FSWorkOrderService: Record "FS Work Order Service"; + EmptyGuid: Guid; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeResetServiceOrderLineItemMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + if not (FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Service, + FSConnectionSetup."Integration Type"::Both]) then + exit; + + ServiceLine.Reset(); + ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); + ServiceLine.SetRange(Type, ServiceLine.Type::Item); + ServiceLine.SetRange("Item Type", ServiceLine."Item Type"::Service); + FSWorkOrderService.Reset(); + FSWorkOrderService.SetFilter(WorkOrderIncident, '<>%1', EmptyGuid); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Line", Database::"FS Work Order Service", + FSWorkOrderService.FieldNo(WorkOrderServiceId), FSWorkOrderService.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::"Service Line", ServiceLine.TableCaption(), ServiceLine.GetView())); + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Work Order Service", FSWorkOrderService.TableCaption(), FSWorkOrderService.GetView())); + IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SRVORDERITEMLINE'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Document Type"), + 0, IntegrationFieldMapping.Direction::FromIntegrationTable, + 'Order', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("No."), + FSWorkOrderService.FieldNo(Service), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo(Description), + FSWorkOrderService.FieldNo(Name), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Unit of Measure Code"), + FSWorkOrderService.FieldNo(Unit), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo(Quantity), + FSWorkOrderService.FieldNo(EstimateDuration), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Ship"), + FSWorkOrderService.FieldNo(Duration), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Consume"), + FSWorkOrderService.FieldNo(DurationToBill), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Invoice"), + FSWorkOrderService.FieldNo(DurationToBill), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + end; + + local procedure ResetServiceOrderLineResourceMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + ServiceLine: Record "Service Line"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + IsHandled: Boolean; + EmptyGuid: Guid; + begin + IsHandled := false; + OnBeforeResetServiceOrderLineResourceMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + if not (FSConnectionSetup."Integration Type" in [FSConnectionSetup."Integration Type"::Service, + FSConnectionSetup."Integration Type"::Both]) then + exit; + + ServiceLine.Reset(); + ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); + ServiceLine.SetRange(Type, ServiceLine.Type::Resource); + FSBookableResourceBooking.Reset(); + FSBookableResourceBooking.SetFilter(WorkOrder, '<>%1', EmptyGuid); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Line", Database::"FS Bookable Resource Booking", + FSBookableResourceBooking.FieldNo(BookableResourceBookingId), FSBookableResourceBooking.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::"Service Line", ServiceLine.TableCaption(), ServiceLine.GetView())); + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Bookable Resource Booking", FSBookableResourceBooking.TableCaption(), FSBookableResourceBooking.GetView())); + IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SRVORDERITEMLINE|RESOURCE-BOOKABLERSC'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Document Type"), + 0, IntegrationFieldMapping.Direction::FromIntegrationTable, + 'Order', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("No."), + FSBookableResourceBooking.FieldNo(Resource), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo(Description), + FSBookableResourceBooking.FieldNo(Name), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo(Quantity), + FSBookableResourceBooking.FieldNo(Duration), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + end; + local procedure ShouldResetServiceItemMapping(): Boolean var ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade"; @@ -570,6 +1035,10 @@ codeunit 6611 "FS Setup Defaults" case BCTableNo of Database::Resource: CDSTableNo := Database::"FS Bookable Resource"; + Database::"Service Order Type": + CDSTableNo := Database::"FS Work Order Type"; + Database::"Service Header": + CDSTableNo := Database::"FS Work Order"; Database::"Service Item": CDSTableNo := Database::"FS Customer Asset"; Database::"Job Task": @@ -609,6 +1078,12 @@ codeunit 6611 "FS Setup Defaults" CRMSetupDefaults.AddEntityTableMapping('msdyn_warehouse', Database::Location, TempNameValueBuffer); CRMSetupDefaults.AddEntityTableMapping('msdyn_warehouse', Database::"FS Warehouse", TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('msdyn_workordertype', Database::"Service Order Type", TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('msdyn_workordertype', Database::"FS Work Order Type", TempNameValueBuffer); + + CRMSetupDefaults.AddEntityTableMapping('msdyn_workorder', Database::"Service Header", TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('msdyn_workorder', Database::"FS Work Order", TempNameValueBuffer); + TempNameValueBuffer.SetRange(Name, 'product'); TempNameValueBuffer.SetRange(Value, Format(Database::Resource)); if TempNameValueBuffer.FindFirst() then @@ -620,9 +1095,13 @@ codeunit 6611 "FS Setup Defaults" local procedure ReturnNameFieldNoOnBeforeGetNameFieldNo(TableId: Integer; var FieldNo: Integer) var FSConnectionSetup: Record "FS Connection Setup"; + ServiceOrderType: Record "Service Order Type"; + ServiceOrder: Record "Service Header"; ServiceItem: Record "Service Item"; FSCustomerAsset: Record "FS Customer Asset"; FSBookableResource: Record "FS Bookable Resource"; + FSWorkOrderType: Record "FS Work Order Type"; + FSWorkOrder: Record "FS Work Order"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; FSProjectTask: Record "FS Project Task"; @@ -635,12 +1114,20 @@ codeunit 6611 "FS Setup Defaults" exit; case TableId of + Database::"Service Order Type": + FieldNo := ServiceOrderType.FieldNo(Code); + Database::"Service Header": + FieldNo := ServiceOrder.FieldNo("No."); Database::"Service Item": FieldNo := ServiceItem.FieldNo("No."); Database::"FS Customer Asset": FieldNo := FSCustomerAsset.FieldNo(Name); Database::"FS Bookable Resource": FieldNo := FSBookableResource.FieldNo(Name); + Database::"FS Work Order Type": + FieldNo := FSWorkOrderType.FieldNo(Code); + Database::"FS Work Order": + FieldNo := FSWorkOrder.FieldNo(Name); Database::"FS Work Order Product": FieldNo := FSWorkOrderProduct.FieldNo(Name); Database::"FS Work Order Service": @@ -663,6 +1150,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMapping: Record "Integration Table Mapping"; begin case NAVTableID of + Database::"Service Order Type", + Database::"Service Header", Database::"Service Item", Database::"Work Type", Database::"Resource": @@ -779,6 +1268,31 @@ codeunit 6611 "FS Setup Defaults" begin end; + [IntegrationEvent(false, false)] + local procedure OnBeforeResetServiceOrderTypeMapping(IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetServiceOrderMapping(IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetServiceOrderItemLineMapping(IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetServiceOrderLineItemMapping(IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetServiceOrderLineResourceMapping(IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + [IntegrationEvent(false, false)] local procedure OnCreateJobQueueEntryOnBeforeJobQueueEnqueue(var JobQueueEntry: Record "Job Queue Entry"; var IntegrationTableMapping: Record "Integration Table Mapping"; JobCodeunitId: Integer; JobDescription: Text) begin diff --git a/Apps/W1/FieldServiceIntegration/app/src/Enums/FSIntegrationType.Enum.al b/Apps/W1/FieldServiceIntegration/app/src/Enums/FSIntegrationType.Enum.al new file mode 100644 index 0000000000..421361466f --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Enums/FSIntegrationType.Enum.al @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +enum 6612 "FS Integration Type" +{ + Extensible = true; + + value(0; Project) + { + Caption = 'Projects (default)'; + } + value(1; Service) + { + Caption = 'Service'; + } + value(2; Both) + { + Caption = 'Both'; + } +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al new file mode 100644 index 0000000000..85cb151538 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al @@ -0,0 +1,224 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using Microsoft.Integration.Dataverse; + +pageextension 6614 "FS Service Order" extends "Service Order" +{ + layout + { + addafter(Status) + { + group("Work Description") + { + Caption = 'Work Description'; + field(WorkDescription; WorkDescription) + { + ApplicationArea = Service; + Caption = 'Work Description'; + ShowCaption = false; + ToolTip = 'Specifies the products or service being offered.'; + MultiLine = true; + Importance = Additional; + + trigger OnValidate() + begin + Rec.SetWorkDescription(WorkDescription); + end; + } + } + } + } + + actions + { + addlast(navigation) + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Enabled = FSIntegrationEnabled; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Work Order'; + Image = ViewServiceOrder; + ToolTip = 'Open the coupled Dynamics 365 Field Service record.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.ShowCRMEntityFromRecordID(Rec.RecordId); + end; + } + action(SynchronizeNowFS) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Synchronize'; + Image = Refresh; + ToolTip = 'Send updated data to Dynamics 365 Field Service.'; + + trigger OnAction() + var + ServiceHeader: Record "Service Header"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + ServiceHeaderRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceHeader); + ServiceHeader.Next(); + + if ServiceHeader.Count = 1 then + CRMIntegrationManagement.UpdateOneNow(ServiceHeader.RecordId) + else begin + ServiceHeaderRecordRef.GetTable(ServiceHeader); + CRMIntegrationManagement.UpdateMultipleNow(ServiceHeaderRecordRef); + end + end; + } + group(CouplingFS) + { + Caption = 'Coupling', Comment = 'Coupling is a noun'; + Image = LinkAccount; + ToolTip = 'Create, change, or delete a coupling between the Business Central record and a Dynamics 365 Field Service record.'; + action(ManageCouplingFS) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Set Up Coupling'; + Image = LinkAccount; + ToolTip = 'Create or modify the coupling to a Dynamics 365 Field Service record.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.DefineCoupling(Rec.RecordId); + end; + } + action(FSMatchBasedCoupling) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Match-Based Coupling'; + Image = CoupledItem; + ToolTip = 'Couple service orders to work orders in Dynamics 365 Field Service record based on matching criteria.'; + + trigger OnAction() + var + ServiceHeader: Record "Service Header"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceHeader); + RecRef.GetTable(ServiceHeader); + CRMIntegrationManagement.MatchBasedCoupling(RecRef); + end; + } + action(DeleteCouplingFS) + { + AccessByPermission = TableData "CRM Integration Record" = D; + ApplicationArea = Suite; + Caption = 'Delete Coupling'; + Enabled = CRMIsCoupledToRecord; + Image = UnLinkAccount; + ToolTip = 'Delete the coupling to a Dynamics 365 Field Service record.'; + + trigger OnAction() + var + ServiceHeader: Record "Service Header"; + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + ServiceHeaderRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceHeader); + ServiceHeaderRecordRef.GetTable(ServiceHeader); + CRMCouplingManagement.RemoveCoupling(ServiceHeaderRecordRef); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the resource table.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.ShowLog(Rec.RecordId); + end; + } + } + } + addlast(Promoted) + { + group(Category_FS_Synchronize) + { + Caption = 'Synchronize'; + Visible = FSIntegrationEnabled; + + group(Category_FS_Coupling) + { + Caption = 'Coupling'; + ShowAs = SplitButton; + + actionref(ManageCouplingFS_Promoted; ManageCouplingFS) + { + } + actionref(DeleteCouplingFS_Promoted; DeleteCouplingFS) + { + } + actionref(FSMatchBasedCoupling_Promoted; FSMatchBasedCoupling) + { + } + } + actionref(SynchronizeNowFS_Promoted; SynchronizeNowFS) + { + } + actionref(GoToProductFS_Promoted; GoToProductFS) + { + } + actionref(FSShowLog_Promoted; FSShowLog) + { + } + } + } + } + + var + WorkDescription: Text; + FSIntegrationEnabled: Boolean; + CRMIsCoupledToRecord: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnAfterGetRecord() + begin + WorkDescription := Rec.GetWorkDescription(); + end; + + trigger OnAfterGetCurrRecord() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + if FSIntegrationEnabled then + CRMIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId); + end; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + end; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al new file mode 100644 index 0000000000..1bfc1729ef --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al @@ -0,0 +1,194 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using Microsoft.Service.Setup; +using Microsoft.Integration.Dataverse; + +pageextension 6625 "FS Service Order Types" extends "Service Order Types" +{ + actions + { + addlast(navigation) + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Enabled = FSIntegrationEnabled; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Work Order Type'; + Image = ViewServiceOrder; + ToolTip = 'Open the coupled Dynamics 365 Field Service record.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.ShowCRMEntityFromRecordID(Rec.RecordId); + end; + } + action(SynchronizeNowFS) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Synchronize'; + Image = Refresh; + ToolTip = 'Send updated data to Dynamics 365 Field Service.'; + + trigger OnAction() + var + ServiceOrderType: Record "Service Order Type"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + ServiceHeaderRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceOrderType); + ServiceOrderType.Next(); + + if ServiceOrderType.Count = 1 then + CRMIntegrationManagement.UpdateOneNow(ServiceOrderType.RecordId) + else begin + ServiceHeaderRecordRef.GetTable(ServiceOrderType); + CRMIntegrationManagement.UpdateMultipleNow(ServiceHeaderRecordRef); + end + end; + } + group(CouplingFS) + { + Caption = 'Coupling', Comment = 'Coupling is a noun'; + Image = LinkAccount; + ToolTip = 'Create, change, or delete a coupling between the Business Central record and a Dynamics 365 Field Service record.'; + action(ManageCouplingFS) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Set Up Coupling'; + Image = LinkAccount; + ToolTip = 'Create or modify the coupling to a Dynamics 365 Field Service record.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.DefineCoupling(Rec.RecordId); + end; + } + action(FSMatchBasedCoupling) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Match-Based Coupling'; + Image = CoupledItem; + ToolTip = 'Couple service order types to work order types in Dynamics 365 Field Service record based on matching criteria.'; + + trigger OnAction() + var + ServiceOrderType: Record "Service Order Type"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceOrderType); + RecRef.GetTable(ServiceOrderType); + CRMIntegrationManagement.MatchBasedCoupling(RecRef); + end; + } + action(DeleteCouplingFS) + { + AccessByPermission = TableData "CRM Integration Record" = D; + ApplicationArea = Suite; + Caption = 'Delete Coupling'; + Enabled = CRMIsCoupledToRecord; + Image = UnLinkAccount; + ToolTip = 'Delete the coupling to a Dynamics 365 Field Service record.'; + + trigger OnAction() + var + ServiceOrderType: Record "Service Order Type"; + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + ServiceHeaderRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceOrderType); + ServiceHeaderRecordRef.GetTable(ServiceOrderType); + CRMCouplingManagement.RemoveCoupling(ServiceHeaderRecordRef); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the resource table.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.ShowLog(Rec.RecordId); + end; + } + } + } + addlast(Promoted) + { + group(Category_FS_Synchronize) + { + Caption = 'Synchronize'; + Visible = FSIntegrationEnabled; + + group(Category_FS_Coupling) + { + Caption = 'Coupling'; + ShowAs = SplitButton; + + actionref(ManageCouplingFS_Promoted; ManageCouplingFS) + { + } + actionref(DeleteCouplingFS_Promoted; DeleteCouplingFS) + { + } + actionref(FSMatchBasedCoupling_Promoted; FSMatchBasedCoupling) + { + } + } + actionref(SynchronizeNowFS_Promoted; SynchronizeNowFS) + { + } + actionref(GoToProductFS_Promoted; GoToProductFS) + { + } + actionref(FSShowLog_Promoted; FSShowLog) + { + } + } + } + } + + var + FSIntegrationEnabled: Boolean; + CRMIsCoupledToRecord: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnAfterGetCurrRecord() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + if FSIntegrationEnabled then + CRMIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId); + end; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + end; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al new file mode 100644 index 0000000000..01b85053c3 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al @@ -0,0 +1,206 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using Microsoft.Integration.Dataverse; + +pageextension 6624 "FS Service Orders" extends "Service Orders" +{ + layout + { + addlast(Control1) + { + field("Coupled to Dataverse"; Rec."Coupled to Dataverse") + { + ApplicationArea = All; + ToolTip = 'Specifies that the sales order is coupled to an order in Dynamics 365 Sales.'; + Visible = FSIntegrationEnabled; + } + } + } + + actions + { + addlast(navigation) + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Enabled = FSIntegrationEnabled; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Work Order'; + Image = ViewServiceOrder; + ToolTip = 'Open the coupled Dynamics 365 Field Service record.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.ShowCRMEntityFromRecordID(Rec.RecordId); + end; + } + action(SynchronizeNowFS) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Synchronize'; + Image = Refresh; + ToolTip = 'Send updated data to Dynamics 365 Field Service.'; + + trigger OnAction() + var + ServiceHeader: Record "Service Header"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + ServiceHeaderRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceHeader); + ServiceHeader.Next(); + + if ServiceHeader.Count = 1 then + CRMIntegrationManagement.UpdateOneNow(ServiceHeader.RecordId) + else begin + ServiceHeaderRecordRef.GetTable(ServiceHeader); + CRMIntegrationManagement.UpdateMultipleNow(ServiceHeaderRecordRef); + end + end; + } + group(CouplingFS) + { + Caption = 'Coupling', Comment = 'Coupling is a noun'; + Image = LinkAccount; + ToolTip = 'Create, change, or delete a coupling between the Business Central record and a Dynamics 365 Field Service record.'; + action(ManageCouplingFS) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Set Up Coupling'; + Image = LinkAccount; + ToolTip = 'Create or modify the coupling to a Dynamics 365 Field Service record.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.DefineCoupling(Rec.RecordId); + end; + } + action(FSMatchBasedCoupling) + { + AccessByPermission = TableData "CRM Integration Record" = IM; + ApplicationArea = Suite; + Caption = 'Match-Based Coupling'; + Image = CoupledItem; + ToolTip = 'Couple service orders to work orders in Dynamics 365 Field Service record based on matching criteria.'; + + trigger OnAction() + var + ServiceHeader: Record "Service Header"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceHeader); + RecRef.GetTable(ServiceHeader); + CRMIntegrationManagement.MatchBasedCoupling(RecRef); + end; + } + action(DeleteCouplingFS) + { + AccessByPermission = TableData "CRM Integration Record" = D; + ApplicationArea = Suite; + Caption = 'Delete Coupling'; + Enabled = CRMIsCoupledToRecord; + Image = UnLinkAccount; + ToolTip = 'Delete the coupling to a Dynamics 365 Field Service record.'; + + trigger OnAction() + var + ServiceHeader: Record "Service Header"; + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + ServiceHeaderRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceHeader); + ServiceHeaderRecordRef.GetTable(ServiceHeader); + CRMCouplingManagement.RemoveCoupling(ServiceHeaderRecordRef); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the resource table.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.ShowLog(Rec.RecordId); + end; + } + } + } + addlast(Promoted) + { + group(Category_FS_Synchronize) + { + Caption = 'Synchronize'; + Visible = FSIntegrationEnabled; + + group(Category_FS_Coupling) + { + Caption = 'Coupling'; + ShowAs = SplitButton; + + actionref(ManageCouplingFS_Promoted; ManageCouplingFS) + { + } + actionref(DeleteCouplingFS_Promoted; DeleteCouplingFS) + { + } + actionref(FSMatchBasedCoupling_Promoted; FSMatchBasedCoupling) + { + } + } + actionref(SynchronizeNowFS_Promoted; SynchronizeNowFS) + { + } + actionref(GoToProductFS_Promoted; GoToProductFS) + { + } + actionref(FSShowLog_Promoted; FSShowLog) + { + } + } + } + } + + var + FSIntegrationEnabled: Boolean; + CRMIsCoupledToRecord: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnAfterGetCurrRecord() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + if FSIntegrationEnabled then + CRMIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId); + end; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + end; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al index ce744c8f99..c9a6a8d12d 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al @@ -127,6 +127,17 @@ page 6612 "FS Connection Setup" ShowMandatory = true; ToolTip = 'Specifies the unit of measure that corresponds to the ''hour'' unit that is used on Dynamics 365 Field Service bookable resources.'; } + group(IntegrationTypeService) + { + ShowCaption = false; + + field("Integration Type"; Rec."Integration Type") + { + ApplicationArea = Service; + Editable = not Rec."Is Enabled"; + ToolTip = 'Specifies the type of integration between Business Central and Dynamics 365 Field Service.'; + } + } } group(SynchSettings) { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al index 1472bb1947..09e1ce8213 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al @@ -156,6 +156,12 @@ page 6613 "FS Connection Setup Wizard" ApplicationArea = Suite; ToolTip = 'Specifies when to post project journal lines that are coupled to work order products and work order services.'; } + field("Integration Type"; Rec."Integration Type") + { + ApplicationArea = Service; + ToolTip = 'Specifies the type of integration between Business Central and Dynamics 365 Field Service.'; + Editable = EnableFSIntegrationType; + } } group("Advanced Settings") { @@ -359,6 +365,7 @@ page 6613 "FS Connection Setup Wizard" CredentialsStepVisible: Boolean; EnableFSConnection: Boolean; ImportSolution: Boolean; + EnableFSIntegrationType: Boolean; EnableFSConnectionEnabled: Boolean; ImportFSSolutionEnabled: Boolean; ShowAdvancedSettings: Boolean; @@ -454,12 +461,18 @@ page 6613 "FS Connection Setup Wizard" EnableFSConnectionEnabled := Rec."Server Address" <> ''; Rec."Authentication Type" := Rec."Authentication Type"::Office365; + EnableFSIntegrationType := true; + if FSConnectionSetup.Get() then begin EnableFSConnection := true; EnableFSConnectionEnabled := not FSConnectionSetup."Is Enabled"; ImportSolution := true; if FSConnectionSetup."Is FS Solution Installed" then ImportFSSolutionEnabled := false; + if FSConnectionSetup."Is Enabled" then begin + Rec."Integration Type" := FSConnectionSetup."Integration Type"; + EnableFSIntegrationType := false; + end; end else begin if ImportFSSolutionEnabled then ImportSolution := true; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrderTypes.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrderTypes.Page.al new file mode 100644 index 0000000000..f2f732cf40 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrderTypes.Page.al @@ -0,0 +1,156 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Integration.Dataverse; +using Microsoft.Service.Setup; +using System.Environment.Configuration; + +page 6615 "FS Work Order Types" +{ + ApplicationArea = Service; + Caption = 'Work Order Types - Dynamics 365 Field Service'; + Editable = false; + PageType = List; + SourceTable = "FS Work Order Type"; + UsageCategory = Lists; + + layout + { + area(content) + { + repeater(General) + { + field(Code; Rec.Code) + { + ApplicationArea = Service; + StyleExpr = FirstColumnStyle; + ToolTip = 'Specifies the work order type code.'; + } + field(Name; Rec.Name) + { + ApplicationArea = Service; + ToolTip = 'Specifies the work order type name.'; + } + } + } + } + + actions + { + area(processing) + { + action(CreateFromFS) + { + ApplicationArea = Suite; + Caption = 'Create in Business Central'; + Image = NewItemNonStock; + ToolTip = 'Generate the entity from the Field Service work order.'; + Visible = ShowCreateInBC; + + trigger OnAction() + var + FSWorkOrderType: Record "FS Work Order Type"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CurrPage.SetSelectionFilter(FSWorkOrderType); + CRMIntegrationManagement.CreateNewRecordsFromSelectedCRMRecords(FSWorkOrderType); + end; + } + action(ShowOnlyUncoupled) + { + ApplicationArea = Suite; + Caption = 'Hide Coupled Records'; + Image = FilterLines; + ToolTip = 'Do not show coupled records.'; + + trigger OnAction() + begin + Rec.MarkedOnly(true); + end; + } + action(ShowAll) + { + ApplicationArea = Suite; + Caption = 'Show Coupled Records'; + Image = ClearFilter; + ToolTip = 'Show coupled records.'; + + trigger OnAction() + begin + Rec.MarkedOnly(false); + end; + } + } + area(Promoted) + { + group(Category_Process) + { + Caption = 'Process'; + + actionref(CreateFromFS_Promoted; CreateFromFS) + { + } + actionref(ShowOnlyUncoupled_Promoted; ShowOnlyUncoupled) + { + } + actionref(ShowAll_Promoted; ShowAll) + { + } + } + } + } + + trigger OnInit() + begin + Codeunit.Run(Codeunit::"CRM Integration Management"); + end; + + trigger OnOpenPage() + var + ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade"; + LookupCRMTables: Codeunit "Lookup CRM Tables"; + begin + Rec.FilterGroup(4); + Rec.SetView(LookupCRMTables.GetIntegrationTableMappingView(Database::"FS Work Order Type")); + Rec.FilterGroup(0); + ShowCreateInBC := ApplicationAreaMgmtFacade.IsPremiumExperienceEnabled(); + end; + + trigger OnAfterGetRecord() + var + CRMIntegrationRecord: Record "CRM Integration Record"; + RecordID: RecordID; + begin + if CRMIntegrationRecord.FindRecordIDFromID(Rec.WorkOrderTypeId, Database::"Service Order Type", RecordID) then + if CurrentlyCoupledFSWorkOrderType.WorkOrderTypeId = Rec.WorkOrderTypeId then begin + Coupled := 'Current'; + FirstColumnStyle := 'Strong'; + Rec.Mark(true); + end else begin + Coupled := 'Yes'; + FirstColumnStyle := 'Subordinate'; + Rec.Mark(false); + end + else begin + Coupled := 'No'; + FirstColumnStyle := 'None'; + Rec.Mark(true); + end; + end; + + + var + CurrentlyCoupledFSWorkOrderType: Record "FS Work Order Type"; + Coupled: Text; + FirstColumnStyle: Text; + ShowCreateInBC: Boolean; + + procedure SetCurrentlyCoupledFSWorkOrderType(FSWorkOrderType: Record "FS Work Order Type") + begin + CurrentlyCoupledFSWorkOrderType := FSWorkOrderType; + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al new file mode 100644 index 0000000000..fc4d1c12fc --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al @@ -0,0 +1,261 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Integration.Dataverse; +using Microsoft.Service.Setup; +using System.Environment.Configuration; +using Microsoft.Integration.D365Sales; +using Microsoft.Service.Document; + +page 6616 "FS Work Orders" +{ + ApplicationArea = Service; + Caption = 'Work Orders - Dynamics 365 Field Service'; + Editable = false; + PageType = List; + SourceTable = "FS Work Order"; + UsageCategory = Lists; + + layout + { + area(content) + { + repeater(General) + { + field(Name; Rec.Name) + { + ApplicationArea = Service; + StyleExpr = FirstColumnStyle; + ToolTip = 'Specifies the work order type code.'; + } + field(WorkOrderType; Rec.WorkOrderType) + { + ApplicationArea = Service; + Visible = false; + ToolTip = 'Specifies the work order type.'; + } + field(WorkOrderTypeName; FSWorkOrderType.Name) + { + Caption = 'Work Order Type'; + ApplicationArea = Service; + ToolTip = 'Specifies the work order type.'; + } + field(StatusCode; Rec.StatusCode) + { + ApplicationArea = Service; + ToolTip = 'Specifies the status of the work order.'; + } + field(CreatedOn; Rec.CreatedOn) + { + ApplicationArea = Service; + ToolTip = 'Specifies the date and time when the work order was created.'; + } + field(ServiceAccount; Rec.ServiceAccount) + { + ApplicationArea = Service; + Visible = false; + ToolTip = 'Specifies the service account for the work order.'; + } + field(ServiceAccountName; CRMAccountService.Name) + { + Caption = 'Service Account'; + ApplicationArea = Service; + ToolTip = 'Specifies the service account for the work order.'; + } + field(Address1; Rec.Address1) + { + ApplicationArea = Service; + Visible = false; + ToolTip = 'Specifies the address for the work order.'; + } + field(Address2; Rec.Address2) + { + ApplicationArea = Service; + Visible = false; + ToolTip = 'Specifies the address for the work order.'; + } + field(Address3; Rec.Address3) + { + ApplicationArea = Service; + Visible = false; + ToolTip = 'Specifies the address for the work order.'; + } + field(PostalCode; Rec.PostalCode) + { + ApplicationArea = Service; + ToolTip = 'Specifies the address for the work order.'; + Visible = false; + } + field(City; Rec.City) + { + ApplicationArea = Service; + ToolTip = 'Specifies the address for the work order.'; + Visible = false; + } + field(BillingAccount; Rec.BillingAccount) + { + ApplicationArea = Service; + Visible = false; + ToolTip = 'Specifies the billing account for the work order.'; + } + field(BillingAccountName; CRMAccountBilling.Name) + { + Caption = 'Billing Account'; + ApplicationArea = Service; + ToolTip = 'Specifies the billing account for the work order.'; + } + } + } + } + + actions + { + area(processing) + { + action(CreateFromFS) + { + ApplicationArea = Suite; + Caption = 'Create in Business Central'; + Image = NewItemNonStock; + ToolTip = 'Generate the entity from the Field Service work order.'; + Visible = ShowCreateInBC; + + trigger OnAction() + var + FSWorkOrder: Record "FS Work Order"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CurrPage.SetSelectionFilter(FSWorkOrder); + CRMIntegrationManagement.CreateNewRecordsFromSelectedCRMRecords(FSWorkOrder); + end; + } + action(OpenInBC) + { + ApplicationArea = Suite; + Caption = 'Open in Business Central'; + Image = Document; + ToolTip = 'Opens the entity in Business Central.'; + Visible = ShowOpenInBC; + + trigger OnAction() + var + ServiceHeader: Record "Service Header"; + begin + RecordID.GetRecord().SetTable(ServiceHeader); + Page.Run(Page::"Service Order", ServiceHeader); + end; + } + action(ShowOnlyUncoupled) + { + ApplicationArea = Suite; + Caption = 'Hide Coupled Records'; + Image = FilterLines; + ToolTip = 'Do not show coupled records.'; + + trigger OnAction() + begin + Rec.MarkedOnly(true); + end; + } + action(ShowAll) + { + ApplicationArea = Suite; + Caption = 'Show Coupled Records'; + Image = ClearFilter; + ToolTip = 'Show coupled records.'; + + trigger OnAction() + begin + Rec.MarkedOnly(false); + end; + } + } + area(Promoted) + { + group(Category_Process) + { + Caption = 'Process'; + + actionref(CreateFromFS_Promoted; CreateFromFS) + { + } + actionref(OpenInBC_Promoted; OpenInBC) + { + } + actionref(ShowOnlyUncoupled_Promoted; ShowOnlyUncoupled) + { + } + actionref(ShowAll_Promoted; ShowAll) + { + } + } + } + } + + trigger OnInit() + begin + Codeunit.Run(Codeunit::"CRM Integration Management"); + end; + + trigger OnOpenPage() + var + ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade"; + LookupCRMTables: Codeunit "Lookup CRM Tables"; + begin + Rec.FilterGroup(4); + Rec.SetView(LookupCRMTables.GetIntegrationTableMappingView(Database::"FS Work Order")); + Rec.FilterGroup(0); + ShowCreateInBC := ApplicationAreaMgmtFacade.IsPremiumExperienceEnabled(); + end; + + trigger OnAfterGetRecord() + var + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + if CRMIntegrationRecord.FindRecordIDFromID(Rec.WorkOrderId, Database::"Service Header", RecordID) then begin + if CurrentlyCoupledFSWorkOrder.WorkOrderId = Rec.WorkOrderId then begin + Coupled := 'Current'; + FirstColumnStyle := 'Strong'; + Rec.Mark(true); + end else begin + Coupled := 'Yes'; + FirstColumnStyle := 'Subordinate'; + Rec.Mark(false); + end; + ShowOpenInBC := true; + end else begin + Coupled := 'No'; + FirstColumnStyle := 'None'; + ShowOpenInBC := false; + Rec.Mark(true); + end; + + if not CRMAccountService.Get(Rec.ServiceAccount) then + Clear(CRMAccountService); + if not CRMAccountBilling.Get(Rec.ServiceAccount) then + Clear(CRMAccountBilling); + if not FSWorkOrderType.Get(Rec.WorkOrderType) then + Clear(FSWorkOrderType); + end; + + + var + CurrentlyCoupledFSWorkOrder: Record "FS Work Order"; + FSWorkOrderType: Record "FS Work Order Type"; + CRMAccountService: Record "CRM Account"; + CRMAccountBilling: Record "CRM Account"; + RecordID: RecordID; + Coupled: Text; + FirstColumnStyle: Text; + ShowCreateInBC: Boolean; + ShowOpenInBC: Boolean; + + procedure SetCurrentlyCoupledFSWorkOrder(FSWorkOrder: Record "FS Work Order") + begin + CurrentlyCoupledFSWorkOrder := FSWorkOrder; + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al new file mode 100644 index 0000000000..cfa25501b6 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al @@ -0,0 +1,49 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using System.Reflection; +using Microsoft.Integration.Dataverse; + +tableextension 6615 "FS Service Header" extends "Service Header" +{ + fields + { + field(12000; "Work Description"; BLOB) + { + Caption = 'Work Description'; + } + + field(12001; "Coupled to Dataverse"; Boolean) + { + FieldClass = FlowField; + Caption = 'Coupled to Dynamics 365 Sales'; + Editable = false; + CalcFormula = exist("CRM Integration Record" where("Integration ID" = field(SystemId), "Table ID" = const(Database::"Service Header"))); + } + } + + procedure SetWorkDescription(NewWorkDescription: Text) + var + OutStream: OutStream; + begin + Clear("Work Description"); + Rec."Work Description".CreateOutStream(OutStream, TextEncoding::UTF8); + OutStream.WriteText(NewWorkDescription); + Modify(); + end; + + procedure GetWorkDescription() WorkDescription: Text + var + TypeHelper: Codeunit "Type Helper"; + InStream: InStream; + begin + Rec.CalcFields("Work Description"); + Rec."Work Description".CreateInStream(InStream, TextEncoding::UTF8); + exit(TypeHelper.TryReadAsTextWithSepAndFieldErrMsg(InStream, TypeHelper.LFSeparator(), Rec.FieldName("Work Description"))); + end; + +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al new file mode 100644 index 0000000000..af2716dcc9 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using Microsoft.Inventory.Item; + +tableextension 6616 "FS Service Line" extends "Service Line" +{ + fields + { + field(12000; "Item Type"; Enum "Item Type") + { + Caption = 'Item Type'; + FieldClass = FlowField; + CalcFormula = lookup(Item.Type where("No." = field("No."))); + } + } +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al index e0c875f5a2..8e465cd797 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al @@ -228,6 +228,24 @@ table 6623 "FS Connection Setup" DataClassification = SystemMetadata; Caption = 'Automatically post project journal lines'; } + field(300; "Integration Type"; Enum "FS Integration Type") + { + DataClassification = SystemMetadata; + Caption = 'Integration Type'; + + trigger OnValidate() + var + IntegrationMgt: Codeunit "FS Integration Mgt."; + begin + IntegrationMgt.TestManualNoSeriesFlag(Rec."Integration Type"); + IntegrationMgt.TestOneServiceItemLinePerOrder(Rec."Integration Type"); + end; + } + field(301; "Default Work Order Incident ID"; Guid) + { + DataClassification = SystemMetadata; + Caption = 'Default Work Order Incident ID'; + } } keys diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSIncidentType.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSIncidentType.Table.al new file mode 100644 index 0000000000..8379e4b673 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSIncidentType.Table.al @@ -0,0 +1,90 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Integration.D365Sales; + +table 6625 "FS Incident Type" +{ + ExternalName = 'msdyn_incidenttype'; + TableType = CRM; + Description = 'Incident Types in CRM.'; + DataClassification = SystemMetadata; + + fields + { + field(1; IncidentTypeId; GUID) + { + ExternalName = 'msdyn_incidenttypeid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Unique identifier of the resource.'; + Caption = 'Incident Type Id'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'CreatedOn'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Date and time when the record was created.'; + Caption = 'Created On'; + DataClassification = SystemMetadata; + } + field(3; CreatedBy; GUID) + { + ExternalName = 'CreatedBy'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the user who created the record.'; + Caption = 'Created By'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(4; ModifiedOn; Datetime) + { + ExternalName = 'ModifiedOn'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Date and time when the record was modified.'; + Caption = 'Modified On'; + DataClassification = SystemMetadata; + } + field(5; ModifiedBy; GUID) + { + ExternalName = 'ModifiedBy'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the user who modified the record.'; + Caption = 'Modified By'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(10; Name; Text[100]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Type the name of the incident.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; IncidentTypeId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al index fa94ea702b..f6d7192bb2 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al @@ -17,7 +17,7 @@ table 6617 "FS Work Order" { field(1; WorkOrderId; GUID) { - ExternalName = 'msdyn_workorderId'; + ExternalName = 'msdyn_workorderid'; ExternalType = 'Uniqueidentifier'; ExternalAccess = Insert; Description = 'Shows the entity instances.'; @@ -886,6 +886,13 @@ table 6617 "FS Work Order" TableRelation = "CDS Company".CompanyId; DataClassification = SystemMetadata; } + field(165; "Integrate to Service"; Boolean) + { + ExternalName = 'bcbi_integratetoervice'; + ExternalType = 'Boolean'; + Caption = 'Integrate to Service'; + DataClassification = SystemMetadata; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al index 6837e3621c..bc3b41e851 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al @@ -292,6 +292,14 @@ table 6618 "FS Work Order Incident" ExternalType = 'String'; ExternalAccess = Read; } + field(60; IncidentType; Guid) + { + ExternalName = 'msdyn_incidenttype'; + ExternalType = 'Lookup'; + Caption = 'Incident Type'; + TableRelation = "FS Incident Type".IncidentTypeId; + DataClassification = SystemMetadata; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al index 5929311a8b..2c1c82cacd 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al @@ -219,6 +219,14 @@ table 6622 "FS Work Order Type" Caption = 'Taxable'; DataClassification = SystemMetadata; } + field(40; Code; Text[10]) + { + ExternalName = 'bcbi_code'; + ExternalType = 'String'; + Description = 'Type the code of the work order type.'; + Caption = 'Code'; + DataClassification = SystemMetadata; + } } keys { From 66d9f9c6323863bef8b5f26e851be61dad9bccbf Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 28 Aug 2024 12:49:22 +0200 Subject: [PATCH 02/49] added: navigation actions for invalid no series message --- .../Codeunits/FSIntegrationMgt.Codeunit.al | 36 ++++++++++++++++--- .../app/src/Tables/FSConnectionSetup.Table.al | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al index a074a36db5..1109a0668b 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al @@ -189,18 +189,46 @@ codeunit 6615 "FS Integration Mgt." exit(GuidVar); end; - internal procedure TestManualNoSeriesFlag(IntegrationType: Enum "FS Integration Type") + internal procedure TestManualServiceOrderNoSeriesFlag(IntegrationType: Enum "FS Integration Type") var ServiceMgtSetup: Record "Service Mgt. Setup"; NoSeries: Codeunit "No. Series"; - NoManualNoSeriesErr: Label 'Manual No. Series is not supported for Service Order Nos. Please make sure that the No. Series setup is correct.'; + CannotInvoiceErrorInfo: ErrorInfo; + NoManualNoSeriesTitleErr: Label 'Service Order No. Series must be set to manual.'; + NoManualNoSeriesErr: Label 'Please make sure that the No. Series setup is correct.'; + SetToManualLbl: Label 'Set to Manual'; + OpenNoSeriesListLbl: Label 'Open No. Series. list'; begin if not (IntegrationType in [IntegrationType::Service, IntegrationType::Both]) then exit; ServiceMgtSetup.Get(); - if not NoSeries.IsManual(ServiceMgtSetup."Service Order Nos.") then - Error(NoManualNoSeriesErr); + if not NoSeries.IsManual(ServiceMgtSetup."Service Order Nos.") then begin + CannotInvoiceErrorInfo.Title := NoManualNoSeriesTitleErr; + CannotInvoiceErrorInfo.Message := NoManualNoSeriesErr; + + if ServiceMgtSetup."Service Order Nos." <> '' then + CannotInvoiceErrorInfo.AddAction( + SetToManualLbl, + Codeunit::"FS Integration Mgt.", + 'SetServiceOrderNoSeriesToManual' + ); + + CannotInvoiceErrorInfo.PageNo := Page::"No. Series"; + CannotInvoiceErrorInfo.AddNavigationAction(OpenNoSeriesListLbl); + Error(CannotInvoiceErrorInfo); + end; + end; + + procedure SetServiceOrderNoSeriesToManual(ErrorInfo: ErrorInfo) + var + ServiceMgtSetup: Record "Service Mgt. Setup"; + NoSeries: Record "No. Series"; + begin + ServiceMgtSetup.Get(); + NoSeries.Get(ServiceMgtSetup."Service Order Nos."); + NoSeries.Validate("Manual Nos.", true); + NoSeries.Modify(true); end; internal procedure TestOneServiceItemLinePerOrder(IntegrationType: Enum "FS Integration Type") diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al index 8e465cd797..8e1cd055f4 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al @@ -237,7 +237,7 @@ table 6623 "FS Connection Setup" var IntegrationMgt: Codeunit "FS Integration Mgt."; begin - IntegrationMgt.TestManualNoSeriesFlag(Rec."Integration Type"); + IntegrationMgt.TestManualServiceOrderNoSeriesFlag(Rec."Integration Type"); IntegrationMgt.TestOneServiceItemLinePerOrder(Rec."Integration Type"); end; } From 0f64dc7893ab40dff6bb58bafc05650c93c1845a Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 28 Aug 2024 12:56:23 +0200 Subject: [PATCH 03/49] added: missing setup checks --- .../app/src/Codeunits/FSIntTableSubscriber.Codeunit.al | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 134e4404c4..444539fc80 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -124,9 +124,12 @@ codeunit 6610 "FS Int. Table Subscriber" [EventSubscriber(ObjectType::Table, Database::"Integration Table Mapping", 'OnBeforeModifyEvent', '', false, false)] local procedure IntegrationTableMappingOnBeforeModifyEvent(var Rec: Record "Integration Table Mapping"; RunTrigger: Boolean) var + FSConnectionSetup: Record "FS Connection Setup"; ServiceItem: Record "Service Item"; MandatoryFilterErr: Label '"%1" must be included in the filter. If you need this behavior, please contact your partner for assistance.', Comment = '%1 = a field caption'; begin + if not FSConnectionSetup.IsEnabled() then + exit; if not RunTrigger then exit; if Rec.IsTemporary() then @@ -416,6 +419,7 @@ codeunit 6610 "FS Int. Table Subscriber" [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Int. Table. Subscriber", 'OnFindNewValueForCoupledRecordPK', '', false, false)] local procedure OnFindNewValueForCoupledRecordPK(IntegrationTableMapping: Record "Integration Table Mapping"; SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean) var + FSConnectionSetup: Record "FS Connection Setup"; CRMIntegrationRecord: Record "CRM Integration Record"; FSWorkOrderService: Record "FS Work Order Service"; FSWorkOrderProduct: Record "FS Work Order Product"; @@ -427,6 +431,9 @@ codeunit 6610 "FS Int. Table Subscriber" NAVItemUomId: Guid; NotCoupledCRMUomErr: Label 'The unit is not coupled to a unit of measure.'; begin + if not FSConnectionSetup.IsEnabled() then + exit; + if (SourceFieldRef.Record().Number = Database::"FS Work Order Product") then case SourceFieldRef.Name() of FSWorkOrderProduct.FieldName(Unit): From 395245f7efe9bb812b5638fab6dc2959cffdf9b3 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 28 Aug 2024 13:18:57 +0200 Subject: [PATCH 04/49] added: mandatory flag (depends on setup) --- .../app/src/Page Extensions/FSServiceOrder.PageExt.al | 9 ++++++++- .../app/src/Tables/FSConnectionSetup.Table.al | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al index 85cb151538..eb701cec29 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrder.PageExt.al @@ -11,6 +11,10 @@ pageextension 6614 "FS Service Order" extends "Service Order" { layout { + modify("Service Order Type") + { + ShowMandatory = FSIntegrationTypeServiceEnabled; + } addafter(Status) { group("Work Description") @@ -196,6 +200,7 @@ pageextension 6614 "FS Service Order" extends "Service Order" var WorkDescription: Text; FSIntegrationEnabled: Boolean; + FSIntegrationTypeServiceEnabled: Boolean; CRMIsCoupledToRecord: Boolean; CRMIntegrationEnabled: Boolean; @@ -218,7 +223,9 @@ pageextension 6614 "FS Service Order" extends "Service Order" CRMIntegrationManagement: Codeunit "CRM Integration Management"; begin CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); - if CRMIntegrationEnabled then + if CRMIntegrationEnabled then begin FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + FSIntegrationTypeServiceEnabled := FSConnectionSetup.IsIntegrationTypeServiceEnabled(); + end; end; } diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al index 8e1cd055f4..8c9866d10c 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al @@ -1027,6 +1027,11 @@ table 6623 "FS Connection Setup" exit("Is Enabled"); end; + internal procedure IsIntegrationTypeServiceEnabled(): Boolean + begin + exit(IsEnabled() and ("Integration Type" in ["Integration Type"::Service, "Integration Type"::Both])); + end; + internal procedure GetProxyVersion(): Integer var EnvironmentInformation: Codeunit "Environment Information"; From 83eacc49794c5b921fe4950bc1882a5757dc93f9 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 29 Aug 2024 07:49:58 +0200 Subject: [PATCH 05/49] added: service order sync trigger - via service (item) line --- .../FSIntTableSubscriber.Codeunit.al | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 444539fc80..f3b3c71266 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -1309,6 +1309,61 @@ codeunit 6610 "FS Int. Table Subscriber" exit(ServiceItemLine.RecordId); end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Table Synch.", 'OnSynchNAVTableToCRMOnBeforeCheckLatestModifiedOn', '', true, false)] + local procedure OnSynchNAVTableToCRMOnBeforeCheckLatestModifiedOn(var SourceRecordRef: RecordRef; IntegrationTableMapping: Record "Integration Table Mapping") + var + ServiceHeader: Record "Service Header"; + ServiceHeaderToSync: Record "Service Header"; + ServiceItemLine: Record "Service Item Line"; + ServiceLine: Record "Service Line"; + CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; + ServiceOrderRecordRef: RecordRef; + ServiceOrdersToIgnore: List of [Code[20]]; + ServiceOrdersToSync: List of [Code[20]]; + ServiceOrderNo: Code[20]; + begin + if SourceRecordRef.Number() <> Database::"Service Header" then + exit; + + // sync of service header should triggered by changes in service item lines and service lines: + // search for modified service orders and start sync -> only if not already synced (via service header). + + // already synced service orders should not be triggered again + SourceRecordRef.SetTable(ServiceHeader); + if ServiceHeader.FindSet() then + repeat + ServiceOrdersToIgnore.Add(ServiceHeader."No."); + until ServiceHeader.Next() = 0; + + // search for modified service (item) lines and start sync + ServiceItemLine.SetRange("Document Type", ServiceItemLine."Document Type"::Order); + ServiceItemLine.SetFilter(SystemModifiedAt, ServiceHeader.GetFilter(SystemModifiedAt)); + if ServiceItemLine.FindSet() then + repeat + if not ServiceOrdersToIgnore.Contains(ServiceItemLine."Document No.") then + if not ServiceOrdersToSync.Contains(ServiceItemLine."Document No.") then + ServiceOrdersToSync.Add(ServiceItemLine."Document No."); + until ServiceItemLine.Next() = 0; + + ServiceLine.SetRange("Document Type", ServiceItemLine."Document Type"::Order); + ServiceLine.SetFilter(SystemModifiedAt, ServiceHeader.GetFilter(SystemModifiedAt)); + if ServiceLine.FindSet() then + repeat + if not ServiceOrdersToIgnore.Contains(ServiceLine."Document No.") then + if not ServiceOrdersToSync.Contains(ServiceLine."Document No.") then + ServiceOrdersToSync.Add(ServiceLine."Document No."); + until ServiceLine.Next() = 0; + + // start sync for found service orders + foreach ServiceOrderNo in ServiceOrdersToSync do begin + ServiceHeaderToSync.Reset(); + ServiceHeaderToSync.SetRange("Document Type", ServiceHeaderToSync."Document Type"::Order); + ServiceHeaderToSync.SetRange("No.", ServiceOrderNo); + ServiceOrderRecordRef.GetTable(ServiceHeaderToSync); + CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(ServiceOrderRecordRef, false, false); + end; + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnDeletionConflictDetectedSetRecordStateAndSynchAction', '', false, false)] local procedure HandleOnDeletionConflictDetectedSetRecordStateAndSynchAction(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var CoupledRecordRef: RecordRef; var RecordState: Option NotFound,Coupled,Decoupled; var SynchAction: Option "None",Insert,Modify,ForceModify,IgnoreUnchanged,Fail,Skip,Delete,Uncouple,Couple; var DeletionConflictHandled: Boolean) var From 865ee28efa8c9b39adc8949475264886b71de92e Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 29 Aug 2024 11:03:28 +0200 Subject: [PATCH 06/49] added: auto archive on delete service (item) line --- .../FSIntTableSubscriber.Codeunit.al | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index f3b3c71266..abd8eef4df 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -12,17 +12,16 @@ using Microsoft.Service.Item; using Microsoft.Integration.SyncEngine; using Microsoft.Sales.Customer; using System.Telemetry; -using Microsoft.Integration.FieldService; using Microsoft.Projects.Project.Posting; using Microsoft.Projects.Project.Journal; using Microsoft.Projects.Resources.Resource; using Microsoft.Integration.D365Sales; using Microsoft.Inventory.Item; +using Microsoft.Service.Archive; using Microsoft.Service.Document; using Microsoft.Projects.Project.Planning; using Microsoft.Projects.Project.Ledger; using Microsoft.Sales.History; -using Microsoft.Sales.Document; using Microsoft.Service.Setup; codeunit 6610 "FS Int. Table Subscriber" @@ -505,6 +504,7 @@ codeunit 6610 "FS Int. Table Subscriber" SalesInvoiceHeader: Record "Sales Invoice Header"; JobPlanningLineInvoice: Record "Job Planning Line Invoice"; JobUsageLink: Record "Job Usage Link"; + ArchivedServiceOrders: List of [Code[20]]; SourceDestCode: Text; begin if not FSConnectionSetup.IsEnabled() then @@ -567,9 +567,9 @@ codeunit 6610 "FS Int. Table Subscriber" end; 'FS Work Order-Service Header': begin - ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin @@ -595,6 +595,7 @@ codeunit 6610 "FS Int. Table Subscriber" JobJournalLine: Record "Job Journal Line"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; + ArchivedServiceOrders: List of [Code[20]]; SourceDestCode: Text; begin if not FSConnectionSetup.IsEnabled() then @@ -618,9 +619,9 @@ codeunit 6610 "FS Int. Table Subscriber" end; 'FS Work Order-Service Header': begin - ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin @@ -635,6 +636,7 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure HandleOnAfterUnchangedRecordHandled(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) var FSConnectionSetup: Record "FS Connection Setup"; + ArchivedServiceOrders: List of [Code[20]]; SourceDestCode: Text; begin if not FSConnectionSetup.IsEnabled() then @@ -645,9 +647,9 @@ codeunit 6610 "FS Int. Table Subscriber" case SourceDestCode of 'FS Work Order-Service Header': begin - ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin @@ -658,7 +660,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - local procedure ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + local procedure ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var ArchivedServiceOrders: List of [Code[20]]) var ServiceHeader: Record "Service Header"; ServiceItemLine: Record "Service Item Line"; @@ -686,6 +688,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderIncident.SetRange(WorkOrderIncidentId, CRMIntegrationRecord."CRM ID"); if FSWorkOrderIncident.IsEmpty() then begin CRMIntegrationRecord.Delete(); + ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); if ServiceItemLineToDelete.GetBySystemId(ServiceItemLine.SystemId) then begin DeleteServiceLines(ServiceItemLine); ServiceItemLineToDelete.Delete(true); @@ -722,7 +725,7 @@ codeunit 6610 "FS Int. Table Subscriber" ServiceLine.DeleteAll(true); end; - local procedure ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + local procedure ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var ArchivedServiceOrders: List of [Code[20]]) var ServiceHeader: Record "Service Header"; ServiceLine: Record "Service Line"; @@ -750,6 +753,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderProduct.SetRange(WorkOrderProductId, CRMIntegrationRecord."CRM ID"); if FSWorkOrderProduct.IsEmpty() then begin CRMIntegrationRecord.Delete(); + ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); if ServiceLineToDelete.GetBySystemId(ServiceLine.SystemId) then ServiceLineToDelete.Delete(true); end; @@ -773,7 +777,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - local procedure ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + local procedure ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var ArchivedServiceOrders: List of [Code[20]]) var ServiceHeader: Record "Service Header"; ServiceLine: Record "Service Item Line"; @@ -801,6 +805,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderService.SetRange(WorkOrderServiceId, CRMIntegrationRecord."CRM ID"); if FSWorkOrderService.IsEmpty() then begin CRMIntegrationRecord.Delete(); + ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); if ServiceItemLineToDelete.GetBySystemId(ServiceLine.SystemId) then ServiceItemLineToDelete.Delete(true); end; @@ -913,6 +918,17 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure ArchiveServiceOrder(ServiceHeader: Record "Service Header"; ArchivedServiceOrders: List of [Code[20]]) + var + ServiceDocumentArchiveMgmt: Codeunit "Service Document Archive Mgmt."; + begin + if ArchivedServiceOrders.Contains(ServiceHeader."No.") then + exit; + + ArchivedServiceOrders.Add(ServiceHeader."No."); + ServiceDocumentArchiveMgmt.ArchServiceDocumentNoConfirm(ServiceHeader); + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeModifyRecord', '', false, false)] local procedure HandleOnBeforeModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) var @@ -997,6 +1013,7 @@ codeunit 6610 "FS Int. Table Subscriber" JobJournalLine: Record "Job Journal Line"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; + ArchivedServiceOrders: List of [Code[20]]; SourceDestCode: Text; begin if not FSConnectionSetup.IsEnabled() then @@ -1019,9 +1036,9 @@ codeunit 6610 "FS Int. Table Subscriber" end; 'FS Work Order-Service Header': begin - ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef); - ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef); + ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin From 01b63fa194b0287c8e865edb45016bb0939be9a0 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 29 Aug 2024 13:50:35 +0200 Subject: [PATCH 07/49] added: sync without service item no. --- .../app/src/Codeunits/FSIntTableSubscriber.Codeunit.al | 8 ++++++++ .../app/src/Codeunits/FSSetupDefaults.Codeunit.al | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index abd8eef4df..c814f27e61 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -151,11 +151,13 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderService: Record "FS Work Order Service"; CRMIntegrationRecord: Record "CRM Integration Record"; FSWorkOrderIncident: Record "FS Work Order Incident"; + FSIncident: Record "FS Incident Type"; JobJournalLine: Record "Job Journal Line"; JobTask: Record "Job Task"; ServiceHeader: Record "Service Header"; ServiceItemLine: Record "Service Item Line"; ServiceLine: Record "Service Line"; + DefaultIncidentLbl: Label 'Service Order Incident'; SourceDestCode: Text; begin if not FSConnectionSetup.IsEnabled() then @@ -202,6 +204,12 @@ codeunit 6610 "FS Int. Table Subscriber" ServiceItemLine."Document No." := ServiceHeader."No."; ServiceItemLine."Line No." := GetNextLineNo(ServiceItemLine); + if IsNullGuid(FSWorkOrderIncident.CustomerAsset) then + if FSIncident.Get(FSWorkOrderIncident.IncidentType) then + ServiceItemLine.Description := FSIncident.Name + else + ServiceItemLine.Description := DefaultIncidentLbl; + DestinationRecordRef.GetTable(ServiceItemLine); end; 'FS Work Order Product-Service Line': diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 22c34634a5..454d92672a 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -622,7 +622,6 @@ codeunit 6611 "FS Setup Defaults" ServiceItemLine.Reset(); ServiceItemLine.SetRange("Document Type", ServiceItemLine."Document Type"::Order); FSWorkOrderIncident.Reset(); - FSWorkOrderIncident.SetFilter(CustomerAsset, '<>%1', EmptyGuid); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, From d9e602fab9c81c8eda57e5d0f79507b273dbcb88 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 4 Sep 2024 09:46:45 +0200 Subject: [PATCH 08/49] added: support of archived services orders (update posted quantities) --- .../FSArchivedServiceOrdersJob.Codeunit.al | 142 ++++++++++++++++++ .../FSIntTableSubscriber.Codeunit.al | 126 ++++++++++++++++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 45 +++++- .../FSIntegrationRecord.TableExt.al | 36 +++++ 4 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al new file mode 100644 index 0000000000..5810e2ba51 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al @@ -0,0 +1,142 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Integration.Dataverse; +using Microsoft.Integration.SyncEngine; +using Microsoft.Service.Document; +using System.Threading; +using Microsoft.Service.Archive; + +codeunit 6617 "FS Archived Service Orders Job" +{ + TableNo = "Job Queue Entry"; + + trigger OnRun() + begin + UpdateOrders(Rec.GetLastLogEntryNo()); + end; + + var + ArchivedOrdersUpdatedMsg: Label 'Archived service orders have been synchronized.'; + + local procedure UpdateOrders(JobLogEntryNo: Integer) + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + Codeunit.Run(Codeunit::"CRM Integration Management"); + UpdateArchivedOrders(JobLogEntryNo); + end; + + local procedure UpdateArchivedOrders(JobLogEntryNo: Integer) + var + CRMIntegrationRecord: Record "CRM Integration Record"; + CRMIntegrationRecord2: Record "CRM Integration Record"; + FSWorkOrder: Record "FS Work Order"; + IntegrationTableSynch: Codeunit "Integration Table Synch."; + SynchActionType: Option "None",Insert,Modify,ForceModify,IgnoreUnchanged,Fail,Skip,Delete; + ModifyCounter: Integer; + begin + IntegrationTableSynch.BeginIntegrationSynchJobLoging(TableConnectionType::CRM, Codeunit::"FS Archived Service Orders Job", JobLogEntryNo, Database::"Service Header"); + + CRMIntegrationRecord.SetRange("Archived Service Order", true); + CRMIntegrationRecord.SetRange("Archived Service Order Updated", false); + if CRMIntegrationRecord.FindSet() then + repeat + if FSWorkOrder.Get(CRMIntegrationRecord."CRM ID") then + if UpdateFromSalesHeader(FSWorkOrder) then begin + CRMIntegrationRecord2.GetBySystemId(CRMIntegrationRecord.SystemId); + CRMIntegrationRecord2."Archived Service Order Updated" := true; + CRMIntegrationRecord2.Modify(); + ModifyCounter += 1; + end; + until CRMIntegrationRecord.Next() = 0; + + IntegrationTableSynch.UpdateSynchJobCounters(SynchActionType::Modify, ModifyCounter); + IntegrationTableSynch.EndIntegrationSynchJobWithMsg(ArchivedOrdersUpdatedMsg); + end; + + [TryFunction] + local procedure UpdateFromSalesHeader(var FSWorkOrder: Record "FS Work Order") + begin + ResetFSWorkOrderLineFromServiceOrderLine(FSWorkOrder); + end; + + local procedure ResetFSWorkOrderLineFromServiceOrderLine(var FSWorkOrder: Record "FS Work Order") + var + SalesLineArchive: Record "Service Line Archive"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + FSWorkOrderProduct.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + if FSWorkOrderProduct.FindSet() then + repeat + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrderProductId) then + if SalesLineArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Line Id") then + UpdateWorkOrderProduct(SalesLineArchive, FSWorkOrderProduct); + until FSWorkOrderProduct.Next() = 0; + + FSWorkOrderService.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + if FSWorkOrderService.FindSet() then + repeat + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.WorkOrderServiceId) then + if SalesLineArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Line Id") then + UpdateWorkOrderService(SalesLineArchive, FSWorkOrderService); + until FSWorkOrderService.Next() = 0; + end; + + local procedure UpdateWorkOrderProduct(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderProduct: Record "FS Work Order Product") + var + Modified: Boolean; + begin + // TODO! Add Quantity Shipped + // if FSWorkOrderProduct.QuantityShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin + // FSWorkOrderProduct.QuantityShipped := SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship"; + // Modified := true; + // end; + + if FSWorkOrderProduct.QuantityInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") then begin + FSWorkOrderProduct.QuantityInvoiced := SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship"; + Modified := true; + end; + + if FSWorkOrderProduct.QuantityConsumed <> SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume" then begin + FSWorkOrderProduct.QuantityConsumed := SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume"; + Modified := true; + end; + + if Modified then + FSWorkOrderProduct.Modify(); + end; + + local procedure UpdateWorkOrderService(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderService: Record "FS Work Order Service") + var + Modified: Boolean; + begin + // TODO! Add Quantity Shipped + // if FSWorkOrderService.QuantityShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin + // FSWorkOrderService.QuantityShipped := SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship"; + // Modified := true; + // end; + + if FSWorkOrderService.DurationInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") then begin + FSWorkOrderService.DurationInvoiced := (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") * 60; + Modified := true; + end; + + if FSWorkOrderService.DurationConsumed <> SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume" then begin + FSWorkOrderService.DurationConsumed := (SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume") * 60; + Modified := true; + end; + + if Modified then + FSWorkOrderService.Modify(); + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index c814f27e61..14bb22d8e3 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -304,6 +304,26 @@ codeunit 6610 "FS Int. Table Subscriber" IsValueFound := true; NeedsConversion := false; end; + FSWorkOrderService.FieldName(DurationInvoiced): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(ServiceLine); + DurationInHours := ServiceLine."Quantity Invoiced"; + DurationInMinutes := DurationInHours * 60; + NewValue := DurationInMinutes; + IsValueFound := true; + NeedsConversion := false; + end; + FSWorkOrderService.FieldName(DurationConsumed): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(ServiceLine); + DurationInHours := ServiceLine."Quantity Consumed"; + DurationInMinutes := DurationInHours * 60; + NewValue := DurationInMinutes; + IsValueFound := true; + NeedsConversion := false; + end; end; if (SourceFieldRef.Record().Number = Database::"FS Work Order Service") then @@ -928,11 +948,16 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure ArchiveServiceOrder(ServiceHeader: Record "Service Header"; ArchivedServiceOrders: List of [Code[20]]) var + ServiceMgtSetup: Record "Service Mgt. Setup"; ServiceDocumentArchiveMgmt: Codeunit "Service Document Archive Mgmt."; begin if ArchivedServiceOrders.Contains(ServiceHeader."No.") then exit; + ServiceMgtSetup.Get(); + if not ServiceMgtSetup."Archive Orders" then + exit; + ArchivedServiceOrders.Add(ServiceHeader."No."); ServiceDocumentArchiveMgmt.ArchServiceDocumentNoConfirm(ServiceHeader); end; @@ -1793,6 +1818,13 @@ codeunit 6610 "FS Int. Table Subscriber" if IgnoreRecord then exit; + case SourceRecordRef.Number() of + Database::"Service Header": + IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord); + Database::"FS Work Order": + IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord); + end; + if FSConnectionSetup.IsEnabled() then exit; @@ -1825,6 +1857,46 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + if IgnoreRecord then + exit; + + SourceRecordRef.SetTable(ServiceHeader); + if not CRMIntegrationRecord.FindByRecordID(ServiceHeader.RecordId) then + exit; + + if CRMIntegrationRecord."Archived Service Order" then + IgnoreRecord := true; + end; + + local procedure IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrder: Record "FS Work Order"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + if IgnoreRecord then + exit; + + SourceRecordRef.SetTable(FSWorkOrder); + if not CRMIntegrationRecord.FindByCRMID(FSWorkOrder.WorkOrderId) then + exit; + + if CRMIntegrationRecord."Archived Service Order" then + IgnoreRecord := true; + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Table Synch.", 'OnAfterInitSynchJob', '', true, true)] local procedure LogTelemetryOnAfterInitSynchJob(ConnectionType: TableConnectionType; IntegrationTableID: Integer) var @@ -1908,6 +1980,60 @@ codeunit 6610 "FS Int. Table Subscriber" IntegrationMgt.TestOneServiceItemLinePerOrderModificationIsAllowed(Rec); end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnIsCRMIntegrationRecord', '', false, false)] + local procedure HandleOnIsCRMIntegrationRecord(TableID: Integer; var isIntegrationRecord: Boolean) + begin + if TableID = Database::"Service Header Archive" then + isIntegrationRecord := true; + end; + + [EventSubscriber(ObjectType::Table, Database::"Service Header", 'OnAfterDeleteEvent', '', false, false)] + local procedure HandleOnAfterDeleteAfterPosting(var Rec: Record "Service Header") + begin + if Rec.IsTemporary() then + exit; + + MarkArchivedServiceOrder(Rec); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Service Document Archive Mgmt.", 'OnAfterStoreServiceLineArchive', '', false, false)] + local procedure OnAfterStoreServiceLineArchive(var ServiceLine: Record "Service Line"; var ServiceLineArchive: Record "Service Line Archive") + begin + MarkArchivedServiceOrderLine(ServiceLine, ServiceLineArchive); + end; + + procedure MarkArchivedServiceOrder(ServiceHeader: Record "Service Header") + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Header"); + CRMIntegrationRecord.SetRange("Integration ID", ServiceHeader.SystemId); + if CRMIntegrationRecord.FindFirst() then begin + CRMIntegrationRecord."Archived Service Order" := true; + CRMIntegrationRecord.Modify(); + end; + end; + + local procedure MarkArchivedServiceOrderLine(var ServiceLine: Record "Service Line"; var ServiceLineArchive: Record "Service Line Archive") + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Line"); + CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); + if CRMIntegrationRecord.FindFirst() then begin + CRMIntegrationRecord."Archived Service Line Id" := ServiceLineArchive.SystemId; + CRMIntegrationRecord.Modify(); + end; + end; + [IntegrationEvent(false, false)] local procedure OnSetUpNewLineOnNewLine(var JobJournalLine: Record "Job Journal Line"; var JobJournalTemplate: Record "Job Journal Template"; var JobJournalBatch: Record "Job Journal Batch"; var Handled: Boolean); begin diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 454d92672a..1bd87a7ff1 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -528,6 +528,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceOrder: Record "Service Header"; FSWorkOrder: Record "FS Work Order"; + CRMSetupDefaults: Codeunit "CRM Setup Defaults"; + ArchivedServiceOrdersSynchJobDescTxt: Label 'Archived Service Orders - %1 synchronization job', Comment = '%1 = CRM product name'; IsHandled: Boolean; begin IsHandled := false; @@ -599,6 +601,7 @@ codeunit 6611 "FS Setup Defaults" '', true, false); RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 30); + CRMSetupDefaults.RecreateJobQueueEntry(ShouldRecreateJobQueueEntry, Codeunit::"FS Archived Service Orders Job", 30, StrSubstNo(ArchivedServiceOrdersSynchJobDescTxt, CRMProductName.SHORT()), false) end; local procedure ResetServiceOrderItemLineMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) @@ -607,7 +610,6 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceItemLine: Record "Service Item Line"; FSWorkOrderIncident: Record "FS Work Order Incident"; - EmptyGuid: Guid; IsHandled: Boolean; begin IsHandled := false; @@ -750,11 +752,25 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); + // InsertIntegrationFieldMapping( + // IntegrationTableMappingName, + // ServiceLine.FieldNo("Quantity Shipped"), + // FSWorkOrderProduct.FieldNo(QuantityInvoiced), // TODO! QuantityShipped + // IntegrationFieldMapping.Direction::ToIntegrationTable, + // '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Invoice"), - FSWorkOrderProduct.FieldNo(QtyToBill), - IntegrationFieldMapping.Direction::Bidirectional, + ServiceLine.FieldNo("Quantity Invoiced"), + FSWorkOrderProduct.FieldNo(QuantityInvoiced), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Quantity Consumed"), + FSWorkOrderProduct.FieldNo(QuantityConsumed), + IntegrationFieldMapping.Direction::ToIntegrationTable, '', true, false); end; @@ -850,6 +866,27 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderService.FieldNo(DurationToBill), IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); + + // InsertIntegrationFieldMapping( + // IntegrationTableMappingName, + // ServiceLine.FieldNo("Quantity Shipped"), + // FSWorkOrderProduct.FieldNo(QuantityInvoiced), // TODO! QuantityShipped + // IntegrationFieldMapping.Direction::ToIntegrationTable, + // '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Quantity Invoiced"), + FSWorkOrderService.FieldNo(DurationInvoiced), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Quantity Consumed"), + FSWorkOrderService.FieldNo(DurationConsumed), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); end; local procedure ResetServiceOrderLineResourceMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al new file mode 100644 index 0000000000..db4c196cf2 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Integration.Dataverse; + +tableextension 6617 "FS Integration Record" extends "CRM Integration Record" +{ + fields + { + field(12000; "Archived Service Order"; Boolean) + { + Caption = 'Archived Service Order'; + DataClassification = SystemMetadata; + } + field(12001; "Archived Service Order Updated"; Boolean) + { + Caption = 'Archived Service Order Updated'; + DataClassification = SystemMetadata; + } + field(12002; "Archived Service Line Id"; Guid) + { + Caption = 'Archived Service Line Id'; + DataClassification = SystemMetadata; + } + } + + keys + { + key(Key1; "Archived Service Line Id") + { + } + } +} From 52deb44e0e5d6fb1ed37dfeb659609e6089e44e6 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 5 Sep 2024 08:19:15 +0200 Subject: [PATCH 09/49] added: support of job plan line reversal --- .../FSIntTableSubscriber.Codeunit.al | 59 ++++++++++++++++++- .../Codeunits/FSIntegrationMgt.Codeunit.al | 3 +- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 16 ++--- .../app/src/Tables/FSConnectionSetup.Table.al | 5 ++ 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 14bb22d8e3..167230d8a4 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -343,7 +343,8 @@ codeunit 6610 "FS Int. Table Subscriber" begin SourceRecordRef := SourceFieldRef.Record(); SourceRecordRef.SetTable(FSWorkOrderService); - SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + if DestinationFieldRef.Record().Number = Database::"Job Journal Line" then + SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); if SourceFieldRef.Name() in [FSWorkOrderService.FieldName(Duration)] then begin DurationInMinutes := FSWorkOrderService.Duration; DurationInHours := (DurationInMinutes / 60); @@ -401,7 +402,8 @@ codeunit 6610 "FS Int. Table Subscriber" begin SourceRecordRef := SourceFieldRef.Record(); SourceRecordRef.SetTable(FSWorkOrderProduct); - SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + if DestinationFieldRef.Record().Number = Database::"Job Journal Line" then + SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); if SourceFieldRef.Name() = FSWorkOrderProduct.FieldName(Quantity) then begin Quantity := FSWorkOrderProduct.Quantity - QuantityCurrentlyConsumed; NewValue := Quantity; @@ -1493,6 +1495,7 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure SetCurrentProjectPlanningQuantities(var SourceRecordRef: RecordRef; var QuantityCurrentlyConsumed: Decimal; var QuantityCurrentlyInvoiced: Decimal) var + FSConnectionSetup: Record "FS Connection Setup"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; FSBookableResourceBooking: Record "FS Bookable Resource Booking"; @@ -1504,6 +1507,10 @@ codeunit 6610 "FS Int. Table Subscriber" begin QuantityCurrentlyConsumed := 0; QuantityCurrentlyInvoiced := 0; + + if not FSConnectionSetup.IsIntegrationTypeProjectEnabled() then + exit; + case SourceRecordRef.Number() of Database::"FS Work Order Product": begin @@ -1819,6 +1826,9 @@ codeunit 6610 "FS Int. Table Subscriber" exit; case SourceRecordRef.Number() of + Database::"FS Work Order Product", + Database::"FS Work Order Service": + IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord); Database::"Service Header": IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord); Database::"FS Work Order": @@ -1857,6 +1867,51 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + FieldServiceId: Guid; + QuantityCurrentlyConsumed: Decimal; + QuantityCurrentlyInvoiced: Decimal; + FSQuantityToConsume: Decimal; + FSQuantityToInvoice: Decimal; + begin + if not FSConnectionSetup.IsIntegrationTypeProjectEnabled() then + exit; + if IgnoreRecord then + exit; + if FSConnectionSetup."Line Synch. Rule" <> "FS Work Order Line Synch. Rule"::LineUsed then + exit; + + case SourceRecordRef.Number() of + Database::"FS Work Order Product": + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + FieldServiceId := FSWorkOrderProduct.WorkOrderProductId; + if FSWorkOrderProduct.LineStatus = FSWorkOrderProduct.LineStatus::Used then begin + FSQuantityToConsume := FSWorkOrderProduct.Quantity; + FSQuantityToInvoice := FSWorkOrderProduct.QtyToBill; + end; + end; + Database::"FS Work Order Service": + begin + SourceRecordRef.SetTable(FSWorkOrderService); + FieldServiceId := FSWorkOrderService.WorkOrderServiceId; + if FSWorkOrderService.LineStatus = FSWorkOrderService.LineStatus::Used then begin + FSQuantityToConsume := FSWorkOrderService.Duration / 60; + FSQuantityToInvoice := FSWorkOrderService.DurationToBill / 60; + end; + end; + end; + + SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + + if (QuantityCurrentlyConsumed = FSQuantityToConsume) and (QuantityCurrentlyInvoiced = FSQuantityToInvoice) then + IgnoreRecord := true; + end; + local procedure IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) var FSConnectionSetup: Record "FS Connection Setup"; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al index 1109a0668b..3badbccdbc 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al @@ -249,8 +249,7 @@ codeunit 6615 "FS Integration Mgt." if not ServiceMgtSetup."One Service Item Line/Order" then exit; - ConnectionSetup.Get(); - if not (ConnectionSetup."Integration Type" in [ConnectionSetup."Integration Type"::Service, ConnectionSetup."Integration Type"::Both]) then + if not ConnectionSetup.IsIntegrationTypeServiceEnabled() then exit; ConnectionSetup.TestField("Is Enabled", false); diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 1bd87a7ff1..617dbe158f 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -145,7 +145,7 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderProduct.SetFilter(ProjectTask, '<>' + Format(EmptyGuid)); case FSConnectionSetup."Line Synch. Rule" of "FS Work Order Line Synch. Rule"::LineUsed: - FSWorkOrderProduct.SetRange(LineStatus, FSWorkOrderProduct.LineStatus::Used); + FSWorkOrderProduct.SetFilter(LineStatus, Format(FSWorkOrderProduct.LineStatus::Estimated) + '|' + Format(FSWorkOrderProduct.LineStatus::Used)); "FS Work Order Line Synch. Rule"::WorkOrderCompleted: FSWorkOrderProduct.SetFilter(WorkOrderStatus, Format(FSWorkOrderProduct.WorkOrderStatus::Completed) + '|' + Format(FSWorkOrderProduct.WorkOrderStatus::Posted)); end; @@ -162,6 +162,7 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMapping.SetIntegrationTableFilter( GetTableFilterFromView(Database::"FS Work Order Product", FSWorkOrderProduct.TableCaption(), FSWorkOrderProduct.GetView())); IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|ITEM-PRODUCT'; + IntegrationTableMapping."Deletion-Conflict Resolution" := IntegrationTableMapping."Deletion-Conflict Resolution"::"Restore Records"; IntegrationTableMapping.Modify(); InsertIntegrationFieldMapping( @@ -200,11 +201,11 @@ codeunit 6611 "FS Setup Defaults" '', true, false); InsertIntegrationFieldMapping( - IntegrationTableMappingName, - JobJournalLine.FieldNo("Location Code"), - FSWorkOrderProduct.FieldNo(WarehouseId), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); + IntegrationTableMappingName, + JobJournalLine.FieldNo("Location Code"), + FSWorkOrderProduct.FieldNo(WarehouseId), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); OnAfterResetProjectJournalLineWOProductMapping(IntegrationTableMappingName); @@ -232,7 +233,7 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderService.SetFilter(ProjectTask, '<>' + Format(EmptyGuid)); case FSConnectionSetup."Line Synch. Rule" of "FS Work Order Line Synch. Rule"::LineUsed: - FSWorkOrderService.SetRange(LineStatus, FSWorkOrderService.LineStatus::Used); + FSWorkOrderService.SetFilter(LineStatus, Format(FSWorkOrderService.LineStatus::Estimated) + '|' + Format(FSWorkOrderService.LineStatus::Used)); "FS Work Order Line Synch. Rule"::WorkOrderCompleted: FSWorkOrderService.SetFilter(WorkOrderStatus, Format(FSWorkOrderService.WorkOrderStatus::Completed) + '|' + Format(FSWorkOrderService.WorkOrderStatus::Posted)); end; @@ -248,6 +249,7 @@ codeunit 6611 "FS Setup Defaults" GetTableFilterFromView(Database::"FS Work Order Service", FSWorkOrderService.TableCaption(), FSWorkOrderService.GetView())); IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|ITEM-PRODUCT|RESOURCE-BOOKABLERSC'; + IntegrationTableMapping."Deletion-Conflict Resolution" := IntegrationTableMapping."Deletion-Conflict Resolution"::"Restore Records"; IntegrationTableMapping.Modify(); InsertIntegrationFieldMapping( diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al index 8c9866d10c..4939b3043b 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al @@ -1032,6 +1032,11 @@ table 6623 "FS Connection Setup" exit(IsEnabled() and ("Integration Type" in ["Integration Type"::Service, "Integration Type"::Both])); end; + internal procedure IsIntegrationTypeProjectEnabled(): Boolean + begin + exit(IsEnabled() and ("Integration Type" in ["Integration Type"::Project, "Integration Type"::Both])); + end; + internal procedure GetProxyVersion(): Integer var EnvironmentInformation: Codeunit "Environment Information"; From e30acae495dc60a827f75354efe75b8c3c657cd7 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 5 Sep 2024 12:49:31 +0200 Subject: [PATCH 10/49] added: default value for integrate to service (service order type) --- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 17 ++++++++++++++++- .../app/src/Tables/FSWorkOrder.Table.al | 2 +- .../app/src/Tables/FSWorkOrderType.Table.al | 8 ++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 617dbe158f..22f631f2aa 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -497,6 +497,7 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderType.Reset(); FSWorkOrderType.SetRange(StateCode, FSWorkOrderType.StateCode::Active); + FSWorkOrderType.SetRange(IntegrateToService, true); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, Database::"Service Order Type", Database::"FS Work Order Type", @@ -521,6 +522,13 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + 0, + FSWorkOrderType.FieldNo(IntegrateToService), + IntegrationFieldMapping.Direction::Bidirectional, + 'true', true, false); + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 30); end; @@ -546,7 +554,7 @@ codeunit 6611 "FS Setup Defaults" ServiceOrder.Reset(); ServiceOrder.SetRange("Document Type", ServiceOrder."Document Type"::Order); FSWorkOrder.Reset(); - FSWorkOrder.SetRange("Integrate to Service", true); + FSWorkOrder.SetRange(IntegrateToService, true); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, @@ -602,6 +610,13 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + 0, + FSWorkOrder.FieldNo(IntegrateToService), + IntegrationFieldMapping.Direction::Bidirectional, + 'true', true, false); + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 30); CRMSetupDefaults.RecreateJobQueueEntry(ShouldRecreateJobQueueEntry, Codeunit::"FS Archived Service Orders Job", 30, StrSubstNo(ArchivedServiceOrdersSynchJobDescTxt, CRMProductName.SHORT()), false) end; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al index f6d7192bb2..22fa8a93a4 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al @@ -886,7 +886,7 @@ table 6617 "FS Work Order" TableRelation = "CDS Company".CompanyId; DataClassification = SystemMetadata; } - field(165; "Integrate to Service"; Boolean) + field(165; IntegrateToService; Boolean) { ExternalName = 'bcbi_integratetoervice'; ExternalType = 'Boolean'; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al index 2c1c82cacd..91b6974075 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al @@ -227,6 +227,14 @@ table 6622 "FS Work Order Type" Caption = 'Code'; DataClassification = SystemMetadata; } + field(41; IntegrateToService; Boolean) + { + ExternalName = 'bcbi_integratetoservice'; + ExternalType = 'Boolean'; + Description = 'Select whether work orders of this type are integrated to service.'; + Caption = 'Integrate To Service'; + DataClassification = SystemMetadata; + } } keys { From 24baad5b9c123c3c2c9767b4de994338044bb62d Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 5 Sep 2024 15:29:17 +0200 Subject: [PATCH 11/49] added: bookable resource bookings sync --- .../FSIntTableSubscriber.Codeunit.al | 141 ++++++++++++++---- .../Codeunits/FSIntegrationMgt.Codeunit.al | 12 ++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 16 +- .../Tables/FSBookableResourceBooking.Table.al | 10 ++ .../app/src/Tables/FSBookingStatus.Table.al | 97 ++++++++++++ 5 files changed, 245 insertions(+), 31 deletions(-) create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookingStatus.Table.al diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 167230d8a4..203e8c891e 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -149,6 +149,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSProjectTask: Record "FS Project Task"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; CRMIntegrationRecord: Record "CRM Integration Record"; FSWorkOrderIncident: Record "FS Work Order Incident"; FSIncident: Record "FS Incident Type"; @@ -254,6 +255,24 @@ codeunit 6610 "FS Int. Table Subscriber" ServiceLine."Service Item No." := ServiceItemLine."Service Item No."; ServiceLine.Type := ServiceLine.Type::Item; + DestinationRecordRef.GetTable(ServiceLine); + end; + 'FS Bookable Resource Booking-Service Line': + begin + SourceRecordRef.SetTable(FSBookableResourceBooking); + DestinationRecordRef.SetTable(ServiceLine); + + if ServiceLine."Document No." <> '' then + exit; + + if CRMIntegrationRecord.FindByCRMID(FSBookableResourceBooking.WorkOrder) then + ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID"); + + ServiceLine."Document Type" := ServiceLine."Document Type"::Order; + ServiceLine."Document No." := ServiceHeader."No."; + ServiceLine."Line No." := GetNextLineNo(ServiceLine); + ServiceLine.Type := ServiceLine.Type::Resource; + DestinationRecordRef.GetTable(ServiceLine); end; end; @@ -443,6 +462,19 @@ codeunit 6610 "FS Int. Table Subscriber" exit; end; end; + if (SourceFieldRef.Record().Number = Database::"FS Bookable Resource Booking") then + case SourceFieldRef.Name() of + FSBookableResourceBooking.FieldName(Duration): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSBookableResourceBooking); + DurationInMinutes := FSBookableResourceBooking.Duration; + DurationInHours := (DurationInMinutes / 60); + NewValue := DurationInHours; + IsValueFound := true; + NeedsConversion := false; + end; + end; end; [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Int. Table. Subscriber", 'OnFindNewValueForCoupledRecordPK', '', false, false)] @@ -600,6 +632,7 @@ codeunit 6610 "FS Int. Table Subscriber" ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSBookableResourceBooking(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin @@ -652,6 +685,7 @@ codeunit 6610 "FS Int. Table Subscriber" ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSBookableResourceBooking(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin @@ -680,6 +714,7 @@ codeunit 6610 "FS Int. Table Subscriber" ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSBookableResourceBooking(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin @@ -766,15 +801,17 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderProduct2: Record "FS Work Order Product"; CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; FSWorkOrderProductRecordRef: RecordRef; - CRMSalesorderdetailId: Guid; - CRMSalesorderdetailIdList: List of [Guid]; - CRMSalesorderdetailIdFilter: Text; + FSWorkOrderProductId: Guid; + FSWorkOrderProductIdList: List of [Guid]; + FSWorkOrderProductIdFilter: Text; begin SourceRecordRef.SetTable(FSWorkOrder); DestinationRecordRef.SetTable(ServiceHeader); ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); ServiceLine.SetRange("Document No.", ServiceHeader."No."); + ServiceLine.SetRange(Type, ServiceLine.Type::Item); + ServiceLine.SetRange("Item Type", ServiceLine."Item Type"::Inventory); if ServiceLine.FindSet() then repeat CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); @@ -794,14 +831,14 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderProduct.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); if FSWorkOrderProduct.FindSet() then begin repeat - CRMSalesorderdetailIdList.Add(FSWorkOrderProduct.WorkOrderProductId) + FSWorkOrderProductIdList.Add(FSWorkOrderProduct.WorkOrderProductId) until FSWorkOrderProduct.Next() = 0; - foreach CRMSalesorderdetailId in CRMSalesorderdetailIdList do - CRMSalesorderdetailIdFilter += CRMSalesorderdetailId + '|'; - CRMSalesorderdetailIdFilter := CRMSalesorderdetailIdFilter.TrimEnd('|'); + foreach FSWorkOrderProductId in FSWorkOrderProductIdList do + FSWorkOrderProductIdFilter += FSWorkOrderProductId + '|'; + FSWorkOrderProductIdFilter := FSWorkOrderProductIdFilter.TrimEnd('|'); - FSWorkOrderProduct2.SetFilter(WorkOrderProductId, CRMSalesorderdetailIdFilter); + FSWorkOrderProduct2.SetFilter(WorkOrderProductId, FSWorkOrderProductIdFilter); FSWorkOrderProductRecordRef.GetTable(FSWorkOrderProduct2); CRMIntegrationTableSynch.SynchRecordsFromIntegrationTable(FSWorkOrderProductRecordRef, Database::"Service Line", false, false); end; @@ -810,23 +847,25 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var ArchivedServiceOrders: List of [Code[20]]) var ServiceHeader: Record "Service Header"; - ServiceLine: Record "Service Item Line"; - ServiceItemLineToDelete: Record "Service Item Line"; + ServiceLine: Record "Service Line"; + ServiceLineToDelete: Record "Service Line"; CRMIntegrationRecord: Record "CRM Integration Record"; FSWorkOrder: Record "FS Work Order"; FSWorkOrderService: Record "FS Work Order Service"; FSWorkOrderService2: Record "FS Work Order Service"; CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; FSWorkOrderServiceRecordRef: RecordRef; - CRMSalesorderdetailId: Guid; - CRMSalesorderdetailIdList: List of [Guid]; - CRMSalesorderdetailIdFilter: Text; + FSWorkOrderServiceId: Guid; + FSWorkOrderServiceIdList: List of [Guid]; + FSWorkOrderServiceIdFilter: Text; begin SourceRecordRef.SetTable(FSWorkOrder); DestinationRecordRef.SetTable(ServiceHeader); ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); ServiceLine.SetRange("Document No.", ServiceHeader."No."); + ServiceLine.SetRange(Type, ServiceLine.Type::Item); + ServiceLine.SetFilter("Item Type", '%1|%2', ServiceLine."Item Type"::Service, ServiceLine."Item Type"::"Non-Inventory"); if ServiceLine.FindSet() then repeat CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); @@ -836,8 +875,8 @@ codeunit 6610 "FS Int. Table Subscriber" if FSWorkOrderService.IsEmpty() then begin CRMIntegrationRecord.Delete(); ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); - if ServiceItemLineToDelete.GetBySystemId(ServiceLine.SystemId) then - ServiceItemLineToDelete.Delete(true); + if ServiceLineToDelete.GetBySystemId(ServiceLine.SystemId) then + ServiceLineToDelete.Delete(true); end; end; until ServiceLine.Next() = 0; @@ -846,19 +885,72 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderService.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); if FSWorkOrderService.FindSet() then begin repeat - CRMSalesorderdetailIdList.Add(FSWorkOrderService.WorkOrderServiceId) + FSWorkOrderServiceIdList.Add(FSWorkOrderService.WorkOrderServiceId) until FSWorkOrderService.Next() = 0; - foreach CRMSalesorderdetailId in CRMSalesorderdetailIdList do - CRMSalesorderdetailIdFilter += CRMSalesorderdetailId + '|'; - CRMSalesorderdetailIdFilter := CRMSalesorderdetailIdFilter.TrimEnd('|'); + foreach FSWorkOrderServiceId in FSWorkOrderServiceIdList do + FSWorkOrderServiceIdFilter += FSWorkOrderServiceId + '|'; + FSWorkOrderServiceIdFilter := FSWorkOrderServiceIdFilter.TrimEnd('|'); - FSWorkOrderService2.SetFilter(WorkOrderServiceId, CRMSalesorderdetailIdFilter); + FSWorkOrderService2.SetFilter(WorkOrderServiceId, FSWorkOrderServiceIdFilter); FSWorkOrderServiceRecordRef.GetTable(FSWorkOrderService2); CRMIntegrationTableSynch.SynchRecordsFromIntegrationTable(FSWorkOrderServiceRecordRef, Database::"Service Line", false, false); end; end; + local procedure ResetServiceOrderLineFromFSBookableResourceBooking(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var ArchivedServiceOrders: List of [Code[20]]) + var + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + ServiceLineToDelete: Record "Service Line"; + CRMIntegrationRecord: Record "CRM Integration Record"; + FSWorkOrder: Record "FS Work Order"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + FSBookableResourceBooking2: Record "FS Bookable Resource Booking"; + CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; + FSBookableResourceBookingRecordRef: RecordRef; + FSBookableResourceBookingId: Guid; + FSBookableResourceBookingIdList: List of [Guid]; + FSBookableResourceBookingIdFilter: Text; + begin + SourceRecordRef.SetTable(FSWorkOrder); + DestinationRecordRef.SetTable(ServiceHeader); + + ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); + ServiceLine.SetRange("Document No.", ServiceHeader."No."); + ServiceLine.SetRange(Type, ServiceLine.Type::Resource); + if ServiceLine.FindSet() then + repeat + CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Line"); + if CRMIntegrationRecord.FindFirst() then begin + FSBookableResourceBooking.SetRange(BookableResourceBookingId, CRMIntegrationRecord."CRM ID"); + if FSBookableResourceBooking.IsEmpty() then begin + CRMIntegrationRecord.Delete(); + ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); + if ServiceLineToDelete.GetBySystemId(ServiceLine.SystemId) then + ServiceLineToDelete.Delete(true); + end; + end; + until ServiceLine.Next() = 0; + + FSBookableResourceBooking.Reset(); + FSBookableResourceBooking.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + if FSBookableResourceBooking.FindSet() then begin + repeat + FSBookableResourceBookingIdList.Add(FSBookableResourceBooking.BookableResourceBookingId) + until FSBookableResourceBooking.Next() = 0; + + foreach FSBookableResourceBookingId in FSBookableResourceBookingIdList do + FSBookableResourceBookingIdFilter += FSBookableResourceBookingId + '|'; + FSBookableResourceBookingIdFilter := FSBookableResourceBookingIdFilter.TrimEnd('|'); + + FSBookableResourceBooking2.SetFilter(BookableResourceBookingId, FSBookableResourceBookingIdFilter); + FSBookableResourceBookingRecordRef.GetTable(FSBookableResourceBooking2); + CRMIntegrationTableSynch.SynchRecordsFromIntegrationTable(FSBookableResourceBookingRecordRef, Database::"Service Line", false, false); + end; + end; + local procedure ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) var ServiceHeader: Record "Service Header"; @@ -1074,6 +1166,7 @@ codeunit 6610 "FS Int. Table Subscriber" ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); + ResetServiceOrderLineFromFSBookableResourceBooking(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); end; 'Service Header-FS Work Order': begin @@ -1312,14 +1405,6 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderService.WorkOrderIncident := WorkOrderIncidentId; DestinationRecordRef.GetTable(FSWorkOrderService); end; - 'Service Line-FS Bookable Resource Booking': - begin - SourceRecordRef.SetTable(ServiceLine); - DestinationRecordRef.SetTable(FSBookableResourceBooking); - if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceItemLine."Document No."), WorkOrderId) then - FSBookableResourceBooking.WorkOrder := WorkOrderId; - DestinationRecordRef.GetTable(ServiceLine); - end; end; end; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al index 3badbccdbc..509afdda03 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al @@ -281,6 +281,18 @@ codeunit 6615 "FS Integration Mgt." exit(IncidentType.IncidentTypeId); end; + internal procedure GetBookingStatusCompleted(): Guid + var + FSBookingStatus: Record "FS Booking Status"; + EmptyGuid: Guid; + begin + FSBookingStatus.SetRange(FieldServiceStatus, FSBookingStatus.FieldServiceStatus::Completed); + if not FSBookingStatus.FindFirst() then + exit(EmptyGuid); + + exit(FSBookingStatus.BookingStatusId); + end; + [EventSubscriber(ObjectType::Table, Database::"Service Connection", 'OnRegisterServiceConnection', '', false, false)] local procedure RegisterFSConnectionOnRegisterServiceConnection(var ServiceConnection: Record "Service Connection") diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 22f631f2aa..56b77db555 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -912,6 +912,7 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceLine: Record "Service Line"; FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + FSIntegrationMgt: Codeunit "FS Integration Mgt."; IsHandled: Boolean; EmptyGuid: Guid; begin @@ -927,8 +928,10 @@ codeunit 6611 "FS Setup Defaults" ServiceLine.Reset(); ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); ServiceLine.SetRange(Type, ServiceLine.Type::Resource); + FSBookableResourceBooking.Reset(); FSBookableResourceBooking.SetFilter(WorkOrder, '<>%1', EmptyGuid); + FSBookableResourceBooking.SetRange(BookingStatus, FSIntegrationMgt.GetBookingStatusCompleted()); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, @@ -953,21 +956,28 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMappingName, ServiceLine.FieldNo("No."), FSBookableResourceBooking.FieldNo(Resource), - IntegrationFieldMapping.Direction::Bidirectional, + IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo(Description), FSBookableResourceBooking.FieldNo(Name), - IntegrationFieldMapping.Direction::Bidirectional, + IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo(Quantity), FSBookableResourceBooking.FieldNo(Duration), - IntegrationFieldMapping.Direction::Bidirectional, + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Qty. to Consume"), + FSBookableResourceBooking.FieldNo(Duration), + IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); end; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al index f566b60040..b4cef60fce 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al @@ -616,6 +616,16 @@ table 6611 "FS Bookable Resource Booking" ExternalType = 'String'; ExternalAccess = Read; } + field(110; BookingStatus; Guid) + { + ExternalName = 'bookingstatus'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the booking status.'; + Caption = 'Booking Status'; + TableRelation = "FS Booking Status".BookingStatusId; + DataClassification = SystemMetadata; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookingStatus.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookingStatus.Table.al new file mode 100644 index 0000000000..c38f0f3103 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookingStatus.Table.al @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Integration.D365Sales; + +table 6627 "FS Booking Status" +{ + ExternalName = 'bookingstatus'; + TableType = CRM; + Description = 'Booking Status in CRM.'; + DataClassification = SystemMetadata; + + fields + { + field(1; BookingStatusId; GUID) + { + ExternalName = 'bookingstatusid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Read; + Description = 'Unique identifier of the booking state.'; + Caption = 'Booking Status Id'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'CreatedOn'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Date and time when the record was created.'; + Caption = 'Created On'; + DataClassification = SystemMetadata; + } + field(3; CreatedBy; GUID) + { + ExternalName = 'CreatedBy'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the user who created the record.'; + Caption = 'Created By'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(4; ModifiedOn; Datetime) + { + ExternalName = 'ModifiedOn'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Date and time when the record was modified.'; + Caption = 'Modified On'; + DataClassification = SystemMetadata; + } + field(5; ModifiedBy; GUID) + { + ExternalName = 'ModifiedBy'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the user who modified the record.'; + Caption = 'Modified By'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(10; Name; Text[100]) + { + ExternalName = 'name'; + ExternalType = 'String'; + Description = 'Type the name of the booking status.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(11; FieldServiceStatus; Option) + { + ExternalName = 'msdyn_fieldservicestatus'; + ExternalType = 'Picklist'; + Description = 'Status in Field Service.'; + OptionMembers = "",Scheduled,Traveling," On Break","In Progress",Completed,Canceled; + OptionOrdinalValues = -1, 690970000, 690970001, 690970002, 690970003, 690970004, 690970005; + Caption = 'Field Service Status'; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; BookingStatusId) + { + Clustered = true; + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file From 05d02fcbfe2a214c593e943b5b91af97a25b72c0 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Fri, 6 Sep 2024 09:25:23 +0200 Subject: [PATCH 12/49] added: use max value and resync estimated qty if not used only --- .../FSIntTableSubscriber.Codeunit.al | 101 ++++++++++++++++++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 42 -------- 2 files changed, 101 insertions(+), 42 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 203e8c891e..10333702ec 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -278,6 +278,43 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterTransferRecordFields', '', false, false)] + local procedure OnAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + ServiceLine: Record "Service Line"; + SourceDestCode: Text; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of + 'FS Work Order Product-Service Line': + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + DestinationRecordRef.SetTable(ServiceLine); + + UpdateQuantities(FSWorkOrderProduct, ServiceLine); + + DestinationRecordRef.GetTable(ServiceLine); + end; + + 'FS Work Order Service-Service Line': + begin + SourceRecordRef.SetTable(FSWorkOrderService); + DestinationRecordRef.SetTable(ServiceLine); + + UpdateQuantities(FSWorkOrderService, ServiceLine); + + DestinationRecordRef.GetTable(ServiceLine); + end; + end; + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Record Synch.", 'OnTransferFieldData', '', false, false)] local procedure OnTransferFieldData(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean; var NeedsConversion: Boolean) var @@ -309,12 +346,35 @@ codeunit 6610 "FS Int. Table Subscriber" if SourceFieldRef.Number() = DestinationFieldRef.Number() then if SourceFieldRef.Record().Number() = DestinationFieldRef.Record().Number() then exit; + if (SourceFieldRef.Record().Number = Database::"Service Line") and + (DestinationFieldRef.Record().Number = Database::"FS Work Order Product") then + case DestinationFieldRef.Name() of + FSWorkOrderProduct.FieldName(EstimateQuantity): + begin + DestinationRecordRef := DestinationFieldRef.Record(); + DestinationRecordRef.SetTable(FSWorkOrderProduct); + // only update estimated quantity if the line status is estimated + if FSWorkOrderProduct.LineStatus <> FSWorkOrderProduct.LineStatus::Estimated then begin + IsValueFound := true; + NeedsConversion := false; + end; + end; + end; if (SourceFieldRef.Record().Number = Database::"Service Line") and (DestinationFieldRef.Record().Number = Database::"FS Work Order Service") then case DestinationFieldRef.Name() of FSWorkOrderService.FieldName(EstimateDuration): begin + DestinationRecordRef := DestinationFieldRef.Record(); + DestinationRecordRef.SetTable(FSWorkOrderService); + // only update estimated quantity if the line status is estimated + if FSWorkOrderService.LineStatus <> FSWorkOrderService.LineStatus::Estimated then begin + IsValueFound := true; + NeedsConversion := false; + exit; + end; + SourceRecordRef := SourceFieldRef.Record(); SourceRecordRef.SetTable(ServiceLine); DurationInHours := ServiceLine.Quantity; @@ -477,6 +537,47 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") + begin + ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.EstimateQuantity, FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill)); + ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill)); + ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.QtyToBill); + end; + + local procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Service"; var ServiceLine: Record "Service Line") + begin + ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.EstimateDuration, FSWorkOrderProduct.Duration, FSWorkOrderProduct.DurationToBill) / 60); + ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Duration, FSWorkOrderProduct.DurationToBill) / 60); + ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.DurationToBill / 60); + end; + + procedure GetMaxQuantity(Quantity1: Decimal; Quantity2: Decimal): Decimal + var + MaxQuantity: Decimal; + begin + MaxQuantity := Quantity1; + + if Quantity2 > MaxQuantity then + MaxQuantity := Quantity2; + + exit(MaxQuantity); + end; + + procedure GetMaxQuantity(Quantity1: Decimal; Quantity2: Decimal; Quantity3: Decimal): Decimal + var + MaxQuantity: Decimal; + begin + MaxQuantity := Quantity1; + + if Quantity2 > MaxQuantity then + MaxQuantity := Quantity2; + + if Quantity3 > MaxQuantity then + MaxQuantity := Quantity3; + + exit(MaxQuantity); + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Int. Table. Subscriber", 'OnFindNewValueForCoupledRecordPK', '', false, false)] local procedure OnFindNewValueForCoupledRecordPK(IntegrationTableMapping: Record "Integration Table Mapping"; SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant; var IsValueFound: Boolean) var diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 56b77db555..7a4d72061f 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -748,27 +748,6 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Ship"), - FSWorkOrderProduct.FieldNo(Quantity), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); - - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Consume"), - FSWorkOrderProduct.FieldNo(QtyToBill), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); - - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Invoice"), - FSWorkOrderProduct.FieldNo(QtyToBill), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); - // InsertIntegrationFieldMapping( // IntegrationTableMappingName, // ServiceLine.FieldNo("Quantity Shipped"), @@ -863,27 +842,6 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Ship"), - FSWorkOrderService.FieldNo(Duration), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); - - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Consume"), - FSWorkOrderService.FieldNo(DurationToBill), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); - - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Invoice"), - FSWorkOrderService.FieldNo(DurationToBill), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); - // InsertIntegrationFieldMapping( // IntegrationTableMappingName, // ServiceLine.FieldNo("Quantity Shipped"), From 2da075df39ab762424b4e128c901fabd4a8a9be9 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Fri, 6 Sep 2024 09:49:44 +0200 Subject: [PATCH 13/49] added: coupled to field service flag --- .../Page Extensions/FSServiceLines.PageExt.al | 38 +++++++++++++++++++ .../FSServiceOrderSubform.PageExt.al | 38 +++++++++++++++++++ .../FSServiceOrderTypes.PageExt.al | 13 +++++++ .../FSServiceOrders.PageExt.al | 4 +- .../FSServiceHeader.TableExt.al | 4 +- .../FSServiceItemLine.TableExt.al | 22 +++++++++++ .../FSServiceLine.TableExt.al | 8 ++++ .../FSServiceOrderType.TableExt.al | 22 +++++++++++ 8 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceLines.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderSubform.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceLines.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceLines.PageExt.al new file mode 100644 index 0000000000..8043227e5a --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceLines.PageExt.al @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using Microsoft.Integration.Dataverse; + +pageextension 6628 "FS Service Lines" extends "Service Lines" +{ + layout + { + addlast(Control1) + { + field("Coupled to FS"; Rec."Coupled to FS") + { + ApplicationArea = All; + ToolTip = 'Specifies if the entity is coupled to an entity in Field Service.'; + Visible = FSIntegrationEnabled; + } + } + } + + var + FSIntegrationEnabled: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + end; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderSubform.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderSubform.PageExt.al new file mode 100644 index 0000000000..7165f0db58 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderSubform.PageExt.al @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using Microsoft.Integration.Dataverse; + +pageextension 6627 "FS Service Order Subform" extends "Service Order Subform" +{ + layout + { + addlast(Control1) + { + field("Coupled to FS"; Rec."Coupled to FS") + { + ApplicationArea = All; + ToolTip = 'Specifies if the entity is coupled to an entity in Field Service.'; + Visible = FSIntegrationEnabled; + } + } + } + + var + FSIntegrationEnabled: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + end; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al index 1bfc1729ef..8da55cc2ef 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al @@ -10,6 +10,19 @@ using Microsoft.Integration.Dataverse; pageextension 6625 "FS Service Order Types" extends "Service Order Types" { + layout + { + addlast(Control1) + { + field("Coupled to FS"; Rec."Coupled to FS") + { + ApplicationArea = All; + ToolTip = 'Specifies if the entity is coupled to an entity in Field Service.'; + Visible = FSIntegrationEnabled; + } + } + } + actions { addlast(navigation) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al index 01b85053c3..75d6df5e08 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrders.PageExt.al @@ -13,10 +13,10 @@ pageextension 6624 "FS Service Orders" extends "Service Orders" { addlast(Control1) { - field("Coupled to Dataverse"; Rec."Coupled to Dataverse") + field("Coupled to FS"; Rec."Coupled to FS") { ApplicationArea = All; - ToolTip = 'Specifies that the sales order is coupled to an order in Dynamics 365 Sales.'; + ToolTip = 'Specifies if the entity is coupled to an entity in Field Service.'; Visible = FSIntegrationEnabled; } } diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al index cfa25501b6..ec1d3a4a03 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceHeader.TableExt.al @@ -17,10 +17,10 @@ tableextension 6615 "FS Service Header" extends "Service Header" Caption = 'Work Description'; } - field(12001; "Coupled to Dataverse"; Boolean) + field(12001; "Coupled to FS"; Boolean) { FieldClass = FlowField; - Caption = 'Coupled to Dynamics 365 Sales'; + Caption = 'Coupled to Field Service'; Editable = false; CalcFormula = exist("CRM Integration Record" where("Integration ID" = field(SystemId), "Table ID" = const(Database::"Service Header"))); } diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al new file mode 100644 index 0000000000..6aaf1a050a --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Document; +using Microsoft.Integration.Dataverse; + +tableextension 6619 "FS Service Item Line" extends "Service Item Line" +{ + fields + { + field(12000; "Coupled to FS"; Boolean) + { + FieldClass = FlowField; + Caption = 'Coupled to Field Service'; + Editable = false; + CalcFormula = exist("CRM Integration Record" where("Integration ID" = field(SystemId), "Table ID" = const(Database::"Service Item Line"))); + } + } +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al index af2716dcc9..642805796c 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceLine.TableExt.al @@ -6,6 +6,7 @@ namespace Microsoft.Integration.DynamicsFieldService; using Microsoft.Service.Document; using Microsoft.Inventory.Item; +using Microsoft.Integration.Dataverse; tableextension 6616 "FS Service Line" extends "Service Line" { @@ -17,5 +18,12 @@ tableextension 6616 "FS Service Line" extends "Service Line" FieldClass = FlowField; CalcFormula = lookup(Item.Type where("No." = field("No."))); } + field(12001; "Coupled to FS"; Boolean) + { + FieldClass = FlowField; + Caption = 'Coupled to Field Service'; + Editable = false; + CalcFormula = exist("CRM Integration Record" where("Integration ID" = field(SystemId), "Table ID" = const(Database::"Service Line"))); + } } } diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al new file mode 100644 index 0000000000..d99ae0fd2e --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Service.Setup; +using Microsoft.Integration.Dataverse; + +tableextension 6618 "FS Service Order Type" extends "Service Order Type" +{ + fields + { + field(12000; "Coupled to FS"; Boolean) + { + FieldClass = FlowField; + Caption = 'Coupled to Field Service'; + Editable = false; + CalcFormula = exist("CRM Integration Record" where("Integration ID" = field(SystemId), "Table ID" = const(Database::"Service Order Type"))); + } + } +} From 7e759c2f91677f7e0421bc821c80000f0e897c70 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Fri, 6 Sep 2024 12:44:42 +0200 Subject: [PATCH 14/49] added: auto enable service order archive --- .../app/src/Codeunits/FSIntegrationMgt.Codeunit.al | 13 ++++++++++++- .../app/src/Codeunits/FSSetupDefaults.Codeunit.al | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al index 509afdda03..79b8b9f4fd 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al @@ -189,6 +189,18 @@ codeunit 6615 "FS Integration Mgt." exit(GuidVar); end; + internal procedure EnableServiceOrderArchive() + var + ServiceMgtSetup: Record "Service Mgt. Setup"; + begin + ServiceMgtSetup.Get(); + if ServiceMgtSetup."Archive Orders" then + exit; + + ServiceMgtSetup.Validate("Archive Orders", true); + ServiceMgtSetup.Modify(true); + end; + internal procedure TestManualServiceOrderNoSeriesFlag(IntegrationType: Enum "FS Integration Type") var ServiceMgtSetup: Record "Service Mgt. Setup"; @@ -293,7 +305,6 @@ codeunit 6615 "FS Integration Mgt." exit(FSBookingStatus.BookingStatusId); end; - [EventSubscriber(ObjectType::Table, Database::"Service Connection", 'OnRegisterServiceConnection', '', false, false)] local procedure RegisterFSConnectionOnRegisterServiceConnection(var ServiceConnection: Record "Service Connection") var diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 7a4d72061f..fb686402b2 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -66,6 +66,8 @@ codeunit 6611 "FS Setup Defaults" if IsNullGuid(FSConnectionSetup."Default Work Order Incident ID") then FSConnectionSetup."Default Work Order Incident ID" := FSIntegrationMgt.GenerateDefaultWorkOrderIncident(); + + FSIntegrationMgt.EnableServiceOrderArchive(); end; SetCustomIntegrationsTableMappings(FSConnectionSetup); From 032b1379f4403bffe51d5e5d039afed37dafdb73 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Mon, 9 Sep 2024 09:32:01 +0200 Subject: [PATCH 15/49] modified: editable of setup fields (wizard and setup card) --- .../app/src/Pages/FSConnectionSetup.Page.al | 16 ++++++++++++ .../src/Pages/FSConnectionSetupWizard.Page.al | 25 +++++++++++++------ .../app/src/Tables/FSConnectionSetup.Table.al | 8 ++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al index c9a6a8d12d..6a1f89da8b 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al @@ -114,12 +114,14 @@ page 6612 "FS Connection Setup" ApplicationArea = Suite; ShowMandatory = true; ToolTip = 'Specifies the project journal template in which project journal lines will be created and coupled to work order products and work order services.'; + Editable = EditableProjectSettings; } field("Job Journal Batch"; Rec."Job Journal Batch") { ApplicationArea = Suite; ShowMandatory = true; ToolTip = 'Specifies the project journal batch in which project journal lines will be created and coupled to work order products and work order services.'; + Editable = EditableProjectSettings; } field("Hour Unit of Measure"; Rec."Hour Unit of Measure") { @@ -136,6 +138,11 @@ page 6612 "FS Connection Setup" ApplicationArea = Service; Editable = not Rec."Is Enabled"; ToolTip = 'Specifies the type of integration between Business Central and Dynamics 365 Field Service.'; + + trigger OnValidate() + begin + UpdateIntegrationTypeEditable(); + end; } } } @@ -147,6 +154,7 @@ page 6612 "FS Connection Setup" { ApplicationArea = Suite; ToolTip = 'Specifies when to synchronize work order products and work order services.'; + Editable = EditableProjectSettings; trigger OnValidate() var @@ -177,6 +185,7 @@ page 6612 "FS Connection Setup" { ApplicationArea = Suite; ToolTip = 'Specifies when to post project journal lines that are coupled to work order products and work order services.'; + Editable = EditableProjectSettings; } } } @@ -448,6 +457,7 @@ page 6612 "FS Connection Setup" IsEditable: Boolean; IsCdsIntegrationEnabled: Boolean; CRMVersionStatus: Boolean; + EditableProjectSettings: Boolean; local procedure RefreshData() begin @@ -455,6 +465,7 @@ page 6612 "FS Connection Setup" RefreshSynchJobsData(); UpdateEnableFlags(); SetStyleExpr(); + UpdateIntegrationTypeEditable(); end; local procedure RefreshSynchJobsData() @@ -512,5 +523,10 @@ page 6612 "FS Connection Setup" begin Rec.Validate("Proxy Version", CRMIntegrationManagement.GetLastProxyVersionItem()); end; + + local procedure UpdateIntegrationTypeEditable() + begin + EditableProjectSettings := Rec."Integration Type" in [Rec."Integration Type"::Project, Rec."Integration Type"::Both]; + end; } diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al index 09e1ce8213..868fafd682 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al @@ -133,12 +133,14 @@ page 6613 "FS Connection Setup Wizard" ApplicationArea = Suite; ShowMandatory = true; ToolTip = 'Specifies the project journal template in which project journal lines will be created and coupled to work order products and work order services.'; + Editable = EditableProjectSettings; } field("Job Journal Batch"; Rec."Job Journal Batch") { ApplicationArea = Suite; ShowMandatory = true; ToolTip = 'Specifies the project journal batch in which project journal lines will be created and coupled to work order products and work order services.'; + Editable = EditableProjectSettings; } field("Hour Unit of Measure"; Rec."Hour Unit of Measure") { @@ -150,17 +152,23 @@ page 6613 "FS Connection Setup Wizard" { ApplicationArea = Suite; ToolTip = 'Specifies when to synchronize work order products and work order services.'; + Editable = EditableProjectSettings; } field("Line Post Rule"; Rec."Line Post Rule") { ApplicationArea = Suite; ToolTip = 'Specifies when to post project journal lines that are coupled to work order products and work order services.'; + Editable = EditableProjectSettings; } field("Integration Type"; Rec."Integration Type") { ApplicationArea = Service; ToolTip = 'Specifies the type of integration between Business Central and Dynamics 365 Field Service.'; - Editable = EnableFSIntegrationType; + + trigger OnValidate() + begin + UpdateIntegrationTypeEditable(); + end; } } group("Advanced Settings") @@ -365,7 +373,6 @@ page 6613 "FS Connection Setup Wizard" CredentialsStepVisible: Boolean; EnableFSConnection: Boolean; ImportSolution: Boolean; - EnableFSIntegrationType: Boolean; EnableFSConnectionEnabled: Boolean; ImportFSSolutionEnabled: Boolean; ShowAdvancedSettings: Boolean; @@ -375,6 +382,7 @@ page 6613 "FS Connection Setup Wizard" PasswordSet: Boolean; [NonDebuggable] Password: Text; + EditableProjectSettings: Boolean; ConnectionNotSetUpQst: Label 'The %1 connection has not been set up.\\Are you sure you want to exit?', Comment = '%1 = CRM product name'; CRMURLShouldNotBeEmptyErr: Label 'You must specify the URL of your %1 solution.', Comment = '%1 = CRM product name'; CRMSynchUserCredentialsNeededErr: Label 'You must specify the credentials for the user account for synchronization with %1.', Comment = '%1 = CRM product name'; @@ -461,7 +469,6 @@ page 6613 "FS Connection Setup Wizard" EnableFSConnectionEnabled := Rec."Server Address" <> ''; Rec."Authentication Type" := Rec."Authentication Type"::Office365; - EnableFSIntegrationType := true; if FSConnectionSetup.Get() then begin EnableFSConnection := true; @@ -469,16 +476,15 @@ page 6613 "FS Connection Setup Wizard" ImportSolution := true; if FSConnectionSetup."Is FS Solution Installed" then ImportFSSolutionEnabled := false; - if FSConnectionSetup."Is Enabled" then begin - Rec."Integration Type" := FSConnectionSetup."Integration Type"; - EnableFSIntegrationType := false; - end; + Rec."Integration Type" := FSConnectionSetup."Integration Type"; end else begin if ImportFSSolutionEnabled then ImportSolution := true; if EnableFSConnectionEnabled then EnableFSConnection := true; end; + + UpdateIntegrationTypeEditable(); end; local procedure FinalizeSetup(): Boolean @@ -526,5 +532,10 @@ page 6613 "FS Connection Setup Wizard" begin Rec.Validate("Proxy Version", CRMIntegrationManagement.GetLastProxyVersionItem()); end; + + local procedure UpdateIntegrationTypeEditable() + begin + EditableProjectSettings := Rec."Integration Type" in [Rec."Integration Type"::Project, Rec."Integration Type"::Both]; + end; } diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al index 4939b3043b..188c926999 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al @@ -985,6 +985,14 @@ table 6623 "FS Connection Setup" JobQueueEntry.SetStatus(NewStatus); until JobQueueEntry.Next() = 0; until IntegrationTableMapping.Next() = 0; + + JobQueueEntry.Reset(); + JobQueueEntry.SetRange("Object Type to Run"); + JobQueueEntry.SetRange("Object ID to Run", Codeunit::"FS Archived Service Orders Job"); + if JobQueueEntry.FindSet() then + repeat + JobQueueEntry.SetStatus(NewStatus); + until JobQueueEntry.Next() = 0; end; internal procedure GetConnectionStringAsStoredInSetup() ConnectionString: Text From c4bd1934e35376c2e561a5676e36b538fe07e713 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Tue, 10 Sep 2024 12:05:00 +0200 Subject: [PATCH 16/49] added: posted service invoice api --- Apps/W1/FieldServiceIntegration/app/app.json | 10 +- .../src/Pages/FSPostedServInvLinesAPI.Page.al | 180 ++++++++++ .../Pages/FSPostedServiceInvoiceAPI.Page.al | 311 ++++++++++++++++++ 3 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServInvLinesAPI.Page.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al diff --git a/Apps/W1/FieldServiceIntegration/app/app.json b/Apps/W1/FieldServiceIntegration/app/app.json index 7f7a93bc72..17e937c799 100644 --- a/Apps/W1/FieldServiceIntegration/app/app.json +++ b/Apps/W1/FieldServiceIntegration/app/app.json @@ -11,9 +11,15 @@ "contextSensitiveHelpUrl": "https://learn.microsoft.com/dynamics365/business-central/", "url": "https://go.microsoft.com/fwlink/?LinkId=724011", "logo": "ExtensionLogo.png", - "dependencies": [ + "dependencies": [ - ], + { + "id": "10cb69d9-bc8a-4d27-970a-9e110e9db2a5", + "name": "_Exclude_APIV2_", + "publisher": "Microsoft", + "version": "25.0.0.0" + } + ], "internalsVisibleTo": [ { "id": "41b3ab6e-3f20-47c7-a67f-feccc4d58a55", diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServInvLinesAPI.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServInvLinesAPI.Page.al new file mode 100644 index 0000000000..8546a8b3a2 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServInvLinesAPI.Page.al @@ -0,0 +1,180 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Finance.GeneralLedger.Account; +using Microsoft.Inventory.Item; +using Microsoft.Inventory.Location; +using Microsoft.Foundation.UOM; +using Microsoft.Service.History; +using Microsoft.API.V2; + +page 6617 "FS Posted Serv. Inv. Lines API" +{ + APIVersion = 'v2.0'; + EntityCaption = 'Posted Service Invoice Line'; + EntitySetCaption = 'Posted Service Invoice Lines'; + PageType = API; + ODataKeyFields = SystemId; + EntityName = 'postedServiceInvoiceLine'; + EntitySetName = 'postedServiceInvoiceLines'; + SourceTable = "Service Invoice Line"; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + Extensible = false; + + layout + { + area(content) + { + repeater(Group) + { + field(id; Rec.SystemId) + { + Caption = 'Id'; + Editable = false; + } + field(documentId; ServiceInvoiceHeader.SystemId) + { + Caption = 'Document Id'; + } + field(sequence; Rec."Line No.") + { + Caption = 'Sequence'; + } + field(itemId; Item.SystemId) + { + Caption = 'Item Id'; + } + field(accountId; GLAccount.SystemId) + { + Caption = 'Account Id'; + } + field(lineType; Rec.Type) + { + Caption = 'Line Type'; + } + field(lineObjectNumber; Rec."No.") + { + Caption = 'Line Object No.'; + } + field(description; Rec.Description) + { + Caption = 'Description'; + } + field(description2; Rec."Description 2") + { + Caption = 'Description 2'; + } + field(unitOfMeasureId; UnitOfMeasure.SystemId) + { + Caption = 'Unit Of Measure Id'; + } + field(unitOfMeasureCode; Rec."Unit of Measure Code") + { + Caption = 'Unit Of Measure Code'; + } + field(quantity; Rec.Quantity) + { + Caption = 'Quantity'; + } + field(unitPrice; Rec."Unit Price") + { + Caption = 'Unit Price'; + } + field(discountAmount; Rec."Line Discount Amount") + { + Caption = 'Discount Amount'; + } + field(discountPercent; Rec."Line Discount %") + { + Caption = 'Discount Percent'; + } + field(discountAppliedBeforeTax; Rec."Line Discount Amount") + { + Caption = 'Discount Applied Before Tax'; + Editable = false; + } + field(amountExcludingTax; Rec."Line Amount") + { + Caption = 'Amount Excluding Tax'; + Editable = false; + } + field(taxCode; Rec."VAT %") + { + Caption = 'Tax Code'; + } + field(taxPercent; Rec."VAT %") + { + Caption = 'Tax Percent'; + Editable = false; + } + field(amountIncludingTax; Rec."Amount Including VAT") + { + Caption = 'Amount Including Tax'; + Editable = false; + } + field(netAmount; Rec.Amount) + { + Caption = 'Net Amount'; + Editable = false; + } + field(netAmountIncludingTax; Rec."Amount Including VAT") + { + Caption = 'Net Amount Including Tax'; + Editable = false; + } + field(itemVariantId; ItemVariant.SystemId) + { + Caption = 'Item Variant Id'; + } + field(locationId; Location.SystemId) + { + Caption = 'Location Id'; + } + part(dimensionSetLines; "APIV2 - Dimension Set Lines") + { + Caption = 'Dimension Set Lines'; + EntityName = 'dimensionSetLine'; + EntitySetName = 'dimensionSetLines'; + SubPageLink = "Parent Id" = field(SystemId); + } + } + } + } + + trigger OnAfterGetRecord() + begin + if not ServiceInvoiceHeader.Get(Rec."Document No.") then + clear(ServiceInvoiceHeader); + + Clear(Item); + Clear(GLAccount); + case Rec.Type of + Rec.Type::Item: + if not Item.Get(Rec."No.") then + Clear(Item); + Rec.Type::"G/L Account": + if not GLAccount.Get(Rec."No.") then + Clear(GLAccount); + end; + + if not UnitOfMeasure.Get(Rec."Unit of Measure Code") then + Clear(UnitOfMeasure); + if not Location.Get(Rec."Location Code") then + Clear(Location); + if not ItemVariant.Get(Item."No.", Rec."Variant Code") then + Clear(ItemVariant); + end; + + var + ServiceInvoiceHeader: Record "Service Invoice Header"; + Item: Record Item; + GLAccount: Record "G/L Account"; + UnitOfMeasure: Record "Unit of Measure"; + ItemVariant: Record "Item Variant"; + Location: Record Location; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al new file mode 100644 index 0000000000..c1e541f857 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al @@ -0,0 +1,311 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.Integration.DynamicsFieldService; + +using Microsoft.Sales.Customer; +using Microsoft.Finance.Currency; +using Microsoft.Service.History; +using Microsoft.Foundation.PaymentTerms; +using Microsoft.Foundation.Shipping; +using Microsoft.Integration.Graph; +using Microsoft.Service.Document; +using Microsoft.API.V2; + +page 6614 "FS Posted Service Invoice API" +{ + APIVersion = 'v2.0'; + EntityCaption = 'Posted Service Invoice'; + EntitySetCaption = 'Posted Service Invoices'; + EntityName = 'postedServiceInvoice'; + EntitySetName = 'postedServiceInvoices'; + ODataKeyFields = SystemId; + PageType = API; + SourceTable = "Service Invoice Header"; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + Extensible = false; + + layout + { + area(content) + { + repeater(Group) + { + field(id; Rec.SystemId) + { + Caption = 'Id'; + } + field(number; Rec."No.") + { + Caption = 'No.'; + } + field(externalDocumentNumber; Rec."External Document No.") + { + Caption = 'External Document No.'; + } + field(invoiceDate; Rec."Document Date") + { + Caption = 'Invoice Date'; + } + field(postingDate; Rec."Posting Date") + { + Caption = 'Posting Date'; + } + field(dueDate; Rec."Due Date") + { + Caption = 'Due Date'; + } + field(customerPurchaseOrderReference; Rec."Your Reference") + { + Caption = 'Customer Purchase Order Reference',; + } + field(customerId; SellToCustomer.SystemId) + { + Caption = 'Customer Id'; + } + field(customerNumber; Rec."Customer No.") + { + Caption = 'Customer No.'; + } + field(customerName; Rec.Name) + { + Caption = 'Customer Name'; + Editable = false; + } + field(billToName; Rec."Bill-to Name") + { + Caption = 'Bill-To Name'; + Editable = false; + } + field(billToCustomerId; BillToCustomer.SystemId) + { + Caption = 'Bill-To Customer Id'; + } + field(billToCustomerNumber; Rec."Bill-to Customer No.") + { + Caption = 'Bill-To Customer No.'; + } + field(shipToName; Rec."Ship-to Name") + { + Caption = 'Ship-to Name'; + } + field(shipToContact; Rec."Ship-to Contact") + { + Caption = 'Ship-to Contact'; + } + field(sellToAddressLine1; Rec.Address) + { + Caption = 'Sell-to Address Line 1'; + } + field(sellToAddressLine2; Rec."Address 2") + { + Caption = 'Sell-to Address Line 2'; + } + field(sellToCity; Rec.City) + { + Caption = 'Sell-to City'; + } + field(sellToCountry; Rec."Country/Region Code") + { + Caption = 'Sell-to Country/Region Code'; + } + field(sellToState; Rec.County) + { + Caption = 'Sell-to State'; + } + field(sellToPostCode; Rec."Post Code") + { + Caption = 'Sell-to Post Code'; + } + field(billToAddressLine1; Rec."Bill-To Address") + { + Caption = 'Bill-to Address Line 1'; + Editable = false; + } + field(billToAddressLine2; Rec."Bill-To Address 2") + { + Caption = 'Bill-to Address Line 2'; + Editable = false; + } + field(billToCity; Rec."Bill-To City") + { + Caption = 'Bill-to City'; + Editable = false; + } + field(billToCountry; Rec."Bill-To Country/Region Code") + { + Caption = 'Bill-to Country/Region Code'; + Editable = false; + } + field(billToState; Rec."Bill-To County") + { + Caption = 'Bill-to State'; + Editable = false; + } + field(billToPostCode; Rec."Bill-To Post Code") + { + Caption = 'Bill-to Post Code'; + Editable = false; + } + field(shipToAddressLine1; Rec."Ship-to Address") + { + Caption = 'Ship-to Address Line 1'; + } + field(shipToAddressLine2; Rec."Ship-to Address 2") + { + Caption = 'Ship-to Address Line 2'; + } + field(shipToCity; Rec."Ship-to City") + { + Caption = 'Ship-to City'; + } + field(shipToCountry; Rec."Ship-to Country/Region Code") + { + Caption = 'Ship-to Country/Region Code'; + } + field(shipToState; Rec."Ship-to County") + { + Caption = 'Ship-to State'; + } + field(shipToPostCode; Rec."Ship-to Post Code") + { + Caption = 'Ship-to Post Code'; + } + field(currencyId; Currency.SystemId) + { + Caption = 'Currency Id'; + } + field(shortcutDimension1Code; Rec."Shortcut Dimension 1 Code") + { + Caption = 'Shortcut Dimension 1 Code'; + } + field(shortcutDimension2Code; Rec."Shortcut Dimension 2 Code") + { + Caption = 'Shortcut Dimension 2 Code'; + } + field(currencyCode; CurrencyCode) + { + Caption = 'Currency Code'; + } + field(orderId; ServiceOrder.SystemId) + { + Caption = 'Order Id'; + Editable = false; + } + field(orderNumber; Rec."Order No.") + { + Caption = 'Order No.'; + Editable = false; + } + field(paymentTermsId; PaymentTerms.SystemId) + { + Caption = 'Payment Terms Id'; + } + field(shipmentMethodId; ShipmentMethod.SystemId) + { + Caption = 'Shipment Method Id'; + } + field(salesperson; Rec."Salesperson Code") + { + Caption = 'Salesperson'; + } + field(pricesIncludeTax; Rec."Prices Including VAT") + { + Caption = 'Prices Include Tax'; + Editable = false; + } + part(dimensionSetLines; "APIV2 - Dimension Set Lines") + { + Caption = 'Dimension Set Lines'; + EntityName = 'dimensionSetLine'; + EntitySetName = 'dimensionSetLines'; + SubPageLink = "Parent Id" = field(SystemId); + } + part(postedServiceInvoiceLines; "FS Posted Serv. Inv. Lines API") + { + Caption = 'Lines'; + EntityName = 'postedServiceInvoiceLine'; + EntitySetName = 'postedServiceInvoiceLines'; + SubPageLink = "Document No." = field("No."); + } + part(pdfDocument; "APIV2 - PDF Document") + { + Caption = 'PDF Document'; + Multiplicity = ZeroOrOne; + EntityName = 'pdfDocument'; + EntitySetName = 'pdfDocument'; + SubPageLink = "Document Id" = field(SystemId); + } + field(totalAmountExcludingTax; Rec.Amount) + { + Caption = 'Total Amount Excluding Tax'; + Editable = false; + } + field(totalAmountIncludingTax; Rec."Amount Including VAT") + { + Caption = 'Total Amount Including Tax'; + Editable = false; + } + field(lastModifiedDateTime; Rec.SystemModifiedAt) + { + Caption = 'Last Modified Date'; + Editable = false; + } + field(phoneNumber; Rec."Phone No.") + { + Caption = 'Phone No.'; + } + field(email; Rec."E-Mail") + { + Caption = 'Email'; + } + part(attachments; "APIV2 - Attachments") + { + Caption = 'Attachments'; + EntityName = 'attachment'; + EntitySetName = 'attachments'; + SubPageLink = "Document Id" = field(SystemId); + } + part(documentAttachments; "APIV2 - Document Attachments") + { + Caption = 'Document Attachments'; + EntityName = 'documentAttachment'; + EntitySetName = 'documentAttachments'; + SubPageLink = "Document Id" = field(SystemId); + } + } + } + } + + trigger OnAfterGetRecord() + var + GraphMgtGeneralTools: Codeunit "Graph Mgt - General Tools"; + begin + if not SellToCustomer.Get(Rec."Customer No.") then + Clear(SellToCustomer); + if not BillToCustomer.Get(Rec."Bill-to Customer No.") then + Clear(BillToCustomer); + CurrencyCode := GraphMgtGeneralTools.TranslateNAVCurrencyCodeToCurrencyCode(CachedCurrencyCode, Rec."Currency Code"); + if not Currency.Get(CurrencyCode) then + Clear(Currency); + if not ServiceOrder.Get(ServiceOrder."Document Type"::Order, Rec."Order No.") then + Clear(ServiceOrder); + if not PaymentTerms.Get(Rec."Payment Terms Code") then + Clear(PaymentTerms); + if not ShipmentMethod.Get(Rec."Shipment Method Code") then + Clear(ShipmentMethod); + end; + + var + SellToCustomer: Record "Customer"; + BillToCustomer: Record "Customer"; + Currency: Record "Currency"; + ServiceOrder: Record "Service Header"; + PaymentTerms: Record "Payment Terms"; + ShipmentMethod: Record "Shipment Method"; + CurrencyCode: Code[10]; + CachedCurrencyCode: Code[10]; +} \ No newline at end of file From 29fca4eae054bce4eb97bf0b3af0962198763175 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 11 Sep 2024 07:35:54 +0200 Subject: [PATCH 17/49] added: bidirectional service item description sync --- .../app/src/Codeunits/FSSetupDefaults.Codeunit.al | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index fb686402b2..275ee4027d 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -669,6 +669,13 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderIncident.FieldNo(CustomerAsset), IntegrationFieldMapping.Direction::Bidirectional, '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceItemLine.FieldNo(Description), + FSWorkOrderIncident.FieldNo(Description), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); end; local procedure ResetServiceOrderLineItemMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) From 38d85438d142c4994d4d72c39de902a0ccfaeceb Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 11 Sep 2024 10:12:40 +0200 Subject: [PATCH 18/49] fixed: datetime to date conversation issue --- .../src/Codeunits/FSIntTableSubscriber.Codeunit.al | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 10333702ec..f3adefe721 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -320,6 +320,7 @@ codeunit 6610 "FS Int. Table Subscriber" var FSConnectionSetup: Record "FS Connection Setup"; CRMIntegrationRecord: Record "CRM Integration Record"; + FSWorkOrder: Record "FS Work Order"; FSWorkOrderService: Record "FS Work Order Service"; FSWorkOrderProduct: Record "FS Work Order Product"; FSBookableResourceBooking: Record "FS Bookable Resource Booking"; @@ -405,6 +406,18 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + if (SourceFieldRef.Record().Number = Database::"FS Work Order") then + case SourceFieldRef.Name() of + FSWorkOrder.FieldName(CreatedOn): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrder); + NewValue := DT2Date(FSWorkOrder.CreatedOn); + IsValueFound := true; + NeedsConversion := false; + end; + end; + if (SourceFieldRef.Record().Number = Database::"FS Work Order Service") then case SourceFieldRef.Name() of FSWorkOrderService.FieldName(EstimateDuration): From 208d73412db495c471db8937100eafab8dbc13e5 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 11 Sep 2024 10:15:14 +0200 Subject: [PATCH 19/49] added: quantity/duration shipped sync --- .../FSArchivedServiceOrdersJob.Codeunit.al | 18 ++++++------ .../FSIntTableSubscriber.Codeunit.al | 19 ++++++++++--- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 28 +++++++++++-------- .../src/Tables/FSWorkOrderProduct.Table.al | 7 +++++ .../src/Tables/FSWorkOrderService.Table.al | 7 +++++ 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al index 5810e2ba51..7a3d9a63ce 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al @@ -95,11 +95,10 @@ codeunit 6617 "FS Archived Service Orders Job" var Modified: Boolean; begin - // TODO! Add Quantity Shipped - // if FSWorkOrderProduct.QuantityShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin - // FSWorkOrderProduct.QuantityShipped := SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship"; - // Modified := true; - // end; + if FSWorkOrderProduct.QuantityShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin + FSWorkOrderProduct.QuantityShipped := SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship"; + Modified := true; + end; if FSWorkOrderProduct.QuantityInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") then begin FSWorkOrderProduct.QuantityInvoiced := SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship"; @@ -119,11 +118,10 @@ codeunit 6617 "FS Archived Service Orders Job" var Modified: Boolean; begin - // TODO! Add Quantity Shipped - // if FSWorkOrderService.QuantityShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin - // FSWorkOrderService.QuantityShipped := SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship"; - // Modified := true; - // end; + if FSWorkOrderService.DurationShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin + FSWorkOrderService.DurationShipped := (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") * 60; + Modified := true; + end; if FSWorkOrderService.DurationInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") then begin FSWorkOrderService.DurationInvoiced := (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") * 60; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index f3adefe721..d77f65f21b 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -347,6 +347,7 @@ codeunit 6610 "FS Int. Table Subscriber" if SourceFieldRef.Number() = DestinationFieldRef.Number() then if SourceFieldRef.Record().Number() = DestinationFieldRef.Record().Number() then exit; + if (SourceFieldRef.Record().Number = Database::"Service Line") and (DestinationFieldRef.Record().Number = Database::"FS Work Order Product") then case DestinationFieldRef.Name() of @@ -384,6 +385,16 @@ codeunit 6610 "FS Int. Table Subscriber" IsValueFound := true; NeedsConversion := false; end; + FSWorkOrderService.FieldName(DurationShipped): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(ServiceLine); + DurationInHours := ServiceLine."Quantity Shipped"; + DurationInMinutes := DurationInHours * 60; + NewValue := DurationInMinutes; + IsValueFound := true; + NeedsConversion := false; + end; FSWorkOrderService.FieldName(DurationInvoiced): begin SourceRecordRef := SourceFieldRef.Record(); @@ -553,15 +564,15 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") begin ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.EstimateQuantity, FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill)); - ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill)); - ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.QtyToBill); + ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill) - ServiceLine."Quantity Shipped"); + ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced"); end; local procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Service"; var ServiceLine: Record "Service Line") begin ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.EstimateDuration, FSWorkOrderProduct.Duration, FSWorkOrderProduct.DurationToBill) / 60); - ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Duration, FSWorkOrderProduct.DurationToBill) / 60); - ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.DurationToBill / 60); + ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Duration, FSWorkOrderProduct.DurationToBill) / 60 - ServiceLine."Quantity Shipped"); + ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.DurationToBill / 60 - ServiceLine."Quantity Invoiced"); end; procedure GetMaxQuantity(Quantity1: Decimal; Quantity2: Decimal): Decimal diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 275ee4027d..3b607f84b5 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -655,6 +655,7 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMapping.SetIntegrationTableFilter( GetTableFilterFromView(Database::"FS Work Order Incident", FSWorkOrderIncident.TableCaption(), FSWorkOrderIncident.GetView())); IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SVCITEM-CUSTASSET'; + IntegrationTableMapping."Update-Conflict Resolution" := IntegrationTableMapping."Update-Conflict Resolution"::"Get Update from Integration"; IntegrationTableMapping.Modify(); InsertIntegrationFieldMapping( @@ -714,6 +715,7 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMapping.SetIntegrationTableFilter( GetTableFilterFromView(Database::"FS Work Order Product", FSWorkOrderProduct.TableCaption(), FSWorkOrderProduct.GetView())); IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SRVORDERITEMLINE'; + IntegrationTableMapping."Update-Conflict Resolution" := IntegrationTableMapping."Update-Conflict Resolution"::"Get Update from Integration"; IntegrationTableMapping.Modify(); InsertIntegrationFieldMapping( @@ -757,12 +759,12 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - // InsertIntegrationFieldMapping( - // IntegrationTableMappingName, - // ServiceLine.FieldNo("Quantity Shipped"), - // FSWorkOrderProduct.FieldNo(QuantityInvoiced), // TODO! QuantityShipped - // IntegrationFieldMapping.Direction::ToIntegrationTable, - // '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Quantity Shipped"), + FSWorkOrderProduct.FieldNo(QuantityShipped), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); InsertIntegrationFieldMapping( IntegrationTableMappingName, @@ -815,6 +817,7 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMapping.SetIntegrationTableFilter( GetTableFilterFromView(Database::"FS Work Order Service", FSWorkOrderService.TableCaption(), FSWorkOrderService.GetView())); IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SRVORDERITEMLINE'; + IntegrationTableMapping."Update-Conflict Resolution" := IntegrationTableMapping."Update-Conflict Resolution"::"Get Update from Integration"; IntegrationTableMapping.Modify(); InsertIntegrationFieldMapping( @@ -851,12 +854,12 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - // InsertIntegrationFieldMapping( - // IntegrationTableMappingName, - // ServiceLine.FieldNo("Quantity Shipped"), - // FSWorkOrderProduct.FieldNo(QuantityInvoiced), // TODO! QuantityShipped - // IntegrationFieldMapping.Direction::ToIntegrationTable, - // '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Quantity Shipped"), + FSWorkOrderService.FieldNo(DurationShipped), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); InsertIntegrationFieldMapping( IntegrationTableMappingName, @@ -911,6 +914,7 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMapping.SetIntegrationTableFilter( GetTableFilterFromView(Database::"FS Bookable Resource Booking", FSBookableResourceBooking.TableCaption(), FSBookableResourceBooking.GetView())); IntegrationTableMapping."Dependency Filter" := 'SRVORDER|SRVORDERITEMLINE|RESOURCE-BOOKABLERSC'; + IntegrationTableMapping."Update-Conflict Resolution" := IntegrationTableMapping."Update-Conflict Resolution"::"Get Update from Integration"; IntegrationTableMapping.Modify(); InsertIntegrationFieldMapping( diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al index aff2eaf9fd..a1e7d93800 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al @@ -706,6 +706,13 @@ table 6619 "FS Work Order Product" Description = 'Quantity invoiced in Dynamics 365 Business Central. When this value is different than 0, you can no longer edit the work order product.'; Caption = 'Quantity Invoiced'; } + field(113; QuantityShipped; Decimal) + { + ExternalName = 'bcbi_quantityshipped'; + ExternalType = 'Float'; + Description = 'Quantity shipped in Dynamics 365 Business Central. When this value is different than 0, you can no longer edit the work order product.'; + Caption = 'Quantity Shipped'; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al index 4830d0383b..ddcb0ab713 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al @@ -747,6 +747,13 @@ table 6620 "FS Work Order Service" Description = 'Duration invoiced in Dynamics 365 Business Central. When this value is different than 0, you can no longer edit the work order service.'; Caption = 'Duration Invoiced'; } + field(114; DurationShipped; Integer) + { + ExternalName = 'bcbi_durationshipped'; + ExternalType = 'Integer'; + Description = 'Duration shipped in Dynamics 365 Business Central. When this value is different than 0, you can no longer edit the work order service.'; + Caption = 'Duration Shipped'; + } } keys { From fa4fe56e1238b2343783c1cdf8fb0e35354a158b Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 11 Sep 2024 11:20:27 +0200 Subject: [PATCH 20/49] fixed: sync of bookable resource bookings --- .../Codeunits/FSIntTableSubscriber.Codeunit.al | 18 ++++++++++++++++++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 10 ++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index d77f65f21b..0af5765fd7 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -284,6 +284,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSConnectionSetup: Record "FS Connection Setup"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; ServiceLine: Record "Service Line"; SourceDestCode: Text; begin @@ -310,6 +311,16 @@ codeunit 6610 "FS Int. Table Subscriber" UpdateQuantities(FSWorkOrderService, ServiceLine); + DestinationRecordRef.GetTable(ServiceLine); + end; + + 'FS Bookable Resource Booking-Service Line': + begin + SourceRecordRef.SetTable(FSBookableResourceBooking); + DestinationRecordRef.SetTable(ServiceLine); + + UpdateQuantities(FSBookableResourceBooking, ServiceLine); + DestinationRecordRef.GetTable(ServiceLine); end; end; @@ -575,6 +586,13 @@ codeunit 6610 "FS Int. Table Subscriber" ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.DurationToBill / 60 - ServiceLine."Quantity Invoiced"); end; + local procedure UpdateQuantities(FSBookableResourceBooking: Record "FS Bookable Resource Booking"; var ServiceLine: Record "Service Line") + begin + ServiceLine.Validate("Qty. to Consume", 0); + ServiceLine.Validate(Quantity, FSBookableResourceBooking.Duration / 60); + ServiceLine.Validate("Qty. to Consume", ServiceLine.Quantity - ServiceLine."Quantity Consumed"); + end; + procedure GetMaxQuantity(Quantity1: Decimal; Quantity2: Decimal): Decimal var MaxQuantity: Decimal; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 3b607f84b5..ae57a9a199 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -941,15 +941,9 @@ codeunit 6611 "FS Setup Defaults" IntegrationTableMappingName, ServiceLine.FieldNo(Quantity), FSBookableResourceBooking.FieldNo(Duration), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); + IntegrationFieldMapping.Direction::Bidirectional, + '', false, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Qty. to Consume"), - FSBookableResourceBooking.FieldNo(Duration), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); end; local procedure ShouldResetServiceItemMapping(): Boolean From 19619d937b6436a368911e4fc809372a59233d5a Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 11 Sep 2024 13:11:55 +0200 Subject: [PATCH 21/49] fixed: missing modification trigger --- .../app/src/Codeunits/FSIntTableSubscriber.Codeunit.al | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 0af5765fd7..d02013c894 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -279,7 +279,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterTransferRecordFields', '', false, false)] - local procedure OnAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) + local procedure OnAfterTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var AdditionalFieldsWereModified: Boolean) var FSConnectionSetup: Record "FS Connection Setup"; FSWorkOrderProduct: Record "FS Work Order Product"; @@ -300,6 +300,7 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(ServiceLine); UpdateQuantities(FSWorkOrderProduct, ServiceLine); + AdditionalFieldsWereModified := true; DestinationRecordRef.GetTable(ServiceLine); end; @@ -310,6 +311,7 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(ServiceLine); UpdateQuantities(FSWorkOrderService, ServiceLine); + AdditionalFieldsWereModified := true; DestinationRecordRef.GetTable(ServiceLine); end; @@ -320,6 +322,7 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(ServiceLine); UpdateQuantities(FSBookableResourceBooking, ServiceLine); + AdditionalFieldsWereModified := true; DestinationRecordRef.GetTable(ServiceLine); end; @@ -368,6 +371,7 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(FSWorkOrderProduct); // only update estimated quantity if the line status is estimated if FSWorkOrderProduct.LineStatus <> FSWorkOrderProduct.LineStatus::Estimated then begin + NewValue := FSWorkOrderProduct.EstimateQuantity; IsValueFound := true; NeedsConversion := false; end; @@ -383,6 +387,7 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(FSWorkOrderService); // only update estimated quantity if the line status is estimated if FSWorkOrderService.LineStatus <> FSWorkOrderService.LineStatus::Estimated then begin + NewValue := FSWorkOrderService.EstimateDuration; IsValueFound := true; NeedsConversion := false; exit; From 4c3cde73d27de2ebb5ce07efecdfe60392d37026 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 11 Sep 2024 14:01:29 +0200 Subject: [PATCH 22/49] fixed: sync of bookable resource bookings --- .../Codeunits/FSIntTableSubscriber.Codeunit.al | 16 ++-------------- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 8 -------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index d02013c894..0221176310 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -562,19 +562,6 @@ codeunit 6610 "FS Int. Table Subscriber" exit; end; end; - if (SourceFieldRef.Record().Number = Database::"FS Bookable Resource Booking") then - case SourceFieldRef.Name() of - FSBookableResourceBooking.FieldName(Duration): - begin - SourceRecordRef := SourceFieldRef.Record(); - SourceRecordRef.SetTable(FSBookableResourceBooking); - DurationInMinutes := FSBookableResourceBooking.Duration; - DurationInHours := (DurationInMinutes / 60); - NewValue := DurationInHours; - IsValueFound := true; - NeedsConversion := false; - end; - end; end; local procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") @@ -593,7 +580,8 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure UpdateQuantities(FSBookableResourceBooking: Record "FS Bookable Resource Booking"; var ServiceLine: Record "Service Line") begin - ServiceLine.Validate("Qty. to Consume", 0); + if ServiceLine."Qty. to Consume" <> 0 then + ServiceLine.Validate("Qty. to Consume", 0); ServiceLine.Validate(Quantity, FSBookableResourceBooking.Duration / 60); ServiceLine.Validate("Qty. to Consume", ServiceLine.Quantity - ServiceLine."Quantity Consumed"); end; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index ae57a9a199..fc7ab199e8 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -936,14 +936,6 @@ codeunit 6611 "FS Setup Defaults" FSBookableResourceBooking.FieldNo(Name), IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); - - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo(Quantity), - FSBookableResourceBooking.FieldNo(Duration), - IntegrationFieldMapping.Direction::Bidirectional, - '', false, false); - end; local procedure ShouldResetServiceItemMapping(): Boolean From 6930b16d57cb67960578c62fddc5535411432158 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Mon, 16 Sep 2024 16:30:38 +0200 Subject: [PATCH 23/49] fixed: object no conflicts --- .../app/src/Pages/FSPostedServiceInvoiceAPI.Page.al | 2 +- .../app/src/Table Extensions/FSIntegrationRecord.TableExt.al | 2 +- .../app/src/Table Extensions/FSServiceItemLine.TableExt.al | 2 +- .../app/src/Table Extensions/FSServiceOrderType.TableExt.al | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al index c1e541f857..8fa2842c85 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al @@ -13,7 +13,7 @@ using Microsoft.Integration.Graph; using Microsoft.Service.Document; using Microsoft.API.V2; -page 6614 "FS Posted Service Invoice API" +page 6618 "FS Posted Service Invoice API" { APIVersion = 'v2.0'; EntityCaption = 'Posted Service Invoice'; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al index db4c196cf2..1e695a00cf 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al @@ -6,7 +6,7 @@ namespace Microsoft.Integration.DynamicsFieldService; using Microsoft.Integration.Dataverse; -tableextension 6617 "FS Integration Record" extends "CRM Integration Record" +tableextension 6618 "FS Integration Record" extends "CRM Integration Record" { fields { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al index 6aaf1a050a..32173b2a02 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al @@ -7,7 +7,7 @@ namespace Microsoft.Integration.DynamicsFieldService; using Microsoft.Service.Document; using Microsoft.Integration.Dataverse; -tableextension 6619 "FS Service Item Line" extends "Service Item Line" +tableextension 6620 "FS Service Item Line" extends "Service Item Line" { fields { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al index d99ae0fd2e..471ae52c40 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceOrderType.TableExt.al @@ -7,7 +7,7 @@ namespace Microsoft.Integration.DynamicsFieldService; using Microsoft.Service.Setup; using Microsoft.Integration.Dataverse; -tableextension 6618 "FS Service Order Type" extends "Service Order Type" +tableextension 6619 "FS Service Order Type" extends "Service Order Type" { fields { From adaf4f2f1f569eec5169fb932ea1025d158173d3 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Tue, 17 Sep 2024 07:51:12 +0200 Subject: [PATCH 24/49] added: crm redirect event subscriber --- .../Codeunits/FSIntTableSubscriber.Codeunit.al | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index aaf573cb28..01aacb166b 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -2350,6 +2350,21 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnBeforeOpenRecordCardPage', '', false, false)] + local procedure OnBeforeOpenRecordCardPage(RecordID: RecordID; var IsHandled: Boolean) + var + ServiceHeader: Record "Service Header"; + RecordRef: RecordRef; + begin + RecordRef := RecordID.GetRecord(); + if RecordID.TableNo <> Database::"Service Header" then + exit; + + RecordRef.SetTable(ServiceHeader); + Page.Run(Page::"Service Order", ServiceHeader); + IsHandled := true; + end; + [IntegrationEvent(false, false)] local procedure OnSetUpNewLineOnNewLine(var JobJournalLine: Record "Job Journal Line"; var JobJournalTemplate: Record "Job Journal Template"; var JobJournalBatch: Record "Job Journal Batch"; var Handled: Boolean); begin From 87567020c803637da3627f081ce303833faf064f Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Tue, 17 Sep 2024 11:06:12 +0200 Subject: [PATCH 25/49] added: unit tests --- .../FSArchivedServiceOrdersJob.Codeunit.al | 12 +- .../FSIntTableSubscriber.Codeunit.al | 24 +- .../app/src/Tables/FSConnectionSetup.Table.al | 6 +- .../src/FSIntegrationTestLibrary.Codeunit.al | 86 ++ .../test/src/FSIntegrationTest.Codeunit.al | 990 ++++++++++++++++++ 5 files changed, 1098 insertions(+), 20 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al index 7a3d9a63ce..96703eec3e 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al @@ -91,7 +91,7 @@ codeunit 6617 "FS Archived Service Orders Job" until FSWorkOrderService.Next() = 0; end; - local procedure UpdateWorkOrderProduct(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderProduct: Record "FS Work Order Product") + internal procedure UpdateWorkOrderProduct(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderProduct: Record "FS Work Order Product") var Modified: Boolean; begin @@ -100,8 +100,8 @@ codeunit 6617 "FS Archived Service Orders Job" Modified := true; end; - if FSWorkOrderProduct.QuantityInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") then begin - FSWorkOrderProduct.QuantityInvoiced := SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship"; + if FSWorkOrderProduct.QuantityInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice") then begin + FSWorkOrderProduct.QuantityInvoiced := SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice"; Modified := true; end; @@ -114,7 +114,7 @@ codeunit 6617 "FS Archived Service Orders Job" FSWorkOrderProduct.Modify(); end; - local procedure UpdateWorkOrderService(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderService: Record "FS Work Order Service") + internal procedure UpdateWorkOrderService(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderService: Record "FS Work Order Service") var Modified: Boolean; begin @@ -123,8 +123,8 @@ codeunit 6617 "FS Archived Service Orders Job" Modified := true; end; - if FSWorkOrderService.DurationInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") then begin - FSWorkOrderService.DurationInvoiced := (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Ship") * 60; + if FSWorkOrderService.DurationInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice") then begin + FSWorkOrderService.DurationInvoiced := (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice") * 60; Modified := true; end; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 01aacb166b..ce3f79a907 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -565,21 +565,21 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - local procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") + internal procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") begin ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.EstimateQuantity, FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill)); ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill) - ServiceLine."Quantity Shipped"); ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced"); end; - local procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Service"; var ServiceLine: Record "Service Line") + internal procedure UpdateQuantities(FSWorkOrderService: Record "FS Work Order Service"; var ServiceLine: Record "Service Line") begin - ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.EstimateDuration, FSWorkOrderProduct.Duration, FSWorkOrderProduct.DurationToBill) / 60); - ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Duration, FSWorkOrderProduct.DurationToBill) / 60 - ServiceLine."Quantity Shipped"); - ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.DurationToBill / 60 - ServiceLine."Quantity Invoiced"); + ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderService.EstimateDuration, FSWorkOrderService.Duration, FSWorkOrderService.DurationToBill) / 60); + ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderService.Duration, FSWorkOrderService.DurationToBill) / 60 - ServiceLine."Quantity Shipped"); + ServiceLine.Validate("Qty. to Invoice", FSWorkOrderService.DurationToBill / 60 - ServiceLine."Quantity Invoiced"); end; - local procedure UpdateQuantities(FSBookableResourceBooking: Record "FS Bookable Resource Booking"; var ServiceLine: Record "Service Line") + internal procedure UpdateQuantities(FSBookableResourceBooking: Record "FS Bookable Resource Booking"; var ServiceLine: Record "Service Line") begin if ServiceLine."Qty. to Consume" <> 0 then ServiceLine.Validate("Qty. to Consume", 0); @@ -1177,7 +1177,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - local procedure ArchiveServiceOrder(ServiceHeader: Record "Service Header"; ArchivedServiceOrders: List of [Code[20]]) + internal procedure ArchiveServiceOrder(ServiceHeader: Record "Service Header"; ArchivedServiceOrders: List of [Code[20]]) var ServiceMgtSetup: Record "Service Mgt. Setup"; ServiceDocumentArchiveMgmt: Codeunit "Service Document Archive Mgmt."; @@ -2128,7 +2128,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - local procedure IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + internal procedure IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) var FSConnectionSetup: Record "FS Connection Setup"; FSWorkOrderProduct: Record "FS Work Order Product"; @@ -2173,7 +2173,7 @@ codeunit 6610 "FS Int. Table Subscriber" IgnoreRecord := true; end; - local procedure IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + internal procedure IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) var FSConnectionSetup: Record "FS Connection Setup"; ServiceHeader: Record "Service Header"; @@ -2193,7 +2193,7 @@ codeunit 6610 "FS Int. Table Subscriber" IgnoreRecord := true; end; - local procedure IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + internal procedure IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) var FSConnectionSetup: Record "FS Connection Setup"; FSWorkOrder: Record "FS Work Order"; @@ -2318,7 +2318,7 @@ codeunit 6610 "FS Int. Table Subscriber" MarkArchivedServiceOrderLine(ServiceLine, ServiceLineArchive); end; - procedure MarkArchivedServiceOrder(ServiceHeader: Record "Service Header") + internal procedure MarkArchivedServiceOrder(ServiceHeader: Record "Service Header") var FSConnectionSetup: Record "FS Connection Setup"; CRMIntegrationRecord: Record "CRM Integration Record"; @@ -2334,7 +2334,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - local procedure MarkArchivedServiceOrderLine(var ServiceLine: Record "Service Line"; var ServiceLineArchive: Record "Service Line Archive") + internal procedure MarkArchivedServiceOrderLine(var ServiceLine: Record "Service Line"; var ServiceLineArchive: Record "Service Line Archive") var FSConnectionSetup: Record "FS Connection Setup"; CRMIntegrationRecord: Record "CRM Integration Record"; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al index be1c6804c8..8e41e7e67a 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al @@ -95,8 +95,10 @@ table 6623 "FS Connection Setup" CRMConnectionSetupPage: Page "CRM Connection Setup"; begin if "Is Enabled" then begin - TestField("Job Journal Template"); - TestField("Job Journal Batch"); + if "Integration Type" <> "Integration Type"::Service then begin + TestField("Job Journal Template"); + TestField("Job Journal Batch"); + end; if not CRMConnectionSetup.IsEnabled() then Error(CRMConnSetupMustBeEnabledErr, CRMConnectionSetupPage.Caption()); if "Hour Unit of Measure" = '' then diff --git a/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al b/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al index dcc5054f25..abe02e449a 100644 --- a/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al @@ -5,6 +5,8 @@ namespace Microsoft.TestLibraries.DynamicsFieldService; using Microsoft.Integration.DynamicsFieldService; +using Microsoft.Service.Document; +using Microsoft.Service.Archive; codeunit 139205 "FS Integration Test Library" { @@ -27,4 +29,88 @@ codeunit 139205 "FS Integration Test Library" begin FSConnectionSetup.PerformTestConnection(); end; + + procedure ResetConfiguration(var FSConnectionSetup: Record "FS Connection Setup") + var + FSSetupDefaults: Codeunit "FS Setup Defaults"; + begin + FSSetupDefaults.ResetConfiguration(FSConnectionSetup); + end; + + procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.UpdateQuantities(FSWorkOrderProduct, ServiceLine); + end; + + procedure UpdateQuantities(FSWorkOrderService: Record "FS Work Order Service"; var ServiceLine: Record "Service Line") + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.UpdateQuantities(FSWorkOrderService, ServiceLine); + end; + + procedure UpdateQuantities(FSBookableResourceBooking: Record "FS Bookable Resource Booking"; var ServiceLine: Record "Service Line") + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.UpdateQuantities(FSBookableResourceBooking, ServiceLine); + end; + + procedure IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord); + end; + + procedure IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord); + end; + + procedure IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.IgnoreArchievedCRMWorkOrdersOnQueryPostFilterIgnoreRecord(SourceRecordRef, IgnoreRecord); + end; + + procedure MarkArchivedServiceOrder(ServiceHeader: Record "Service Header") + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.MarkArchivedServiceOrder(ServiceHeader); + end; + + procedure MarkArchivedServiceOrderLine(var ServiceLine: Record "Service Line"; var ServiceLineArchive: Record "Service Line Archive") + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.MarkArchivedServiceOrderLine(ServiceLine, ServiceLineArchive); + end; + + procedure ArchiveServiceOrder(ServiceHeader: Record "Service Header"; ArchivedServiceOrders: List of [Code[20]]) + var + FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; + begin + FSIntTableSubscriber.ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); + end; + + procedure UpdateWorkOrderProduct(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderProduct: Record "FS Work Order Product") + var + FSArchivedServiceOrdersJob: Codeunit "FS Archived Service Orders Job"; + begin + FSArchivedServiceOrdersJob.UpdateWorkOrderProduct(SalesLineArchive, FSWorkOrderProduct); + end; + + procedure UpdateWorkOrderService(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderService: Record "FS Work Order Service") + var + FSArchivedServiceOrdersJob: Codeunit "FS Archived Service Orders Job"; + begin + FSArchivedServiceOrdersJob.UpdateWorkOrderService(SalesLineArchive, FSWorkOrderService); + end; } \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al b/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al index 5d206f6fa7..9cf038226c 100644 --- a/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al @@ -20,8 +20,14 @@ using Microsoft.CRM.Contact; using Microsoft.Sales.Customer; using Microsoft.CRM.Team; using Microsoft.Purchases.Vendor; +using Microsoft.Service.Setup; +using Microsoft.Foundation.NoSeries; using System.Security.AccessControl; using Microsoft.TestLibraries.DynamicsFieldService; +using Microsoft.Service.Document; +using Microsoft.Service.Test; +using Microsoft.Service.Item; +using Microsoft.Service.Archive; codeunit 139204 "FS Integration Test" { @@ -40,6 +46,10 @@ codeunit 139204 "FS Integration Test" FSIntegrationTestLibrary: Codeunit "FS Integration Test Library"; Assert: Codeunit Assert; LibraryCRMIntegration: Codeunit "Library - CRM Integration"; + LibrarySales: Codeunit "Library - Sales"; + LibraryService: Codeunit "Library - Service"; + LibraryInventory: Codeunit "Library - Inventory"; + LibraryResource: Codeunit "Library - Resource"; ConnectionErr: Label 'The connection setup cannot be validated. Verify the settings and try again.'; ConnectionSuccessMsg: Label 'The connection test was successful'; JobQueueEntryStatusReadyErr: Label 'Job Queue Entry status should be Ready.'; @@ -119,6 +129,27 @@ codeunit 139204 "FS Integration Test" Assert.ExpectedError(FSConnectionSetup.FieldCaption("Job Journal Template")); end; + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure JournalTemplateNameNotRequiredToEnable() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + // [FEATURE] [UT] Service Order Integration + // [SCENARIO] Journal Template is not required to enable the Service integration type. + // [GIVEN] FS Connection Setup, where "Is Enabled" = No. + Initialize(); + InitSetup(false, ''); + + // [GIVEN] Setup without Job Journal Template and Integration Type = Service. + FSConnectionSetup."Integration Type" := FSConnectionSetup."Integration Type"::Service; + FSConnectionSetup.Modify(); + + // [THEN] Validate that the connection is enabled without error message (regarding job journal). + asserterror FSConnectionSetup.Validate("Is Enabled", true); + Assert.ExpectedError('You must enable the connection in page Dynamics 365 Sales Integration Setup'); + end; + [Test] [TransactionModel(TransactionModel::AutoRollback)] procedure JournalBatchRequiredToEnable() @@ -145,6 +176,34 @@ codeunit 139204 "FS Integration Test" JobJournalTemplate.Delete(); end; + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure JournalBatchNotRequiredToEnable() + var + FSConnectionSetup: Record "FS Connection Setup"; + JobJournalTemplate: Record "Job Journal Template"; + LibraryJob: Codeunit "Library - Job"; + begin + // [FEATURE] [UT] Service Order Integration + // [SCENARIO] Journal Batch is not required to enable the Service integration type. + // [GIVEN] FS Connection Setup, where "Is Enabled" = No. + Initialize(); + InitSetup(false, ''); + + // [GIVEN] Setup without Job Journal Batch and Integration Type = Service. + LibraryJob.CreateJobJournalTemplate(JobJournalTemplate); + JobJournalTemplate.Insert(); + FSConnectionSetup."Job Journal Template" := JobJournalTemplate.Name; + FSConnectionSetup."Integration Type" := FSConnectionSetup."Integration Type"::Service; + FSConnectionSetup.Modify(); + + // [THEN] Validate that the connection is enabled without error message (regarding job journal). + asserterror FSConnectionSetup.Validate("Is Enabled", true); + Assert.ExpectedError('You must enable the connection in page Dynamics 365 Sales Integration Setup'); + if JobJournalTemplate.Find() then + JobJournalTemplate.Delete(); + end; + [Test] [TransactionModel(TransactionModel::AutoRollback)] procedure HourUOMRequiredToEnable() @@ -177,6 +236,162 @@ codeunit 139204 "FS Integration Test" JobJournalTemplate.Delete(); end; + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ManualNoSeriesRequiredToSelectService() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + // [FEATURE] [UI] Service Order Integration. + // [SCENARIO] User selects "Service" in "Integration Type" field, but "Manual No. Series" is not selected. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] No Series of Service Order is not set to manual. + InitServiceManagementSetup(false, false, false); + + // [WHEN] Set Integration Type to "Service". + // [THEN] Error message "Manual No. Series is required for Service integration." appears. + FSConnectionSetup.Get(); + asserterror FSConnectionSetup.Validate("Integration Type", FSConnectionSetup."Integration Type"::Service); + Assert.ExpectedError('Please make sure that the No. Series setup is correct.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ManualNoSeriesNotRequiredToSelectProject() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + // [FEATURE] [UI] Service Order Integration. + // [SCENARIO] User selects "Project" in "Integration Type" field, but "Manual No. Series" is not selected. + Initialize(); + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + InitSetup(true, ''); + // [GIVEN] No Series of Service Order is not set to manual. + InitServiceManagementSetup(false, false, false); + + // [WHEN] Set Integration Type to "Project". + // [THEN] No error message appears. + FSConnectionSetup.Get(); + FSConnectionSetup.Validate("Integration Type", FSConnectionSetup."Integration Type"::Project); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ArchiveOfServiceOrdersIsAutomaticallyEnabled() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceMgtSetup: Record "Service Mgt. Setup"; + begin + // [FEATURE] [UI] Service Order Integration. + // [SCENARIO] Archive Orders should be enabled for Service integration type. + // [GIVEN] Disabled FS Connection Setup. + Initialize(); + + // [GIVEN] Service Managment Archive Flag is set to false. + InitServiceManagementSetup(false, false, false); + ServiceMgtSetup.Get(); + Assert.IsFalse(ServiceMgtSetup."Archive Orders", 'Archive Orders should be disabled.'); + + // [WHEN] Field Service Integration is enabled. + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := FSConnectionSetup."Integration Type"::Service; + FSConnectionSetup.Modify(false); + FSIntegrationTestLibrary.ResetConfiguration(FSConnectionSetup); + + // [THEN] Service Managment Archive Flag is set to true. + ServiceMgtSetup.Get(); + Assert.IsTrue(ServiceMgtSetup."Archive Orders", 'Archive Orders should be enabled.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ArchiveOfServiceOrdersIsNotAutomaticallyEnabled() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceMgtSetup: Record "Service Mgt. Setup"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Archive Orders should not be enabled for default integration type. + // [GIVEN] Disabled FS Connection Setup. + Initialize(); + + // [GIVEN] Service Managment Archive Flag is set to false. + InitServiceManagementSetup(false, false, false); + ServiceMgtSetup.Get(); + Assert.IsFalse(ServiceMgtSetup."Archive Orders", 'Archive Orders should be disabled.'); + + // [WHEN] Field Service Integration is enabled. + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSIntegrationTestLibrary.ResetConfiguration(FSConnectionSetup); + + // [THEN] Service Managment Archive Flag is set to false for default integration type. + ServiceMgtSetup.Get(); + Assert.IsFalse(ServiceMgtSetup."Archive Orders", 'Archive Orders should be enabled.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure OneServiceItemLinePerOrderIsDisabled() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceMgtSetup: Record "Service Mgt. Setup"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] One Service Item Line Per Order becomes enabled and this is not allowed. + // [GIVEN] Disabled FS Connection Setup. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := FSConnectionSetup."Integration Type"::Service; + FSConnectionSetup.Modify(false); + + // [GIVEN] Service Managment Flag is set to false. + InitServiceManagementSetup(false, false, false); + + // [WHEN] One Service Item Line Per Order becomes enabled. + // [THEN] Error message "One Service Item Line Per Order is not allowed for Field Service Integration." appears. + ServiceMgtSetup.Get(); + asserterror ServiceMgtSetup.Validate("One Service Item Line/Order", true); + Assert.ExpectedError(FSConnectionSetup.FieldCaption("Is Enabled")); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ServiceItemComponentsFilterMandatory() + var + FSConnectionSetup: Record "FS Connection Setup"; + IntegrationTableMapping: Record "Integration Table Mapping"; + ServiceItem: Record "Service Item"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Service Item Components Filter is mandatory for Service integration. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := FSConnectionSetup."Integration Type"::Service; + FSConnectionSetup.Modify(false); + FSIntegrationTestLibrary.ResetConfiguration(FSConnectionSetup); + + // [WHEN] Service Item Components filter is removed. + IntegrationTableMapping.SetRange("Table ID", Database::"Service Item"); + IntegrationTableMapping.FindFirst(); + + ServiceItem.SetView(IntegrationTableMapping.GetTableFilter()); + ServiceItem.SetRange("Service Item Components"); + IntegrationTableMapping.SetTableFilter(ServiceItem.GetView(false)); + IntegrationTableMapping.Modify(false); // force blob update without validation + + // [THEN] Error message "Service Item Components Filter is mandatory for Service integration." appears. + asserterror IntegrationTableMapping.Modify(true); + Assert.ExpectedError(ServiceItem.FieldCaption("Service Item Components")); + end; + [Test] [TransactionModel(TransactionModel::AutoRollback)] procedure WorkingConnectionRequiredToEnable() @@ -535,6 +750,750 @@ codeunit 139204 "FS Integration Test" CRMConnectionSetup.Delete(); end; + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderProductDefault() + var + WorkOrderProduct: Record "FS Work Order Product"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderProduct.EstimateQuantity := 3; + WorkOrderProduct.Quantity := 2; + WorkOrderProduct.QtyToBill := 1; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + + // [THEN] Quantities should be updated accordingly. + Assert.AreEqual(WorkOrderProduct.EstimateQuantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.EstimateQuantity)); + Assert.AreEqual(WorkOrderProduct.Quantity, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderProduct.Quantity)); + Assert.AreEqual(WorkOrderProduct.QtyToBill, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderProduct.QtyToBill)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderProductToShipHigherThanExpected() + var + WorkOrderProduct: Record "FS Work Order Product"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderProduct.EstimateQuantity := 3; + WorkOrderProduct.Quantity := 10; + WorkOrderProduct.QtyToBill := 1; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + + // [THEN] Quantities should be updated accordingly. Qty to Ship increases Quantity. + Assert.AreEqual(WorkOrderProduct.Quantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.Quantity)); + Assert.AreEqual(WorkOrderProduct.Quantity, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderProduct.Quantity)); + Assert.AreEqual(WorkOrderProduct.QtyToBill, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderProduct.QtyToBill)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderProductToInvoiceHigherThanExpected() + var + WorkOrderProduct: Record "FS Work Order Product"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderProduct.EstimateQuantity := 3; + WorkOrderProduct.Quantity := 2; + WorkOrderProduct.QtyToBill := 10; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + + // [THEN] Quantities should be updated accordingly. Qty to Invoice increases all other quantities. + Assert.AreEqual(WorkOrderProduct.QtyToBill, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.QtyToBill)); + Assert.AreEqual(WorkOrderProduct.QtyToBill, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderProduct.QtyToBill)); + Assert.AreEqual(WorkOrderProduct.QtyToBill, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderProduct.QtyToBill)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderProductAlreadyPosted() + var + WorkOrderProduct: Record "FS Work Order Product"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderProduct.EstimateQuantity := 5; + WorkOrderProduct.Quantity := 3; + WorkOrderProduct.QtyToBill := 2; + ServiceLine."Quantity Shipped" := 2; + ServiceLine."Quantity Invoiced" := 1; + + // [WHEN] Update quantities on work order lines that are partly posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + + // [THEN] Quantities should be updated accordingly. Posted quantities are considered. + Assert.AreEqual(WorkOrderProduct.EstimateQuantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.EstimateQuantity)); + Assert.AreEqual(WorkOrderProduct.Quantity - ServiceLine."Quantity Shipped", ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderProduct.Quantity - ServiceLine."Quantity Shipped")); + Assert.AreEqual(WorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced", ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced")); + end; + + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderServiceDefault() + var + WorkOrderService: Record "FS Work Order Service"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderService.EstimateDuration := 180; + WorkOrderService.Duration := 120; + WorkOrderService.DurationToBill := 60; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + + // [THEN] Quantities should be updated accordingly. + Assert.AreEqual(WorkOrderService.EstimateDuration / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.EstimateDuration / 60)); + Assert.AreEqual(WorkOrderService.Duration / 60, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderService.Duration / 60)); + Assert.AreEqual(WorkOrderService.DurationToBill / 60, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderService.DurationToBill / 60)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderServiceToShipHigherThanExpected() + var + WorkOrderService: Record "FS Work Order Service"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderService.EstimateDuration := 180; + WorkOrderService.Duration := 240; + WorkOrderService.DurationToBill := 60; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + + // [THEN] Quantities should be updated accordingly. Qty to Ship increases Quantity. + Assert.AreEqual(WorkOrderService.Duration / 60, ServiceLine.Quantity, 'Duration should be ' + Format(WorkOrderService.Duration / 60)); + Assert.AreEqual(WorkOrderService.Duration / 60, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderService.Duration / 60)); + Assert.AreEqual(WorkOrderService.DurationToBill / 60, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderService.DurationToBill / 60)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderServiceToInvoiceHigherThanExpected() + var + WorkOrderService: Record "FS Work Order Service"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderService.EstimateDuration := 180; + WorkOrderService.Duration := 120; + WorkOrderService.DurationToBill := 240; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + + // [THEN] Quantities should be updated accordingly. Qty to Invoice increases all other quantities. + Assert.AreEqual(WorkOrderService.DurationToBill / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.DurationToBill / 60)); + Assert.AreEqual(WorkOrderService.DurationToBill / 60, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderService.DurationToBill / 60)); + Assert.AreEqual(WorkOrderService.DurationToBill / 60, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderService.DurationToBill / 60)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderServiceAlreadyPosted() + var + WorkOrderService: Record "FS Work Order Service"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderService.EstimateDuration := 300; + WorkOrderService.Duration := 180; + WorkOrderService.DurationToBill := 120; + ServiceLine."Quantity Shipped" := 2; + ServiceLine."Quantity Invoiced" := 1; + + // [WHEN] Update quantities on work order lines that are partly posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + + // [THEN] Quantities should be updated accordingly. Posted Quantities are considered. + Assert.AreEqual(WorkOrderService.EstimateDuration / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.EstimateDuration / 60, ServiceLine.Quantity)); + Assert.AreEqual(WorkOrderService.Duration / 60 - ServiceLine."Quantity Shipped", ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderService.Duration / 60 - ServiceLine."Quantity Shipped")); + Assert.AreEqual(WorkOrderService.DurationToBill / 60 - ServiceLine."Quantity Invoiced", ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderService.DurationToBill / 60 - ServiceLine."Quantity Invoiced", ServiceLine."Qty. to Invoice")); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateFSBookableResourceBookingDefault() + var + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on booking lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on booking lines. + FSBookableResourceBooking.Duration := 120; + + // [WHEN] Update quantities on booking lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(FSBookableResourceBooking, ServiceLine); + + // [THEN] Quantities should be updated accordingly. + Assert.AreEqual(FSBookableResourceBooking.Duration / 60 - ServiceLine."Quantity Consumed", ServiceLine."Qty. to Consume", 'Qty. to Consume should be ' + Format(FSBookableResourceBooking.Duration / 60)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateFSBookableResourceBookingAlreadyExistingQuantities() + var + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on booking lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Resource, LibraryResource.CreateResourceNo()); + + // [GIVEN] Quantities on booking lines. + FSBookableResourceBooking.Duration := 180; + ServiceLine.Validate(Quantity, 5); + ServiceLine.Validate("Qty. to Consume", 3); + + // [WHEN] Update quantities on booking lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(FSBookableResourceBooking, ServiceLine); + + // [THEN] Quantities should be updated accordingly. Existing Quantities are reset. + Assert.AreEqual(FSBookableResourceBooking.Duration / 60 - ServiceLine."Quantity Consumed", ServiceLine."Qty. to Consume", 'Qty. to Consume should be ' + Format(FSBookableResourceBooking.Duration / 60 - ServiceLine."Quantity Consumed")); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure IgnorePostedJobJournalLinesInFilterForProductEstimatedQuantity() + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderProduct: Record "FS Work Order Product"; + RecordRef: RecordRef; + IgnoreRecord: Boolean; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User adds quantity with LineStatus=Estimated in FS. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Project; + FSConnectionSetup.Modify(false); + + // [GIVEN] Existing Work Order Line + FSWorkOrderProduct.LineStatus := FSWorkOrderProduct.LineStatus::Estimated; + FSWorkOrderProduct.EstimateQuantity := 5; + FSWorkOrderProduct.Quantity := 5; + + // [GIVEN] Existing Work Order Line as reference + RecordRef.GetTable(FSWorkOrderProduct); + + // [WHEN] Filter is build -> ignore estimated lines + FSIntegrationTestLibrary.IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(RecordRef, IgnoreRecord); + + // [THEN] Record should be ignored. + Assert.IsTrue(IgnoreRecord, 'Record should be ignored.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure IgnorePostedJobJournalLinesInFilterForServiceEstimatedQuantity() + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderService: Record "FS Work Order Service"; + RecordRef: RecordRef; + IgnoreRecord: Boolean; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User adds quantity with LineStatus=Estimated in FS. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Project; + FSConnectionSetup.Modify(false); + + // [GIVEN] Existing Work Order Line + FSWorkOrderService.LineStatus := FSWorkOrderService.LineStatus::Estimated; + FSWorkOrderService.EstimateDuration := 300; + FSWorkOrderService.Duration := 300; + + // [GIVEN] Existing Work Order Line as reference + RecordRef.GetTable(FSWorkOrderService); + + // [WHEN] Filter is build -> ignore estimated lines + FSIntegrationTestLibrary.IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(RecordRef, IgnoreRecord); + + // [THEN] Record should be ignored. + Assert.IsTrue(IgnoreRecord, 'Record should be ignored.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure IgnorePostedJobJournalLinesInFilterForProductUsedQuantity() + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderProduct: Record "FS Work Order Product"; + RecordRef: RecordRef; + IgnoreRecord: Boolean; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User adds quantity with LineStatus=Used in FS. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Project; + FSConnectionSetup.Modify(false); + + // [GIVEN] Existing Work Order Line + FSWorkOrderProduct.LineStatus := FSWorkOrderProduct.LineStatus::Used; + FSWorkOrderProduct.EstimateQuantity := 5; + FSWorkOrderProduct.Quantity := 5; + + // [GIVEN] Existing Work Order Line as reference + RecordRef.GetTable(FSWorkOrderProduct); + + // [WHEN] Filter is build + FSIntegrationTestLibrary.IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(RecordRef, IgnoreRecord); + + // [THEN] Record should not be ignored. + Assert.IsFalse(IgnoreRecord, 'Record should not be ignored.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure IgnorePostedJobJournalLinesInFilterForServiceUsedQuantity() + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderService: Record "FS Work Order Service"; + RecordRef: RecordRef; + IgnoreRecord: Boolean; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User adds quantity with LineStatus=Used in FS. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Project; + FSConnectionSetup.Modify(false); + + // [GIVEN] Existing Work Order Line + FSWorkOrderService.LineStatus := FSWorkOrderService.LineStatus::Used; + FSWorkOrderService.EstimateDuration := 300; + FSWorkOrderService.Duration := 300; + + // [GIVEN] Existing Work Order Line as reference + RecordRef.GetTable(FSWorkOrderService); + + // [WHEN] Filter is build -> consider used lines + FSIntegrationTestLibrary.IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(RecordRef, IgnoreRecord); + + // [THEN] Record should not be ignored. + Assert.IsFalse(IgnoreRecord, 'Record should not be ignored.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure IgnoreArchivedServiceOrdersInFilterNotArchivedYet() + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrder: Record "FS Work Order"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + CRMIntegrationRecord: Record "CRM Integration Record"; + RecordRef: RecordRef; + IgnoreRecord: Boolean; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Ignore Archived Service Orders in Filter. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Service; + FSConnectionSetup.Modify(false); + + // [GIVEN] Existing Service Header + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Existing Work Order Line as reference + FSWorkOrder.WorkOrderId := CreateGuid(); + CRMIntegrationRecord.CoupleCRMIDToRecordID(FSWorkOrder.WorkOrderId, ServiceHeader.RecordId()); + + // [GIVEN] Existing Work Order Line as reference + RecordRef.GetTable(ServiceHeader); + + // [WHEN] Filter is build -> consider used lines + FSIntegrationTestLibrary.IgnoreArchievedServiceOrdersOnQueryPostFilterIgnoreRecord(RecordRef, IgnoreRecord); + + // [THEN] Servie Order should be ignored + Assert.IsFalse(IgnoreRecord, 'Record should not be ignored.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure MarkArchivedServiceOrder() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Service Header becomes linked to archive. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Service; + FSConnectionSetup.Modify(false); + + // [GIVEN] Archive Service Orders is enabled + InitServiceManagementSetup(true, true, false); + + // [GIVEN] Existing Service Header + LibraryService.CreateServiceDocumentForCustomerNo(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + + // [GIVEN] Existing Work Order as reference. + CRMIntegrationRecord."Table ID" := Database::"Service Header"; + CRMIntegrationRecord."Integration ID" := ServiceHeader.SystemId; + CRMIntegrationRecord.Insert(false); + + // [WHEN] Marked as archived. + FSIntegrationTestLibrary.MarkArchivedServiceOrder(ServiceHeader); + + // [THEN] Integration Record should be marked as archived. + Clear(CRMIntegrationRecord); + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Header"); + CRMIntegrationRecord.SetRange("Integration ID", ServiceHeader.SystemId); + CRMIntegrationRecord.FindFirst(); + + Assert.IsTrue(CRMIntegrationRecord."Archived Service Order", 'Record should be marked as archived.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure MarkArchivedServiceOrderLine() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + ServiceLineArchive: Record "Service Line Archive"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Service Line becomes linked to archive. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Service; + FSConnectionSetup.Modify(false); + + // [GIVEN] Archive Service Orders is enabled + InitServiceManagementSetup(true, true, false); + + // [GIVEN] Existing Service Header + LibraryService.CreateServiceDocumentForCustomerNo(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + ServiceLine.SetRange("Document Type", ServiceHeader."Document Type"); + ServiceLine.SetRange("Document No.", ServiceHeader."No."); + ServiceLine.FindFirst(); + + // [GIVEN] Existing Work Order as reference. + CRMIntegrationRecord."Table ID" := Database::"Service Line"; + CRMIntegrationRecord."Integration ID" := ServiceLine.SystemId; + CRMIntegrationRecord.Insert(false); + + // [WHEN] Marked as archived. + ServiceLineArchive."Document Type" := ServiceLine."Document Type"; + ServiceLineArchive."Document No." := ServiceLine."Document No."; + ServiceLineArchive."Line No." := ServiceLine."Line No."; + ServiceLineArchive.Insert(false); + FSIntegrationTestLibrary.MarkArchivedServiceOrderLine(ServiceLine, ServiceLineArchive); + + // [THEN] Service Line and Service Line Archive should be linked. + Clear(CRMIntegrationRecord); + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Line"); + CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); + CRMIntegrationRecord.FindFirst(); + + Assert.AreEqual(CRMIntegrationRecord."Archived Service Line Id", ServiceLineArchive.SystemId, 'Archived Service Line Id should be ' + Format(ServiceLineArchive.SystemId)); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ArchiveServiceOrder() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; + ArchivedServiceOrders: List of [Code[20]]; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Service Header becomes archived. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Service; + FSConnectionSetup.Modify(false); + + // [GIVEN] Archive Service Orders is enabled + InitServiceManagementSetup(true, true, false); + + // [GIVEN] Existing Service Header + LibraryService.CreateServiceDocumentForCustomerNo(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + + // [WHEN] Archive Service Order. + FSIntegrationTestLibrary.ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); + + // [THEN] Version should increase + ServiceHeader.Get(ServiceHeader."Document Type"::Order, ServiceHeader."No."); + ServiceHeader.CalcFields("No. of Archived Versions"); + Assert.AreEqual(1, ServiceHeader."No. of Archived Versions", 'Record should be marked as archived.'); + end; + + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ArchiveServiceOrderWithDisabledSetupFlag() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; + ArchivedServiceOrders: List of [Code[20]]; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Service Header becomes archived but setup flag is disabled. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Service; + FSConnectionSetup.Modify(false); + + // [GIVEN] Archive Service Orders is disabled + InitServiceManagementSetup(true, false, false); + + // [GIVEN] Existing Service Header + LibraryService.CreateServiceDocumentForCustomerNo(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + + // [WHEN] Archive Service Order. + FSIntegrationTestLibrary.ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); + + // [THEN] Version should not increase + ServiceHeader.Get(ServiceHeader."Document Type"::Order, ServiceHeader."No."); + ServiceHeader.CalcFields("No. of Archived Versions"); + Assert.AreEqual(0, ServiceHeader."No. of Archived Versions", 'Record should not be marked as archived.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ArchiveServiceOrderMultiple() + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; + ArchivedServiceOrders: List of [Code[20]]; + I: Integer; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Service Header becomes archived multiple times. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + FSConnectionSetup.Get(); + FSConnectionSetup."Integration Type" := "FS Integration Type"::Service; + FSConnectionSetup.Modify(false); + + // [GIVEN] Archive Service Orders is enabled + InitServiceManagementSetup(true, true, false); + + // [GIVEN] Existing Service Header + LibraryService.CreateServiceDocumentForCustomerNo(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + + // [WHEN] Archive Service Order. + for I := 1 to 5 do + FSIntegrationTestLibrary.ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); + + // [THEN] Version should increase + ServiceHeader.Get(ServiceHeader."Document Type"::Order, ServiceHeader."No."); + ServiceHeader.CalcFields("No. of Archived Versions"); + Assert.AreEqual(1, ServiceHeader."No. of Archived Versions", 'Record should be marked as archived.'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderProduct() + var + ServiceLineArchive: Record "Service Line Archive"; + FSWorkOrderProduct: Record "FS Work Order Product"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Archive Service Orders transfer to FS. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line Archive. + ServiceLineArchive."Qty. to Ship" := 1; + ServiceLineArchive."Quantity Shipped" := 2; + ServiceLineArchive."Qty. to Invoice" := 3; + ServiceLineArchive."Quantity Invoiced" := 4; + ServiceLineArchive."Qty. to Consume" := 5; + ServiceLineArchive."Quantity Consumed" := 6; + + // [GIVEN] ExistingWork Order Product. + FSWorkOrderProduct.Insert(); + + // [WHEN] Update quantities on booking lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateWorkOrderProduct(ServiceLineArchive, FSWorkORderProduct); + + // [THEN] Quantities should be updated accordingly. + Assert.AreEqual(ServiceLineArchive."Qty. to Ship" + ServiceLineArchive."Quantity Shipped", FSWorkOrderProduct.QuantityShipped, 'Quantity should be ' + Format(ServiceLineArchive."Qty. to Ship" + ServiceLineArchive."Quantity Shipped")); + Assert.AreEqual(ServiceLineArchive."Qty. to Invoice" + ServiceLineArchive."Quantity Invoiced", FSWorkOrderProduct.QuantityInvoiced, 'Qty. to Invoice should be ' + Format(ServiceLineArchive."Qty. to Invoice" + ServiceLineArchive."Quantity Invoiced")); + Assert.AreEqual(ServiceLineArchive."Qty. to Consume" + ServiceLineArchive."Quantity Consumed", FSWorkOrderProduct.QuantityConsumed, 'Qty. to Consume should be ' + Format(ServiceLineArchive."Qty. to Consume" + ServiceLineArchive."Quantity Consumed")); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderService() + var + ServiceLineArchive: Record "Service Line Archive"; + FSWorkOrderService: Record "FS Work Order Service"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] Archive Service Orders transfer to FS. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line Archive. + ServiceLineArchive."Qty. to Ship" := 1; + ServiceLineArchive."Quantity Shipped" := 2; + ServiceLineArchive."Qty. to Invoice" := 3; + ServiceLineArchive."Quantity Invoiced" := 4; + ServiceLineArchive."Qty. to Consume" := 5; + ServiceLineArchive."Quantity Consumed" := 6; + + // [GIVEN] ExistingWork Order Service. + FSWorkOrderService.Insert(); + + // [WHEN] Update quantities on booking lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateWorkOrderService(ServiceLineArchive, FSWorkORderService); + + // [THEN] Quantities should be updated accordingly. + Assert.AreEqual((ServiceLineArchive."Qty. to Ship" + ServiceLineArchive."Quantity Shipped") * 60, FSWorkOrderService.DurationShipped, 'Duration should be ' + Format(ServiceLineArchive."Qty. to Ship" + ServiceLineArchive."Quantity Shipped")); + Assert.AreEqual((ServiceLineArchive."Qty. to Invoice" + ServiceLineArchive."Quantity Invoiced") * 60, FSWorkOrderService.DurationInvoiced, 'Duration Invoiced should be ' + Format(ServiceLineArchive."Qty. to Invoice" + ServiceLineArchive."Quantity Invoiced")); + Assert.AreEqual((ServiceLineArchive."Qty. to Consume" + ServiceLineArchive."Quantity Consumed") * 60, FSWorkOrderService.DurationConsumed, 'Duration Consumed should be ' + Format(ServiceLineArchive."Qty. to Consume" + ServiceLineArchive."Quantity Consumed")); + end; + local procedure Initialize() var AssistedSetupTestLibrary: Codeunit "Assisted Setup Test Library"; @@ -674,6 +1633,37 @@ codeunit 139204 "FS Integration Test" FSIntegrationTestLibrary.RegisterConnection(FSConnectionSetup); end; + procedure InitServiceManagementSetup(ManualNoSeries: Boolean; ArchiveOrdersEnabled: Boolean; OneServiceItemLinePerOrder: Boolean) + var + NoSeries: Record "No. Series"; + NoSeriesLine: Record "No. Series Line"; + + ServiceMgtSetup: Record "Service Mgt. Setup"; + NewNoSeries: Code[20]; + begin + NewNoSeries := 'ServiceOrder'; + + // create new No. Series + NoSeries.Code := NewNoSeries; + NoSeries."Manual Nos." := ManualNoSeries; + NoSeries."Default Nos." := true; + NoSeries.Insert(true); + + // create new No. Series Line + NoSeriesLine."Series Code" := NoSeries.Code; + NoSeriesLine."Starting Date" := 20100101D; + NoSeriesLine."Starting No." := '00001'; + NoSeriesLine."Increment-by No." := 1; + NoSeriesLine.Insert(true); + + // update ServiceMgtSetup record + ServiceMgtSetup.Get(); + ServiceMgtSetup."Service Order Nos." := NewNoSeries; + ServiceMgtSetup."Archive Orders" := ArchiveOrdersEnabled; + ServiceMgtSetup."One Service Item Line/Order" := OneServiceItemLinePerOrder; + ServiceMgtSetup.Modify(true); + end; + local procedure InsertJobQueueEntries() var JobQueueEntry: Record "Job Queue Entry"; From 6b2aacd705abccd965af2f3268a21ce7e56ceb83 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 12 Sep 2024 16:38:33 +0200 Subject: [PATCH 26/49] added: only sync work order product/service if IntegrateToService=false --- .../src/Codeunits/FSIntTableSubscriber.Codeunit.al | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index ce3f79a907..d31575a3f3 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -2131,13 +2131,14 @@ codeunit 6610 "FS Int. Table Subscriber" internal procedure IgnorePostedJobJournalLinesOnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) var FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrder: Record "FS Work Order"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; - FieldServiceId: Guid; QuantityCurrentlyConsumed: Decimal; QuantityCurrentlyInvoiced: Decimal; FSQuantityToConsume: Decimal; FSQuantityToInvoice: Decimal; + IntegrateToService: Boolean; begin if not FSConnectionSetup.IsIntegrationTypeProjectEnabled() then exit; @@ -2150,25 +2151,30 @@ codeunit 6610 "FS Int. Table Subscriber" Database::"FS Work Order Product": begin SourceRecordRef.SetTable(FSWorkOrderProduct); - FieldServiceId := FSWorkOrderProduct.WorkOrderProductId; if FSWorkOrderProduct.LineStatus = FSWorkOrderProduct.LineStatus::Used then begin FSQuantityToConsume := FSWorkOrderProduct.Quantity; FSQuantityToInvoice := FSWorkOrderProduct.QtyToBill; end; + if FSWorkOrder.Get(FSWorkOrderProduct.WorkOrder) then + IntegrateToService := FSWorkOrder.IntegrateToService; end; Database::"FS Work Order Service": begin SourceRecordRef.SetTable(FSWorkOrderService); - FieldServiceId := FSWorkOrderService.WorkOrderServiceId; if FSWorkOrderService.LineStatus = FSWorkOrderService.LineStatus::Used then begin FSQuantityToConsume := FSWorkOrderService.Duration / 60; FSQuantityToInvoice := FSWorkOrderService.DurationToBill / 60; end; + if FSWorkOrder.Get(FSWorkOrderService.WorkOrder) then + IntegrateToService := FSWorkOrder.IntegrateToService; end; end; SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + if IntegrateToService then + IgnoreRecord := true; + if (QuantityCurrentlyConsumed = FSQuantityToConsume) and (QuantityCurrentlyInvoiced = FSQuantityToInvoice) then IgnoreRecord := true; end; From bc8ad1e20893bd60b46b2f564311149367e95fd1 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 26 Sep 2024 09:05:36 +0200 Subject: [PATCH 27/49] added: company id --- .../FSIntTableSubscriber.Codeunit.al | 12 +++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 80 ++++++++++++++++++- .../Tables/FSBookableResourceBooking.Table.al | 16 ++++ .../src/Tables/FSWorkOrderIncident.Table.al | 9 +++ .../src/Tables/FSWorkOrderProduct.Table.al | 22 +++++ .../src/Tables/FSWorkOrderService.Table.al | 22 +++++ .../app/src/Tables/FSWorkOrderType.Table.al | 9 +++ 7 files changed, 166 insertions(+), 4 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index d31575a3f3..fc4824bfd4 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -1444,6 +1444,8 @@ codeunit 6610 "FS Int. Table Subscriber" SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); case SourceDestCode of + 'Location-FS Warehouse': + SetCompanyId(DestinationRecordRef); 'Service Item-FS Customer Asset': SetCompanyId(DestinationRecordRef); 'FS Customer Asset-Service Item': @@ -1552,8 +1554,14 @@ codeunit 6610 "FS Int. Table Subscriber" end; DestinationRecordRef.GetTable(JobJournalLine); end; + 'Service Order Type-FS Work Order Type': + SetCompanyId(DestinationRecordRef); + 'Service Header-FS Work Order': + SetCompanyId(DestinationRecordRef); 'Service Item Line-FS Work Order Incident': begin + SetCompanyId(DestinationRecordRef); + SourceRecordRef.SetTable(ServiceItemLine); DestinationRecordRef.SetTable(FSWorkOrderIncident); if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceItemLine."Document No."), WorkOrderId) then @@ -1563,6 +1571,8 @@ codeunit 6610 "FS Int. Table Subscriber" end; 'Service Line-FS Work Order Product': begin + SetCompanyId(DestinationRecordRef); + SourceRecordRef.SetTable(ServiceLine); DestinationRecordRef.SetTable(FSWorkOrderProduct); if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceLine."Document No."), WorkOrderId) then @@ -1573,6 +1583,8 @@ codeunit 6610 "FS Int. Table Subscriber" end; 'Service Line-FS Work Order Service': begin + SetCompanyId(DestinationRecordRef); + SourceRecordRef.SetTable(ServiceLine); DestinationRecordRef.SetTable(FSWorkOrderService); if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceLine."Document No."), WorkOrderId) then diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 4179f2dbba..1077b6a622 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -436,6 +436,8 @@ codeunit 6611 "FS Setup Defaults" InventorySetup: Record "Inventory Setup"; Location: Record Location; FSWarehouse: Record "FS Warehouse"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; begin if not SkipLocationMandatoryCheck then if InventorySetup.Get() and (not InventorySetup."Location Mandatory") then @@ -449,6 +451,9 @@ codeunit 6611 "FS Setup Defaults" Format(Location."Asm. Consump. Whse. Handling"::"Warehouse Pick (optional)") + '''|''' + Format(Location."Asm. Consump. Whse. Handling"::"Inventory Movement") + ''''); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWarehouse.SetRange(CompanyId, CDSCompany.CompanyId); + InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, Database::Location, Database::"FS Warehouse", @@ -489,6 +494,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceOrderType: Record "Service Order Type"; FSWorkOrderType: Record "FS Work Order Type"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; IsHandled: Boolean; begin IsHandled := false; @@ -503,6 +510,9 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderType.Reset(); FSWorkOrderType.SetRange(StateCode, FSWorkOrderType.StateCode::Active); FSWorkOrderType.SetRange(IntegrateToService, true); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWorkOrderType.SetRange(CompanyId, CDSCompany.CompanyId); + InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, Database::"Service Order Type", Database::"FS Work Order Type", @@ -543,6 +553,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceOrder: Record "Service Header"; FSWorkOrder: Record "FS Work Order"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; CRMSetupDefaults: Codeunit "CRM Setup Defaults"; ArchivedServiceOrdersSynchJobDescTxt: Label 'Archived Service Orders - %1 synchronization job', Comment = '%1 = CRM product name'; IsHandled: Boolean; @@ -560,6 +572,8 @@ codeunit 6611 "FS Setup Defaults" ServiceOrder.SetRange("Document Type", ServiceOrder."Document Type"::Order); FSWorkOrder.Reset(); FSWorkOrder.SetRange(IntegrateToService, true); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWorkOrder.SetRange(CompanyId, CDSCompany.CompanyId); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, @@ -632,6 +646,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceItemLine: Record "Service Item Line"; FSWorkOrderIncident: Record "FS Work Order Incident"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; IsHandled: Boolean; begin IsHandled := false; @@ -645,13 +661,16 @@ codeunit 6611 "FS Setup Defaults" ServiceItemLine.Reset(); ServiceItemLine.SetRange("Document Type", ServiceItemLine."Document Type"::Order); + FSWorkOrderIncident.Reset(); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWorkOrderIncident.SetRange(CompanyId, CDSCompany.CompanyId); InsertIntegrationTableMapping( - IntegrationTableMapping, IntegrationTableMappingName, - Database::"Service Item Line", Database::"FS Work Order Incident", - FSWorkOrderIncident.FieldNo(WorkOrderIncidentId), FSWorkOrderIncident.FieldNo(ModifiedOn), - '', '', false); + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Item Line", Database::"FS Work Order Incident", + FSWorkOrderIncident.FieldNo(WorkOrderIncidentId), FSWorkOrderIncident.FieldNo(ModifiedOn), + '', '', false); IntegrationTableMapping.SetTableFilter( GetTableFilterFromView(Database::"Service Item Line", ServiceItemLine.TableCaption(), ServiceItemLine.GetView())); @@ -688,6 +707,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceLine: Record "Service Line"; FSWorkOrderProduct: Record "FS Work Order Product"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; EmptyGuid: Guid; IsHandled: Boolean; begin @@ -706,6 +727,8 @@ codeunit 6611 "FS Setup Defaults" ServiceLine.SetFilter("Item Type", '%1|%2', ServiceLine."Item Type"::Inventory, ServiceLine."Item Type"::"Non-Inventory"); FSWorkOrderProduct.Reset(); FSWorkOrderProduct.SetFilter(WorkOrderIncident, '<>%1', EmptyGuid); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWorkOrderProduct.SetRange(CompanyId, CDSCompany.CompanyId); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, @@ -734,6 +757,13 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("No."), + FSWorkOrderProduct.FieldNo(ProductId), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo(Description), @@ -748,6 +778,13 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Location Code"), + FSWorkOrderProduct.FieldNo(LocationCode), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo("Unit of Measure Code"), @@ -782,6 +819,12 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderProduct.FieldNo(QuantityConsumed), IntegrationFieldMapping.Direction::ToIntegrationTable, '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + 0, + FSWorkOrderProduct.FieldNo(IntegrateToService), + IntegrationFieldMapping.Direction::Bidirectional, + 'true', true, false); end; local procedure ResetServiceOrderLineServiceItemMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) @@ -790,6 +833,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceLine: Record "Service Line"; FSWorkOrderService: Record "FS Work Order Service"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; EmptyGuid: Guid; IsHandled: Boolean; begin @@ -808,6 +853,8 @@ codeunit 6611 "FS Setup Defaults" ServiceLine.SetRange("Item Type", ServiceLine."Item Type"::Service); FSWorkOrderService.Reset(); FSWorkOrderService.SetFilter(WorkOrderIncident, '<>%1', EmptyGuid); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWorkOrderService.SetRange(CompanyId, CDSCompany.CompanyId); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, @@ -836,6 +883,13 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("No."), + FSWorkOrderService.FieldNo(ProductId), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo(Description), @@ -877,6 +931,13 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderService.FieldNo(DurationConsumed), IntegrationFieldMapping.Direction::ToIntegrationTable, '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + 0, + FSWorkOrderService.FieldNo(IntegrateToService), + IntegrationFieldMapping.Direction::Bidirectional, + 'true', true, false); end; local procedure ResetServiceOrderLineResourceMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) @@ -885,6 +946,8 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping: Record "Integration Field Mapping"; ServiceLine: Record "Service Line"; FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; FSIntegrationMgt: Codeunit "FS Integration Mgt."; IsHandled: Boolean; EmptyGuid: Guid; @@ -905,6 +968,8 @@ codeunit 6611 "FS Setup Defaults" FSBookableResourceBooking.Reset(); FSBookableResourceBooking.SetFilter(WorkOrder, '<>%1', EmptyGuid); FSBookableResourceBooking.SetRange(BookingStatus, FSIntegrationMgt.GetBookingStatusCompleted()); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSBookableResourceBooking.SetRange(CompanyId, CDSCompany.CompanyId); InsertIntegrationTableMapping( IntegrationTableMapping, IntegrationTableMappingName, @@ -939,6 +1004,13 @@ codeunit 6611 "FS Setup Defaults" FSBookableResourceBooking.FieldNo(Name), IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + 0, + FSBookableResourceBooking.FieldNo(IntegrateToService), + IntegrationFieldMapping.Direction::Bidirectional, + 'true', true, false); end; local procedure ShouldResetServiceItemMapping(): Boolean diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al index b4cef60fce..3419b5c75b 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al @@ -626,6 +626,22 @@ table 6611 "FS Bookable Resource Booking" TableRelation = "FS Booking Status".BookingStatusId; DataClassification = SystemMetadata; } + field(120; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + field(121; IntegrateToService; Boolean) + { + ExternalName = 'bcbi_integratetoervice'; + ExternalType = 'Boolean'; + Caption = 'Integrate to Service'; + DataClassification = SystemMetadata; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al index bc3b41e851..59d73fc2cd 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al @@ -300,6 +300,15 @@ table 6618 "FS Work Order Incident" TableRelation = "FS Incident Type".IncidentTypeId; DataClassification = SystemMetadata; } + field(70; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al index a1e7d93800..bec6a653a5 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al @@ -713,6 +713,28 @@ table 6619 "FS Work Order Product" Description = 'Quantity shipped in Dynamics 365 Business Central. When this value is different than 0, you can no longer edit the work order product.'; Caption = 'Quantity Shipped'; } + field(120; IntegrateToService; Boolean) + { + ExternalName = 'bcbi_integratetoervice'; + ExternalType = 'Boolean'; + Caption = 'Integrate to Service'; + DataClassification = SystemMetadata; + } + field(121; LocationCode; Code[10]) + { + ExternalName = 'bcbi_locationcode'; + ExternalType = 'String'; + Description = 'Unique identifier of the warehouse associated with the entity.'; + Caption = 'Location Code'; + ExternalAccess = Read; + } + field(122; ProductId; Text[1024]) + { + ExternalName = 'bcbi_productid'; + ExternalType = 'String'; + Description = 'Unique identifier of the warehouse associated with the entity.'; + Caption = 'Location Code'; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al index ddcb0ab713..9cc495e0cb 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al @@ -754,6 +754,28 @@ table 6620 "FS Work Order Service" Description = 'Duration shipped in Dynamics 365 Business Central. When this value is different than 0, you can no longer edit the work order service.'; Caption = 'Duration Shipped'; } + field(120; IntegrateToService; Boolean) + { + ExternalName = 'bcbi_integratetoervice'; + ExternalType = 'Boolean'; + Caption = 'Integrate to Service'; + DataClassification = SystemMetadata; + } + field(121; LocationCode; Code[10]) + { + ExternalName = 'bcbi_locationcode'; + ExternalType = 'String'; + Description = 'Unique identifier of the warehouse associated with the entity.'; + Caption = 'Location Code'; + ExternalAccess = Read; + } + field(122; ProductId; Text[1024]) + { + ExternalName = 'bcbi_productid'; + ExternalType = 'String'; + Description = 'Unique identifier of the warehouse associated with the entity.'; + Caption = 'Location Code'; + } } keys { diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al index 91b6974075..34276899dc 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al @@ -235,6 +235,15 @@ table 6622 "FS Work Order Type" Caption = 'Integrate To Service'; DataClassification = SystemMetadata; } + field(50; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } } keys { From 8e32a89cf72545ca586b9a6ea165798ec4aa996c Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Tue, 1 Oct 2024 08:01:29 +0200 Subject: [PATCH 28/49] fixed: typo --- .../FSArchivedServiceOrdersJob.Codeunit.al | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al index 96703eec3e..8011ade641 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al @@ -49,7 +49,7 @@ codeunit 6617 "FS Archived Service Orders Job" if CRMIntegrationRecord.FindSet() then repeat if FSWorkOrder.Get(CRMIntegrationRecord."CRM ID") then - if UpdateFromSalesHeader(FSWorkOrder) then begin + if UpdateFromServiceHeader(FSWorkOrder) then begin CRMIntegrationRecord2.GetBySystemId(CRMIntegrationRecord.SystemId); CRMIntegrationRecord2."Archived Service Order Updated" := true; CRMIntegrationRecord2.Modify(); @@ -62,14 +62,14 @@ codeunit 6617 "FS Archived Service Orders Job" end; [TryFunction] - local procedure UpdateFromSalesHeader(var FSWorkOrder: Record "FS Work Order") + local procedure UpdateFromServiceHeader(var FSWorkOrder: Record "FS Work Order") begin ResetFSWorkOrderLineFromServiceOrderLine(FSWorkOrder); end; local procedure ResetFSWorkOrderLineFromServiceOrderLine(var FSWorkOrder: Record "FS Work Order") var - SalesLineArchive: Record "Service Line Archive"; + ServiceLineArchive: Record "Service Line Archive"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; CRMIntegrationRecord: Record "CRM Integration Record"; @@ -78,35 +78,35 @@ codeunit 6617 "FS Archived Service Orders Job" if FSWorkOrderProduct.FindSet() then repeat if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrderProductId) then - if SalesLineArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Line Id") then - UpdateWorkOrderProduct(SalesLineArchive, FSWorkOrderProduct); + if ServiceLineArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Line Id") then + UpdateWorkOrderProduct(ServiceLineArchive, FSWorkOrderProduct); until FSWorkOrderProduct.Next() = 0; FSWorkOrderService.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); if FSWorkOrderService.FindSet() then repeat if CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.WorkOrderServiceId) then - if SalesLineArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Line Id") then - UpdateWorkOrderService(SalesLineArchive, FSWorkOrderService); + if ServiceLineArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Line Id") then + UpdateWorkOrderService(ServiceLineArchive, FSWorkOrderService); until FSWorkOrderService.Next() = 0; end; - internal procedure UpdateWorkOrderProduct(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderProduct: Record "FS Work Order Product") + internal procedure UpdateWorkOrderProduct(ServiceLineArchive: Record "Service Line Archive"; var FSWorkOrderProduct: Record "FS Work Order Product") var Modified: Boolean; begin - if FSWorkOrderProduct.QuantityShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin - FSWorkOrderProduct.QuantityShipped := SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship"; + if FSWorkOrderProduct.QuantityShipped <> (ServiceLineArchive."Quantity Shipped" + ServiceLineArchive."Qty. to Ship") then begin + FSWorkOrderProduct.QuantityShipped := ServiceLineArchive."Quantity Shipped" + ServiceLineArchive."Qty. to Ship"; Modified := true; end; - if FSWorkOrderProduct.QuantityInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice") then begin - FSWorkOrderProduct.QuantityInvoiced := SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice"; + if FSWorkOrderProduct.QuantityInvoiced <> (ServiceLineArchive."Quantity Invoiced" + ServiceLineArchive."Qty. to Invoice") then begin + FSWorkOrderProduct.QuantityInvoiced := ServiceLineArchive."Quantity Invoiced" + ServiceLineArchive."Qty. to Invoice"; Modified := true; end; - if FSWorkOrderProduct.QuantityConsumed <> SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume" then begin - FSWorkOrderProduct.QuantityConsumed := SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume"; + if FSWorkOrderProduct.QuantityConsumed <> ServiceLineArchive."Quantity Consumed" + ServiceLineArchive."Qty. to Consume" then begin + FSWorkOrderProduct.QuantityConsumed := ServiceLineArchive."Quantity Consumed" + ServiceLineArchive."Qty. to Consume"; Modified := true; end; @@ -114,22 +114,22 @@ codeunit 6617 "FS Archived Service Orders Job" FSWorkOrderProduct.Modify(); end; - internal procedure UpdateWorkOrderService(SalesLineArchive: Record "Service Line Archive"; var FSWorkOrderService: Record "FS Work Order Service") + internal procedure UpdateWorkOrderService(ServiceLineArchive: Record "Service Line Archive"; var FSWorkOrderService: Record "FS Work Order Service") var Modified: Boolean; begin - if FSWorkOrderService.DurationShipped <> (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") then begin - FSWorkOrderService.DurationShipped := (SalesLineArchive."Quantity Shipped" + SalesLineArchive."Qty. to Ship") * 60; + if FSWorkOrderService.DurationShipped <> (ServiceLineArchive."Quantity Shipped" + ServiceLineArchive."Qty. to Ship") then begin + FSWorkOrderService.DurationShipped := (ServiceLineArchive."Quantity Shipped" + ServiceLineArchive."Qty. to Ship") * 60; Modified := true; end; - if FSWorkOrderService.DurationInvoiced <> (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice") then begin - FSWorkOrderService.DurationInvoiced := (SalesLineArchive."Quantity Invoiced" + SalesLineArchive."Qty. to Invoice") * 60; + if FSWorkOrderService.DurationInvoiced <> (ServiceLineArchive."Quantity Invoiced" + ServiceLineArchive."Qty. to Invoice") then begin + FSWorkOrderService.DurationInvoiced := (ServiceLineArchive."Quantity Invoiced" + ServiceLineArchive."Qty. to Invoice") * 60; Modified := true; end; - if FSWorkOrderService.DurationConsumed <> SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume" then begin - FSWorkOrderService.DurationConsumed := (SalesLineArchive."Quantity Consumed" + SalesLineArchive."Qty. to Consume") * 60; + if FSWorkOrderService.DurationConsumed <> ServiceLineArchive."Quantity Consumed" + ServiceLineArchive."Qty. to Consume" then begin + FSWorkOrderService.DurationConsumed := (ServiceLineArchive."Quantity Consumed" + ServiceLineArchive."Qty. to Consume") * 60; Modified := true; end; From 406e1852f1c1d3e011722a30c8368347f84a5756 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Tue, 1 Oct 2024 09:30:11 +0200 Subject: [PATCH 29/49] added: redirect to archive for "show in bc button" --- .../FSIntTableSubscriber.Codeunit.al | 62 ++++++++++++++++--- .../FSIntegrationRecord.TableExt.al | 12 +++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index fc4824bfd4..e207c64e01 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -2347,6 +2347,7 @@ codeunit 6610 "FS Int. Table Subscriber" CRMIntegrationRecord.SetRange("Table ID", Database::"Service Header"); CRMIntegrationRecord.SetRange("Integration ID", ServiceHeader.SystemId); if CRMIntegrationRecord.FindFirst() then begin + CRMIntegrationRecord."Archived Service Header Id" := GetLatestServiceOrderArchiveSystemId(ServiceHeader."No."); CRMIntegrationRecord."Archived Service Order" := true; CRMIntegrationRecord.Modify(); end; @@ -2368,19 +2369,64 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnBeforeOpenRecordCardPage', '', false, false)] - local procedure OnBeforeOpenRecordCardPage(RecordID: RecordID; var IsHandled: Boolean) + local procedure GetLatestServiceOrderArchiveSystemId(OrderNo: Code[20]): Guid var - ServiceHeader: Record "Service Header"; - RecordRef: RecordRef; + ServiceHeaderArchive: Record "Service Header Archive"; + EmptyGuid: Guid; + begin + ServiceHeaderArchive.SetRange("Document Type", ServiceHeaderArchive."Document Type"::Order); + ServiceHeaderArchive.SetRange("No.", OrderNo); + if not ServiceHeaderArchive.FindLast() then + exit(EmptyGuid); + + exit(ServiceHeaderArchive.SystemId); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnBeforeOpenCoupledNavRecordPage', '', false, false)] + local procedure OnBeforeOpenCoupledNavRecordPage(CRMID: Guid; CRMEntityTypeName: Text; var Result: Boolean; var IsHandled: Boolean) begin - RecordRef := RecordID.GetRecord(); - if RecordID.TableNo <> Database::"Service Header" then + if IsHandled or Result then exit; - RecordRef.SetTable(ServiceHeader); + Result := OpenServiceHeader(CRMID); + if not Result then + Result := OpenServiceHeaderArchive(CRMID); + + IsHandled := Result; + end; + + local procedure OpenServiceHeader(CRMID: Guid): Boolean + var + CRMIntegrationRecord: Record "CRM Integration Record"; + ServiceHeader: Record "Service Header"; + begin + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Header"); + CRMIntegrationRecord.SetRange("CRM ID", CRMID); + if not CRMIntegrationRecord.FindFirst() then + exit(false); + + if not ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID") then + exit(false); + Page.Run(Page::"Service Order", ServiceHeader); - IsHandled := true; + exit(true); + end; + + local procedure OpenServiceHeaderArchive(CRMID: Guid): Boolean + var + CRMIntegrationRecord: Record "CRM Integration Record"; + ServiceHeaderArchive: Record "Service Header Archive"; + begin + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Header"); + CRMIntegrationRecord.SetRange("CRM ID", CRMID); + if not CRMIntegrationRecord.FindFirst() then + exit(false); + + if not ServiceHeaderArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Header Id") then + exit(false); + + Page.Run(Page::"Service Order Archive", ServiceHeaderArchive); + exit(true); end; [IntegrationEvent(false, false)] diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al index 1e695a00cf..65c2ad08eb 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al @@ -20,7 +20,12 @@ tableextension 6618 "FS Integration Record" extends "CRM Integration Record" Caption = 'Archived Service Order Updated'; DataClassification = SystemMetadata; } - field(12002; "Archived Service Line Id"; Guid) + field(12002; "Archived Service Header Id"; Guid) + { + Caption = 'Archived Service Header Id'; + DataClassification = SystemMetadata; + } + field(12003; "Archived Service Line Id"; Guid) { Caption = 'Archived Service Line Id'; DataClassification = SystemMetadata; @@ -29,7 +34,10 @@ tableextension 6618 "FS Integration Record" extends "CRM Integration Record" keys { - key(Key1; "Archived Service Line Id") + key(Key1; "Archived Service Header Id") + { + } + key(Key2; "Archived Service Line Id") { } } From df1be68dc460cdd48e982ab4fb8e5f75b4c1b7c6 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 3 Oct 2024 09:23:42 +0200 Subject: [PATCH 30/49] fixed: explicit call of service item sync --- .../app/src/Codeunits/FSIntTableSubscriber.Codeunit.al | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index e207c64e01..8930784899 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -139,6 +139,8 @@ codeunit 6610 "FS Int. Table Subscriber" exit; ServiceItem.SetView(Rec.GetTableFilter()); + if ServiceItem.GetFilter(SystemId) <> '' then + exit; if ServiceItem.GetFilter("Service Item Components") = '' then Error(MandatoryFilterErr, ServiceItem.FieldCaption("Service Item Components")); end; @@ -1424,7 +1426,6 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderIncident: Record "FS Work Order Incident"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; - FSBookableResourceBooking: Record "FS Bookable Resource Booking"; ServiceItemLine: Record "Service Item Line"; ServiceLine: Record "Service Line"; CRMProductName: Codeunit "CRM Product Name"; From ce80929a7f27ef22d5837ec426b1d9c3ac27bab2 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 3 Oct 2024 09:25:18 +0200 Subject: [PATCH 31/49] fixed: optional location code --- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 1077b6a622..150fe3a78e 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -71,7 +71,7 @@ codeunit 6611 "FS Setup Defaults" FSIntegrationMgt.EnableServiceOrderArchive(); end; - + SetCustomIntegrationsTableMappings(FSConnectionSetup); end; @@ -135,6 +135,7 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderProduct: Record "FS Work Order Product"; JobJournalLine: Record "Job Journal Line"; CDSCompany: Record "CDS Company"; + InventorySetup: Record "Inventory Setup"; CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; IsHandled: Boolean; EmptyGuid: Guid; @@ -204,12 +205,13 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::FromIntegrationTable, '', true, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - JobJournalLine.FieldNo("Location Code"), - FSWorkOrderProduct.FieldNo(WarehouseId), - IntegrationFieldMapping.Direction::FromIntegrationTable, - '', true, false); + if InventorySetup.Get() and (InventorySetup."Location Mandatory") then + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("Location Code"), + FSWorkOrderProduct.FieldNo(WarehouseId), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); OnAfterResetProjectJournalLineWOProductMapping(IntegrationTableMappingName); @@ -708,6 +710,7 @@ codeunit 6611 "FS Setup Defaults" ServiceLine: Record "Service Line"; FSWorkOrderProduct: Record "FS Work Order Product"; CDSCompany: Record "CDS Company"; + InventorySetup: Record "Inventory Setup"; CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; EmptyGuid: Guid; IsHandled: Boolean; @@ -771,19 +774,21 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Location Code"), - FSWorkOrderProduct.FieldNo(WarehouseId), - IntegrationFieldMapping.Direction::Bidirectional, - '', true, false); - - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("Location Code"), - FSWorkOrderProduct.FieldNo(LocationCode), - IntegrationFieldMapping.Direction::ToIntegrationTable, - '', true, false); + if InventorySetup.Get() and (InventorySetup."Location Mandatory") then begin + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Location Code"), + FSWorkOrderProduct.FieldNo(WarehouseId), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Location Code"), + FSWorkOrderProduct.FieldNo(LocationCode), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + end; InsertIntegrationFieldMapping( IntegrationTableMappingName, From 220e0748df7a065a11d18e29c3fb4bd615307dbd Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 3 Oct 2024 10:40:34 +0200 Subject: [PATCH 32/49] removed: product id on work order service (only necessaryfor product) --- .../app/src/Codeunits/FSSetupDefaults.Codeunit.al | 7 ------- .../app/src/Tables/FSWarehouse.Table.al | 2 +- .../app/src/Tables/FSWorkOrderService.Table.al | 7 ------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 150fe3a78e..16ac0df985 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -888,13 +888,6 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo("No."), - FSWorkOrderService.FieldNo(ProductId), - IntegrationFieldMapping.Direction::ToIntegrationTable, - '', true, false); - InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo(Description), diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al index a36c232f06..fe16069679 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al @@ -195,7 +195,7 @@ table 6624 "FS Warehouse" } field(33; CompanyId; GUID) { - ExternalName = 'bcbi_companyid'; + ExternalName = 'bcbi_company'; ExternalType = 'Lookup'; Description = 'The unique identifier of the company associated with the location.'; Caption = 'Company'; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al index 9cc495e0cb..fc76add2d2 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al @@ -769,13 +769,6 @@ table 6620 "FS Work Order Service" Caption = 'Location Code'; ExternalAccess = Read; } - field(122; ProductId; Text[1024]) - { - ExternalName = 'bcbi_productid'; - ExternalType = 'String'; - Description = 'Unique identifier of the warehouse associated with the entity.'; - Caption = 'Location Code'; - } } keys { From dd91a3cca6465a60193abd00a32a0e36b23a388f Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 3 Oct 2024 12:08:57 +0200 Subject: [PATCH 33/49] added: separate service item line for bookings --- .../FSIntTableSubscriber.Codeunit.al | 59 ++++++++++++++++++- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 9 +-- .../FSServiceItemLine.TableExt.al | 5 ++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 8930784899..f6ac846600 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -1047,6 +1047,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSBookableResourceBooking: Record "FS Bookable Resource Booking"; FSBookableResourceBooking2: Record "FS Bookable Resource Booking"; CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch."; + FSIntegrationMgt: Codeunit "FS Integration Mgt."; FSBookableResourceBookingRecordRef: RecordRef; FSBookableResourceBookingId: Guid; FSBookableResourceBookingIdList: List of [Guid]; @@ -1067,14 +1068,17 @@ codeunit 6610 "FS Int. Table Subscriber" if FSBookableResourceBooking.IsEmpty() then begin CRMIntegrationRecord.Delete(); ArchiveServiceOrder(ServiceHeader, ArchivedServiceOrders); - if ServiceLineToDelete.GetBySystemId(ServiceLine.SystemId) then + if ServiceLineToDelete.GetBySystemId(ServiceLine.SystemId) then begin + DeleteServiceItemLineForBooking(ServiceLine); ServiceLineToDelete.Delete(true); + end; end; end; until ServiceLine.Next() = 0; FSBookableResourceBooking.Reset(); FSBookableResourceBooking.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + FSBookableResourceBooking.SetRange(BookingStatus, FSIntegrationMgt.GetBookingStatusCompleted()); if FSBookableResourceBooking.FindSet() then begin repeat FSBookableResourceBookingIdList.Add(FSBookableResourceBooking.BookableResourceBookingId) @@ -1103,6 +1107,7 @@ codeunit 6610 "FS Int. Table Subscriber" ServiceItemLine.SetRange("Document Type", ServiceHeader."Document Type"); ServiceItemLine.SetRange("Document No.", ServiceHeader."No."); + ServiceItemLine.SetRange("FS Bookings", false); if not ServiceItemLine.IsEmpty() then begin ServiceItemLineRecordRef.GetTable(ServiceItemLine); CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(ServiceItemLineRecordRef, false, false); @@ -1555,6 +1560,12 @@ codeunit 6610 "FS Int. Table Subscriber" end; DestinationRecordRef.GetTable(JobJournalLine); end; + 'FS Bookable Resource Booking-Service Line': + begin + DestinationRecordRef.SetTable(ServiceLine); + GenerateServiceItemLineForBooking(ServiceLine); + DestinationRecordRef.GetTable(ServiceLine); + end; 'Service Order Type-FS Work Order Type': SetCompanyId(DestinationRecordRef); 'Service Header-FS Work Order': @@ -1597,6 +1608,52 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure GenerateServiceItemLineForBooking(var ServiceLine: Record "Service Line") + var + ServiceItemLine: Record "Service Item Line"; + Resource: Record Resource; + begin + if not GetServiceItemLine(ServiceLine, ServiceItemLine) then begin + ServiceItemLine.Validate("Document Type", ServiceLine."Document Type"); + ServiceItemLine.Validate("Document No.", ServiceLine."Document No."); + ServiceItemLine.Validate("Line No.", GetNextLineNo(ServiceItemLine)); + ServiceItemLine.Validate(Description, Resource.TableCaption()); + ServiceItemLine.Validate("FS Bookings", true); + ServiceItemLine.Insert(true); + end; + + ServiceLine.Validate("Service Item Line No.", ServiceItemLine."Line No."); + end; + + local procedure DeleteServiceItemLineForBooking(ServiceLineToDelete: Record "Service Line") + var + ServiceItemLine: Record "Service Item Line"; + ServiceLine: Record "Service Line"; + begin + // other service line exists? + ServiceLine.SetRange("Document Type", ServiceLineToDelete."Document Type"); + ServiceLine.SetRange("Document No.", ServiceLineToDelete."Document No."); + ServiceLine.SetRange("Service Item Line No.", ServiceLineToDelete."Service Item Line No."); + ServiceLine.SetFilter("Line No.", '<>%1', ServiceLineToDelete."Line No."); + if not ServiceLine.IsEmpty() then + exit; + + // delete service item line -> only if no other service line exists + ServiceItemLine.SetRange("Document Type", ServiceLineToDelete."Document Type"); + ServiceItemLine.SetRange("Document No.", ServiceLineToDelete."Document No."); + ServiceItemLine.SetRange("Line No.", ServiceLineToDelete."Service Item Line No."); + if not ServiceItemLine.IsEmpty() then + ServiceItemLine.DeleteAll() + end; + + local procedure GetServiceItemLine(ServiceLine: Record "Service Line"; var ServiceItemLine: Record "Service Item Line"): Boolean + begin + ServiceItemLine.SetRange("Document Type", ServiceLine."Document Type"); + ServiceItemLine.SetRange("Document No.", ServiceLine."Document No."); + ServiceItemLine.SetRange("FS Bookings", true); + exit(ServiceItemLine.FindFirst()); + end; + local procedure GetNextLineNo(ServiceItemLine: Record "Service Item Line"): Integer var ServiceItemLineSearch: Record "Service Item Line"; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 16ac0df985..ad52ddb8b6 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -663,16 +663,17 @@ codeunit 6611 "FS Setup Defaults" ServiceItemLine.Reset(); ServiceItemLine.SetRange("Document Type", ServiceItemLine."Document Type"::Order); + ServiceItemLine.SetRange("FS Bookings", false); FSWorkOrderIncident.Reset(); if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then FSWorkOrderIncident.SetRange(CompanyId, CDSCompany.CompanyId); InsertIntegrationTableMapping( - IntegrationTableMapping, IntegrationTableMappingName, - Database::"Service Item Line", Database::"FS Work Order Incident", - FSWorkOrderIncident.FieldNo(WorkOrderIncidentId), FSWorkOrderIncident.FieldNo(ModifiedOn), - '', '', false); + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Item Line", Database::"FS Work Order Incident", + FSWorkOrderIncident.FieldNo(WorkOrderIncidentId), FSWorkOrderIncident.FieldNo(ModifiedOn), + '', '', false); IntegrationTableMapping.SetTableFilter( GetTableFilterFromView(Database::"Service Item Line", ServiceItemLine.TableCaption(), ServiceItemLine.GetView())); diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al index 32173b2a02..7ff724ee65 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItemLine.TableExt.al @@ -18,5 +18,10 @@ tableextension 6620 "FS Service Item Line" extends "Service Item Line" Editable = false; CalcFormula = exist("CRM Integration Record" where("Integration ID" = field(SystemId), "Table ID" = const(Database::"Service Item Line"))); } + field(12001; "FS Bookings"; Boolean) + { + Caption = 'Field Service Bookings'; + DataClassification = CustomerContent; + } } } From c5fd58e59a0a957214ee6c82a932956262bca098 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 3 Oct 2024 14:52:08 +0200 Subject: [PATCH 34/49] added: skip reimport for sales (item) lines --- .../FSIntTableSubscriber.Codeunit.al | 97 +++++++++++++++++-- .../FSIntegrationRecord.TableExt.al | 5 + 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index f6ac846600..43ba0defb5 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -905,7 +905,8 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderIncident.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); if FSWorkOrderIncident.FindSet() then begin repeat - FSWorkOrderIncidentIdList.Add(FSWorkOrderIncident.WorkOrderIncidentId) + if not SkipReimport(FSWorkOrderIncident.WorkOrderIncidentId, FSWorkOrderIncident.ModifiedOn) then + FSWorkOrderIncidentIdList.Add(FSWorkOrderIncident.WorkOrderIncidentId) until FSWorkOrderIncident.Next() = 0; foreach CRMSalesorderdetailId in FSWorkOrderIncidentIdList do @@ -970,7 +971,8 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderProduct.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); if FSWorkOrderProduct.FindSet() then begin repeat - FSWorkOrderProductIdList.Add(FSWorkOrderProduct.WorkOrderProductId) + if not SkipReimport(FSWorkOrderProduct.WorkOrderProductId, FSWorkOrderProduct.ModifiedOn) then + FSWorkOrderProductIdList.Add(FSWorkOrderProduct.WorkOrderProductId); until FSWorkOrderProduct.Next() = 0; foreach FSWorkOrderProductId in FSWorkOrderProductIdList do @@ -1024,7 +1026,8 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderService.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); if FSWorkOrderService.FindSet() then begin repeat - FSWorkOrderServiceIdList.Add(FSWorkOrderService.WorkOrderServiceId) + if not SkipReimport(FSWorkOrderService.WorkOrderServiceId, FSWorkOrderService.ModifiedOn) then + FSWorkOrderServiceIdList.Add(FSWorkOrderService.WorkOrderServiceId); until FSWorkOrderService.Next() = 0; foreach FSWorkOrderServiceId in FSWorkOrderServiceIdList do @@ -1081,7 +1084,8 @@ codeunit 6610 "FS Int. Table Subscriber" FSBookableResourceBooking.SetRange(BookingStatus, FSIntegrationMgt.GetBookingStatusCompleted()); if FSBookableResourceBooking.FindSet() then begin repeat - FSBookableResourceBookingIdList.Add(FSBookableResourceBooking.BookableResourceBookingId) + if not SkipReimport(FSBookableResourceBooking.BookableResourceBookingId, FSBookableResourceBooking.ModifiedOn) then + FSBookableResourceBookingIdList.Add(FSBookableResourceBooking.BookableResourceBookingId); until FSBookableResourceBooking.Next() = 0; foreach FSBookableResourceBookingId in FSBookableResourceBookingIdList do @@ -1094,6 +1098,23 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure SkipReimport(CRMId: Guid; CurrentModifyTimeStamp: DateTime): Boolean + var + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + // check if skipped (because of deletion) + CRMIntegrationRecord.SetRange("CRM ID", CRMId); + if not CRMIntegrationRecord.FindFirst() then + exit(false); + if not CRMIntegrationRecord."Skip Reimport" then + exit(false); + if (CRMIntegrationRecord."Skip Reimport") and (CRMIntegrationRecord."Last Synch. Modified On" > CurrentModifyTimeStamp) then + exit(true); + + CRMIntegrationRecord.Delete(); // there was an update in field service -> start import again with new coupling + exit(false); + end; + local procedure ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) var ServiceHeader: Record "Service Header"; @@ -2380,7 +2401,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; [EventSubscriber(ObjectType::Table, Database::"Service Header", 'OnAfterDeleteEvent', '', false, false)] - local procedure HandleOnAfterDeleteAfterPosting(var Rec: Record "Service Header") + local procedure HandleOnAfterDeleteServiceHeader(var Rec: Record "Service Header") begin if Rec.IsTemporary() then exit; @@ -2394,12 +2415,74 @@ codeunit 6610 "FS Int. Table Subscriber" MarkArchivedServiceOrderLine(ServiceLine, ServiceLineArchive); end; + [EventSubscriber(ObjectType::Table, Database::"Service Item Line", 'OnAfterDeleteEvent', '', false, false)] + local procedure HandleOnAfterDeleteServiceItemLine(var Rec: Record "Service Item Line") + begin + if Rec.IsTemporary() then + exit; + + UncoupleRecord(Rec); + end; + + [EventSubscriber(ObjectType::Table, Database::"Service Line", 'OnAfterDeleteEvent', '', false, false)] + local procedure HandleOnAfterDeleteServiceLine(var Rec: Record "Service Line") + begin + if Rec.IsTemporary() then + exit; + + UncoupleRecord(Rec); + end; + + local procedure UncoupleRecord(ServiceItemLine: Record "Service Item Line") + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + if not FSConnectionSetup.IsIntegrationTypeServiceEnabled() then + exit; + + CRMIntegrationRecord.SetCurrentKey("Integration ID"); + CRMIntegrationRecord.SetRange("Integration ID", ServiceItemLine.SystemId); + if not CRMIntegrationRecord.FindFirst() then + exit; + + CRMIntegrationRecord."Last Synch. CRM Modified On" := CurrentDateTime(); + CRMIntegrationRecord."Last Synch. CRM Result" := CRMIntegrationRecord."Last Synch. CRM Result"::Success; + CRMIntegrationRecord."Last Synch. Modified On" := CurrentDateTime(); + CRMIntegrationRecord."Last Synch. Result" := CRMIntegrationRecord."Last Synch. Result"::Success; + CRMIntegrationRecord.Skipped := true; + CRMIntegrationRecord."Skip Reimport" := true; + CRMIntegrationRecord.Modify(); + end; + + local procedure UncoupleRecord(ServiceLine: Record "Service Line") + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + if not FSConnectionSetup.IsIntegrationTypeServiceEnabled() then + exit; + + CRMIntegrationRecord.SetCurrentKey("Integration ID"); + CRMIntegrationRecord.SetRange("Integration ID", ServiceLine.SystemId); + if not CRMIntegrationRecord.FindFirst() then + exit; + + CRMIntegrationRecord."Last Synch. CRM Modified On" := CurrentDateTime(); + CRMIntegrationRecord."Last Synch. CRM Result" := CRMIntegrationRecord."Last Synch. CRM Result"::Success; + CRMIntegrationRecord."Last Synch. Modified On" := CurrentDateTime(); + CRMIntegrationRecord."Last Synch. Result" := CRMIntegrationRecord."Last Synch. Result"::Success; + CRMIntegrationRecord.Skipped := true; + CRMIntegrationRecord."Skip Reimport" := true; + CRMIntegrationRecord.Modify(); + end; + internal procedure MarkArchivedServiceOrder(ServiceHeader: Record "Service Header") var FSConnectionSetup: Record "FS Connection Setup"; CRMIntegrationRecord: Record "CRM Integration Record"; begin - if not FSConnectionSetup.IsEnabled() then + if not FSConnectionSetup.IsIntegrationTypeServiceEnabled() then exit; CRMIntegrationRecord.SetRange("Table ID", Database::"Service Header"); @@ -2416,7 +2499,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSConnectionSetup: Record "FS Connection Setup"; CRMIntegrationRecord: Record "CRM Integration Record"; begin - if not FSConnectionSetup.IsEnabled() then + if not FSConnectionSetup.IsIntegrationTypeServiceEnabled() then exit; CRMIntegrationRecord.SetRange("Table ID", Database::"Service Line"); diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al index 65c2ad08eb..3447fb6577 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSIntegrationRecord.TableExt.al @@ -30,6 +30,11 @@ tableextension 6618 "FS Integration Record" extends "CRM Integration Record" Caption = 'Archived Service Line Id'; DataClassification = SystemMetadata; } + field(12004; "Skip Reimport"; Boolean) + { + Caption = 'Skip Reimport'; + DataClassification = SystemMetadata; + } } keys From 458d48ef74a8859b4d64308556ebbcdd5036b5c1 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Fri, 4 Oct 2024 08:13:23 +0200 Subject: [PATCH 35/49] added: status sync --- .../FSArchivedServiceOrdersJob.Codeunit.al | 7 +++ .../FSIntTableSubscriber.Codeunit.al | 45 +++++++++++++++++++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 14 ++++++ 3 files changed, 66 insertions(+) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al index 8011ade641..4aa5a63401 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al @@ -65,6 +65,7 @@ codeunit 6617 "FS Archived Service Orders Job" local procedure UpdateFromServiceHeader(var FSWorkOrder: Record "FS Work Order") begin ResetFSWorkOrderLineFromServiceOrderLine(FSWorkOrder); + MarkPosted(FSWorkOrder); end; local procedure ResetFSWorkOrderLineFromServiceOrderLine(var FSWorkOrder: Record "FS Work Order") @@ -91,6 +92,12 @@ codeunit 6617 "FS Archived Service Orders Job" until FSWorkOrderService.Next() = 0; end; + local procedure MarkPosted(var FSWorkOrder: Record "FS Work Order") + begin + FSWorkOrder.SystemStatus := FSWorkOrder.SystemStatus::Posted; + FSWorkOrder.Modify(); + end; + internal procedure UpdateWorkOrderProduct(ServiceLineArchive: Record "Service Line Archive"; var FSWorkOrderProduct: Record "FS Work Order Product") var Modified: Boolean; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 43ba0defb5..6bfc607fdf 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -342,6 +342,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderProduct: Record "FS Work Order Product"; FSBookableResourceBooking: Record "FS Bookable Resource Booking"; JobJournalLine: Record "Job Journal Line"; + ServiceHeader: Record "Service Header"; ServiceLine: Record "Service Line"; ItemUnitOfMeasure: Record "Item Unit of Measure"; SourceRecordRef: RecordRef; @@ -365,6 +366,50 @@ codeunit 6610 "FS Int. Table Subscriber" if SourceFieldRef.Record().Number() = DestinationFieldRef.Record().Number() then exit; + if (SourceFieldRef.Record().Number = Database::"Service Header") and + (DestinationFieldRef.Record().Number = Database::"FS Work Order") then + case DestinationFieldRef.Name() of + FSWorkOrder.FieldName(SystemStatus): + begin + SourceFieldRef.Record().SetTable(ServiceHeader); + DestinationFieldRef.Record().SetTable(FSWorkOrder); + + case ServiceHeader.Status of + ServiceHeader.Status::Pending: + NewValue := FSWorkOrder.SystemStatus::Unscheduled; + else + NewValue := FSWorkOrder.SystemStatus; // default -> no update + end; + + IsValueFound := true; + NeedsConversion := false; + end; + end; + if (SourceFieldRef.Record().Number = Database::"FS Work Order") and + (DestinationFieldRef.Record().Number = Database::"Service Header") then + case DestinationFieldRef.Name() of + ServiceHeader.FieldName(Status): + begin + SourceFieldRef.Record().SetTable(FSWorkOrder); + DestinationFieldRef.Record().SetTable(ServiceHeader); + + case FSWorkOrder.SystemStatus of + FSWorkOrder.SystemStatus::Unscheduled, + FSWorkOrder.SystemStatus::Scheduled: + NewValue := ServiceHeader.Status::Pending; + FSWorkOrder.SystemStatus::InProgress: + NewValue := ServiceHeader.Status::"In Process"; + FSWorkOrder.SystemStatus::Completed: + NewValue := ServiceHeader.Status::Finished; + else + NewValue := ServiceHeader.Status; // default -> no update + end; + + IsValueFound := true; + NeedsConversion := false; + end; + end; + if (SourceFieldRef.Record().Number = Database::"Service Line") and (DestinationFieldRef.Record().Number = Database::"FS Work Order Product") then case DestinationFieldRef.Name() of diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index ad52ddb8b6..7923a499e7 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -572,8 +572,15 @@ codeunit 6611 "FS Setup Defaults" ServiceOrder.Reset(); ServiceOrder.SetRange("Document Type", ServiceOrder."Document Type"::Order); + FSWorkOrder.Reset(); FSWorkOrder.SetRange(IntegrateToService, true); + FSWorkOrder.SetFilter(SystemStatus, '%1|%2|%3|%4', + FSWorkOrder.SystemStatus::Unscheduled, + FSWorkOrder.SystemStatus::Scheduled, + FSWorkOrder.SystemStatus::InProgress, + FSWorkOrder.SystemStatus::Completed + ); if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then FSWorkOrder.SetRange(CompanyId, CDSCompany.CompanyId); @@ -631,6 +638,13 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceOrder.FieldNo(Status), + FSWorkOrder.FieldNo(SystemStatus), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, 0, From 480f54307475f8d80d442823d5d45e8e74985765 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Fri, 4 Oct 2024 11:55:27 +0200 Subject: [PATCH 36/49] modified: object id of archived service order job --- .../app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al index 4aa5a63401..bf9ffdfad5 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSArchivedServiceOrdersJob.Codeunit.al @@ -10,7 +10,7 @@ using Microsoft.Service.Document; using System.Threading; using Microsoft.Service.Archive; -codeunit 6617 "FS Archived Service Orders Job" +codeunit 6618 "FS Archived Service Orders Job" { TableNo = "Job Queue Entry"; From 9f568087c46776303c626bdd0128cf007ba9bd79 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Mon, 7 Oct 2024 07:33:56 +0200 Subject: [PATCH 37/49] removed: unnecessary namespace usings --- .../app/src/Page Extensions/FSServiceOrderTypes.PageExt.al | 1 - .../FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al | 1 - 2 files changed, 2 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al index 8da55cc2ef..50d73f7d00 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceOrderTypes.PageExt.al @@ -4,7 +4,6 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.Integration.DynamicsFieldService; -using Microsoft.Service.Document; using Microsoft.Service.Setup; using Microsoft.Integration.Dataverse; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al index fc4d1c12fc..0c324e8995 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSWorkOrders.Page.al @@ -5,7 +5,6 @@ namespace Microsoft.Integration.DynamicsFieldService; using Microsoft.Integration.Dataverse; -using Microsoft.Service.Setup; using System.Environment.Configuration; using Microsoft.Integration.D365Sales; using Microsoft.Service.Document; From 5b37b929e9b359b18e21613a06c9892ca51c0a44 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 16 Oct 2024 08:17:26 +0200 Subject: [PATCH 38/49] fixed: product id label --- .../app/src/Tables/FSWorkOrderProduct.Table.al | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al index bec6a653a5..9605841d5a 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al @@ -728,12 +728,12 @@ table 6619 "FS Work Order Product" Caption = 'Location Code'; ExternalAccess = Read; } - field(122; ProductId; Text[1024]) + field(122; ProductId; Code[20]) { ExternalName = 'bcbi_productid'; ExternalType = 'String'; - Description = 'Unique identifier of the warehouse associated with the entity.'; - Caption = 'Location Code'; + Description = 'Unique identifier of the product associated with the entity.'; + Caption = 'Product Id'; } } keys From 8853f1a7704795f91b46760ad553c8abc1a5687a Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 16 Oct 2024 08:17:35 +0200 Subject: [PATCH 39/49] fixed: missing objects in permissionset files --- .../app/src/Permissions/FSEdit.PermissionSet.al | 4 +++- .../app/src/Permissions/FSObjects.PermissionSet.al | 11 +++++++++-- .../app/src/Permissions/FSRead.PermissionSet.al | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al index 70b9c6083f..93dffb31ab 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al @@ -26,5 +26,7 @@ permissionset 6610 "FS - Edit" tabledata "FS Work Order Product" = IMD, tabledata "FS Work Order Service" = IMD, tabledata "FS Work Order Substatus" = IMD, - tabledata "FS Work Order Type" = IMD; + tabledata "FS Work Order Type" = IMD, + tabledata "FS Booking Status" = IMD, + tabledata "FS Incident Type" = IMD; } diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al index a9cac825a6..a6dceefbd2 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al @@ -10,11 +10,12 @@ permissionset 6612 "FS - Objects" Assignable = false; Caption = 'Field Service - Objects'; - Permissions = codeunit "FS Assisted Setup Subscriber" = X, + Permissions = codeunit "FS Archived Service Orders Job" = X, + codeunit "FS Assisted Setup Subscriber" = X, codeunit "FS Data Classification" = X, codeunit "FS Install" = X, - codeunit "FS Integration Mgt." = X, codeunit "FS Int. Table Subscriber" = X, + codeunit "FS Integration Mgt." = X, codeunit "FS Lookup FS Tables" = X, codeunit "FS Setup Defaults" = X, page "FS Bookable Resource List" = X, @@ -22,13 +23,19 @@ permissionset 6612 "FS - Objects" page "FS Connection Setup Wizard" = X, page "FS Customer Asset List" = X, page "FS Item Avail. by Location" = X, + page "FS Posted Serv. Inv. Lines API" = X, + page "FS Posted Service Invoice API" = X, + page "FS Work Order Types" = X, + page "FS Work Orders" = X, query "FS Item Avail. by Location" = X, table "FS Bookable Resource" = X, table "FS Bookable Resource Booking" = X, table "FS BookableResourceBookingHdr" = X, + table "FS Booking Status" = X, table "FS Connection Setup" = X, table "FS Customer Asset" = X, table "FS Customer Asset Category" = X, + table "FS Incident Type" = X, table "FS Project Task" = X, table "FS Resource Pay Type" = X, table "FS Warehouse" = X, diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al index 1f4dda8f14..37019b5b1d 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al @@ -26,5 +26,7 @@ permissionset 6611 "FS - Read" tabledata "FS Work Order Product" = R, tabledata "FS Work Order Service" = R, tabledata "FS Work Order Substatus" = R, - tabledata "FS Work Order Type" = R; + tabledata "FS Work Order Type" = R, + tabledata "FS Booking Status" = R, + tabledata "FS Incident Type" = R; } From d1c72e02312c1394015768bc2f5912184811607f Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 16 Oct 2024 14:48:21 +0200 Subject: [PATCH 40/49] fixed: sync of service item line / work order incident --- .../FSIntTableSubscriber.Codeunit.al | 78 ++++++++++++++++--- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 15 ++++ 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 6bfc607fdf..12700f1bb2 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -226,8 +226,6 @@ codeunit 6610 "FS Int. Table Subscriber" if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrder) then ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID"); - if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrderIncident) then - ServiceItemLine.GetBySystemId(CRMIntegrationRecord."Integration ID"); ServiceLine."Document Type" := ServiceLine."Document Type"::Order; ServiceLine."Document No." := ServiceHeader."No."; @@ -248,8 +246,6 @@ codeunit 6610 "FS Int. Table Subscriber" if CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.WorkOrder) then ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID"); - if CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.WorkOrderIncident) then - ServiceItemLine.GetBySystemId(CRMIntegrationRecord."Integration ID"); ServiceLine."Document Type" := ServiceLine."Document Type"::Order; ServiceLine."Document No." := ServiceHeader."No."; @@ -668,19 +664,33 @@ codeunit 6610 "FS Int. Table Subscriber" CRMIntegrationRecord: Record "CRM Integration Record"; FSWorkOrderService: Record "FS Work Order Service"; FSWorkOrderProduct: Record "FS Work Order Product"; + ServiceItemLine: Record "Service Item Line"; ServiceLine: Record "Service Line"; ItemUnitOfMeasure: Record "Item Unit of Measure"; CRMUom: Record "CRM Uom"; SourceRecordRef: RecordRef; NAVItemUomRecordId: RecordId; + WorkOrderIncidentId: Guid; NAVItemUomId: Guid; NotCoupledCRMUomErr: Label 'The unit is not coupled to a unit of measure.'; begin if not FSConnectionSetup.IsEnabled() then exit; - if (SourceFieldRef.Record().Number = Database::"FS Work Order Product") then + if (SourceFieldRef.Record().Number = Database::"FS Work Order Product") and + (DestinationFieldRef.Record().Number = Database::"Service Line") then case SourceFieldRef.Name() of + FSWorkOrderProduct.FieldName(WorkOrderIncident): + begin + SourceFieldRef.Record().SetTable(FSWorkOrderProduct); + DestinationFieldRef.Record().SetTable(ServiceLine); + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrderIncident) then begin + ServiceItemLine.GetBySystemId(CRMIntegrationRecord."Integration ID"); + NewValue := ServiceItemLine."Line No."; + IsValueFound := true; + end; + end; + FSWorkOrderProduct.FieldName(Unit): begin SourceRecordRef := SourceFieldRef.Record(); @@ -697,8 +707,20 @@ codeunit 6610 "FS Int. Table Subscriber" exit; end; end; - if (SourceFieldRef.Record().Number = Database::"FS Work Order Service") then + if (SourceFieldRef.Record().Number = Database::"FS Work Order Service") and + (DestinationFieldRef.Record().Number = Database::"Service Line") then case SourceFieldRef.Name() of + FSWorkOrderProduct.FieldName(WorkOrderIncident): + begin + SourceFieldRef.Record().SetTable(FSWorkOrderService); + DestinationFieldRef.Record().SetTable(ServiceLine); + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.WorkOrderIncident) then begin + ServiceItemLine.GetBySystemId(CRMIntegrationRecord."Integration ID"); + NewValue := ServiceItemLine."Line No."; + IsValueFound := true; + end; + end; + FSWorkOrderService.FieldName(Unit): begin SourceRecordRef := SourceFieldRef.Record(); @@ -715,8 +737,45 @@ codeunit 6610 "FS Int. Table Subscriber" exit; end; end; - if (SourceFieldRef.Record().Number = Database::"Service Line") then + + if (SourceFieldRef.Record().Number = Database::"Service Line") and + (DestinationFieldRef.Record().Number = Database::"FS Work Order Product") then case SourceFieldRef.Name() of + ServiceLine.FieldName("Service Item Line No."): + begin + SourceFieldRef.Record().SetTable(ServiceLine); + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderItemLineRecordId(ServiceLine."Document No.", ServiceLine."Service Item Line No."), WorkOrderIncidentId) then begin + NewValue := WorkOrderIncidentId; + IsValueFound := true; + end; + end; + end; + + if (SourceFieldRef.Record().Number = Database::"Service Line") and + (DestinationFieldRef.Record().Number = Database::"FS Work Order Service") then + case SourceFieldRef.Name() of + ServiceLine.FieldName("Service Item Line No."): + begin + SourceFieldRef.Record().SetTable(ServiceLine); + if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderItemLineRecordId(ServiceLine."Document No.", ServiceLine."Service Item Line No."), WorkOrderIncidentId) then begin + NewValue := WorkOrderIncidentId; + IsValueFound := true; + end; + end; + end; + + if (SourceFieldRef.Record().Number = Database::"Service Line") and + (DestinationFieldRef.Record().Number in [Database::"FS Work Order Product", Database::"FS Work Order Service"]) then + case SourceFieldRef.Name() of + ServiceLine.FieldName("Service Item Line No."): + begin + SourceFieldRef.Record().SetTable(ServiceLine); + if CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.WorkOrderIncident) then begin + ServiceItemLine.GetBySystemId(CRMIntegrationRecord."Integration ID"); + NewValue := ServiceItemLine."Line No."; + IsValueFound := true; + end; + end; ServiceLine.FieldName("Unit of Measure Code"): begin SourceRecordRef := SourceFieldRef.Record(); @@ -1507,7 +1566,6 @@ codeunit 6610 "FS Int. Table Subscriber" BillingAccId: Guid; ServiceAccId: Guid; WorkOrderId: Guid; - WorkOrderIncidentId: Guid; Handled: Boolean; begin if not FSConnectionSetup.IsEnabled() then @@ -1655,8 +1713,6 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(FSWorkOrderProduct); if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceLine."Document No."), WorkOrderId) then FSWorkOrderProduct.WorkOrder := WorkOrderId; - if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderItemLineRecordId(ServiceLine."Document No.", ServiceLine."Service Item Line No."), WorkOrderIncidentId) then - FSWorkOrderProduct.WorkOrderIncident := WorkOrderIncidentId; DestinationRecordRef.GetTable(FSWorkOrderProduct); end; 'Service Line-FS Work Order Service': @@ -1667,8 +1723,6 @@ codeunit 6610 "FS Int. Table Subscriber" DestinationRecordRef.SetTable(FSWorkOrderService); if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderRecordId(ServiceLine."Document No."), WorkOrderId) then FSWorkOrderService.WorkOrder := WorkOrderId; - if CRMIntegrationRecord.FindIDFromRecordID(GetServiceOrderItemLineRecordId(ServiceLine."Document No.", ServiceLine."Service Item Line No."), WorkOrderIncidentId) then - FSWorkOrderService.WorkOrderIncident := WorkOrderIncidentId; DestinationRecordRef.GetTable(FSWorkOrderService); end; end; diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index 7923a499e7..b9588644eb 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -768,6 +768,13 @@ codeunit 6611 "FS Setup Defaults" 0, IntegrationFieldMapping.Direction::FromIntegrationTable, 'Order', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Service Item Line No."), + FSWorkOrderProduct.FieldNo(WorkOrderIncident), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo("No."), @@ -839,6 +846,7 @@ codeunit 6611 "FS Setup Defaults" FSWorkOrderProduct.FieldNo(QuantityConsumed), IntegrationFieldMapping.Direction::ToIntegrationTable, '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, 0, @@ -896,6 +904,13 @@ codeunit 6611 "FS Setup Defaults" 0, IntegrationFieldMapping.Direction::FromIntegrationTable, 'Order', true, false); + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceLine.FieldNo("Service Item Line No."), + FSWorkOrderService.FieldNo(WorkOrderIncident), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo("No."), From 26dec3f02bbb7a9154c34e35ca9479ba99eef646 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Sun, 20 Oct 2024 09:49:57 +0200 Subject: [PATCH 41/49] added: filled service item line no test --- .../FSIntTableSubscriber.Codeunit.al | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 12700f1bb2..d562bb91af 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -901,6 +901,7 @@ codeunit 6610 "FS Int. Table Subscriber" JobJournalLine: Record "Job Journal Line"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; + ServiceHeader: Record "Service Header"; ArchivedServiceOrders: List of [Code[20]]; SourceDestCode: Text; begin @@ -932,6 +933,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; 'Service Header-FS Work Order': begin + ProofAllServiceItemLinesAssigned(ServiceHeader); ResetFSWorkOrderIncidentFromServiceOrderItemLine(SourceRecordRef, DestinationRecordRef); ResetFSWorkOrderProductFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); ResetFSWorkOrderServiceFromServiceOrderLine(SourceRecordRef, DestinationRecordRef); @@ -939,6 +941,17 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure ProofAllServiceItemLinesAssigned(ServiceHeader: Record "Service Header") + var + ServiceLine: Record "Service Line"; + begin + ServiceLine.SetRange("Document Type", ServiceLine."Document Type"::Order); + ServiceLine.SetRange("Document No.", ServiceHeader."No."); + ServiceLine.SetRange("Service Item Line No.", 0); + if ServiceLine.FindFirst() then + ServiceLine.TestField("Service Item Line No."); + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterUnchangedRecordHandled', '', false, false)] local procedure HandleOnAfterUnchangedRecordHandled(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) var @@ -1329,6 +1342,7 @@ codeunit 6610 "FS Int. Table Subscriber" local procedure HandleOnBeforeModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) var FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; SourceDestCode: Text; begin if not FSConnectionSetup.IsEnabled() then @@ -1339,9 +1353,38 @@ codeunit 6610 "FS Int. Table Subscriber" case SourceDestCode of 'FS Work Order Service-Job Journal Line': UpdateCorrelatedJobJournalLine(SourceRecordRef, DestinationRecordRef); + 'Service Header-FS Work Order': + begin + SourceRecordRef.SetTable(ServiceHeader); + ProofAllServiceItemLinesAssigned(ServiceHeader); + SetCompanyId(DestinationRecordRef); + end; end; end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnUpdateIntegrationRecordCoupling', '', false, false)] + local procedure HandleOnUpdateIntegrationRecordCoupling(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceHeader: Record "Service Header"; + SourceDestCode: Text; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of + 'Service Header-FS Work Order': + begin + SourceRecordRef.SetTable(ServiceHeader); + ProofAllServiceItemLinesAssigned(ServiceHeader); + SetCompanyId(DestinationRecordRef); + end; + end; + end; + + [EventSubscriber(ObjectType::Table, Database::"Inventory Setup", 'OnAfterValidateEvent', 'Location Mandatory', false, false)] local procedure AfterValidateLocationMandatory(var Rec: Record "Inventory Setup"; var xRec: Record "Inventory Setup"; CurrFieldNo: Integer) var @@ -1556,6 +1599,7 @@ codeunit 6610 "FS Int. Table Subscriber" FSWorkOrderIncident: Record "FS Work Order Incident"; FSWorkOrderProduct: Record "FS Work Order Product"; FSWorkOrderService: Record "FS Work Order Service"; + ServiceHeader: Record "Service Header"; ServiceItemLine: Record "Service Item Line"; ServiceLine: Record "Service Line"; CRMProductName: Codeunit "CRM Product Name"; @@ -1693,7 +1737,11 @@ codeunit 6610 "FS Int. Table Subscriber" 'Service Order Type-FS Work Order Type': SetCompanyId(DestinationRecordRef); 'Service Header-FS Work Order': - SetCompanyId(DestinationRecordRef); + begin + SourceRecordRef.SetTable(ServiceHeader); + ProofAllServiceItemLinesAssigned(ServiceHeader); + SetCompanyId(DestinationRecordRef); + end; 'Service Item Line-FS Work Order Incident': begin SetCompanyId(DestinationRecordRef); From 03e5828c226bf0abda04c9021b7bcb02da5e69c4 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Sun, 20 Oct 2024 10:19:13 +0200 Subject: [PATCH 42/49] added: reset of qty to ship/invoice/consume fields --- .../FSIntTableSubscriber.Codeunit.al | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index d562bb91af..7218db34e6 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -411,14 +411,20 @@ codeunit 6610 "FS Int. Table Subscriber" case DestinationFieldRef.Name() of FSWorkOrderProduct.FieldName(EstimateQuantity): begin - DestinationRecordRef := DestinationFieldRef.Record(); - DestinationRecordRef.SetTable(FSWorkOrderProduct); + SourceFieldRef.Record().SetTable(ServiceLine); + DestinationFieldRef.Record().SetTable(FSWorkOrderProduct); // only update estimated quantity if the line status is estimated - if FSWorkOrderProduct.LineStatus <> FSWorkOrderProduct.LineStatus::Estimated then begin + if FSWorkOrderProduct.LineStatus = FSWorkOrderProduct.LineStatus::Estimated then begin + ServiceLine.Validate("Qty. to Ship", 0); + ServiceLine.Validate("Qty. to Invoice", 0); + ServiceLine.Validate("Qty. to Consume", 0); + ServiceLine.Modify(true); + end else begin NewValue := FSWorkOrderProduct.EstimateQuantity; IsValueFound := true; NeedsConversion := false; end; + SourceFieldRef.Record().GetTable(ServiceLine); end; end; @@ -427,18 +433,21 @@ codeunit 6610 "FS Int. Table Subscriber" case DestinationFieldRef.Name() of FSWorkOrderService.FieldName(EstimateDuration): begin - DestinationRecordRef := DestinationFieldRef.Record(); - DestinationRecordRef.SetTable(FSWorkOrderService); + SourceFieldRef.Record().SetTable(ServiceLine); + DestinationFieldRef.Record().SetTable(FSWorkOrderService); // only update estimated quantity if the line status is estimated - if FSWorkOrderService.LineStatus <> FSWorkOrderService.LineStatus::Estimated then begin + if FSWorkOrderService.LineStatus = FSWorkOrderService.LineStatus::Estimated then begin + ServiceLine.Validate("Qty. to Ship", 0); + ServiceLine.Validate("Qty. to Invoice", 0); + ServiceLine.Validate("Qty. to Consume", 0); + ServiceLine.Modify(true); + end else begin NewValue := FSWorkOrderService.EstimateDuration; IsValueFound := true; NeedsConversion := false; exit; end; - SourceRecordRef := SourceFieldRef.Record(); - SourceRecordRef.SetTable(ServiceLine); DurationInHours := ServiceLine.Quantity; DurationInMinutes := DurationInHours * 60; NewValue := DurationInMinutes; From 575119a32bbb3c36063ecd6c2f3295d7c067cb55 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Mon, 21 Oct 2024 08:37:59 +0200 Subject: [PATCH 43/49] added: auto sync of uncoupled work orders (on open page) --- .../FSIntTableSubscriber.Codeunit.al | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 7218db34e6..37e52ddfe9 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -2682,16 +2682,52 @@ codeunit 6610 "FS Int. Table Subscriber" [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnBeforeOpenCoupledNavRecordPage', '', false, false)] local procedure OnBeforeOpenCoupledNavRecordPage(CRMID: Guid; CRMEntityTypeName: Text; var Result: Boolean; var IsHandled: Boolean) begin - if IsHandled or Result then + if IsHandled or Result or (CRMEntityTypeName <> 'msdyn_workorder') then exit; - Result := OpenServiceHeader(CRMID); - if not Result then - Result := OpenServiceHeaderArchive(CRMID); + if not OnlyServiceHeaderArchiveExists(CRMID) then + exit; + Result := OpenServiceHeaderArchive(CRMID); IsHandled := Result; end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnBeforeOpenRecordCardPage', '', false, false)] + local procedure OnBeforeOpenRecordCardPage(RecordID: RecordID; var IsHandled: Boolean) + var + ServiceHeader: Record "Service Header"; + RecordRef: RecordRef; + Result: Boolean; + begin + RecordRef := RecordID.GetRecord(); + case RecordID.TableNo of + Database::"Service Header": + begin + RecordRef.SetTable(ServiceHeader); + Page.Run(Page::"Service Order", ServiceHeader); + IsHandled := true; + end; + end; + end; + + local procedure OnlyServiceHeaderArchiveExists(CRMID: Guid): Boolean + var + CRMIntegrationRecord: Record "CRM Integration Record"; + ServiceHeader: Record "Service Header"; + ServiceHeaderArchive: Record "Service Header Archive"; + begin + CRMIntegrationRecord.SetRange("Table ID", Database::"Service Header"); + CRMIntegrationRecord.SetRange("CRM ID", CRMID); + if not CRMIntegrationRecord.FindFirst() then + exit(false); + + if ServiceHeader.GetBySystemId(CRMIntegrationRecord."Integration ID") then + exit(false); + + if ServiceHeaderArchive.GetBySystemId(CRMIntegrationRecord."Archived Service Header Id") then + exit(true); + end; + local procedure OpenServiceHeader(CRMID: Guid): Boolean var CRMIntegrationRecord: Record "CRM Integration Record"; From 3a11be6fa5d59c735052117e7f64b6313b1fc1b2 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Mon, 21 Oct 2024 12:44:33 +0200 Subject: [PATCH 44/49] removed: resync of pending state --- .../app/src/Codeunits/FSIntTableSubscriber.Codeunit.al | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index 37e52ddfe9..a97ad7dc92 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -369,14 +369,7 @@ codeunit 6610 "FS Int. Table Subscriber" begin SourceFieldRef.Record().SetTable(ServiceHeader); DestinationFieldRef.Record().SetTable(FSWorkOrder); - - case ServiceHeader.Status of - ServiceHeader.Status::Pending: - NewValue := FSWorkOrder.SystemStatus::Unscheduled; - else - NewValue := FSWorkOrder.SystemStatus; // default -> no update - end; - + NewValue := FSWorkOrder.SystemStatus; // default -> no update IsValueFound := true; NeedsConversion := false; end; From d1bc26c9068e8ccb71b3cc8853c8d892281b8de2 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Mon, 21 Oct 2024 13:03:13 +0200 Subject: [PATCH 45/49] added: skip work orders without incidents --- .../src/Codeunits/FSIntTableSubscriber.Codeunit.al | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index a97ad7dc92..e0714e9894 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -2443,6 +2443,7 @@ codeunit 6610 "FS Int. Table Subscriber" var FSConnectionSetup: Record "FS Connection Setup"; FSWorkOrder: Record "FS Work Order"; + FSWorkOrderIncident: Record "FS Work Order Incident"; CRMIntegrationRecord: Record "CRM Integration Record"; begin if not FSConnectionSetup.IsEnabled() then @@ -2452,11 +2453,16 @@ codeunit 6610 "FS Int. Table Subscriber" exit; SourceRecordRef.SetTable(FSWorkOrder); - if not CRMIntegrationRecord.FindByCRMID(FSWorkOrder.WorkOrderId) then - exit; - if CRMIntegrationRecord."Archived Service Order" then + // at least one work order incident should exist + FSWorkOrderIncident.SetRange(WorkOrder, FSWorkOrder.WorkOrderId); + if FSWorkOrderIncident.IsEmpty() then IgnoreRecord := true; + + // skip archived work orders + if CRMIntegrationRecord.FindByCRMID(FSWorkOrder.WorkOrderId) then + if CRMIntegrationRecord."Archived Service Order" then + IgnoreRecord := true; end; [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Table Synch.", 'OnAfterInitSynchJob', '', true, true)] From b957eeceeca1bf08b49948a662f0ead043502a34 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 24 Oct 2024 13:02:16 +0200 Subject: [PATCH 46/49] added: support of min qty --- .../FSIntTableSubscriber.Codeunit.al | 119 ++++++++++-------- .../src/Codeunits/FSSetupDefaults.Codeunit.al | 14 --- 2 files changed, 64 insertions(+), 69 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index e0714e9894..eefb287663 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -298,22 +298,48 @@ codeunit 6610 "FS Int. Table Subscriber" SourceRecordRef.SetTable(FSWorkOrderProduct); DestinationRecordRef.SetTable(ServiceLine); - UpdateQuantities(FSWorkOrderProduct, ServiceLine); + UpdateQuantities(FSWorkOrderProduct, ServiceLine, false); AdditionalFieldsWereModified := true; + SourceRecordRef.GetTable(FSWorkOrderProduct); DestinationRecordRef.GetTable(ServiceLine); end; + 'Service Line-FS Work Order Product': + begin + SourceRecordRef.SetTable(ServiceLine); + DestinationRecordRef.SetTable(FSWorkOrderProduct); + + UpdateQuantities(FSWorkOrderProduct, ServiceLine, true); + AdditionalFieldsWereModified := true; + + SourceRecordRef.GetTable(ServiceLine); + DestinationRecordRef.GetTable(FSWorkOrderProduct); + end; + 'FS Work Order Service-Service Line': begin SourceRecordRef.SetTable(FSWorkOrderService); DestinationRecordRef.SetTable(ServiceLine); - UpdateQuantities(FSWorkOrderService, ServiceLine); + UpdateQuantities(FSWorkOrderService, ServiceLine, false); AdditionalFieldsWereModified := true; + SourceRecordRef.GetTable(FSWorkOrderService); DestinationRecordRef.GetTable(ServiceLine); end; + 'Service Line-FS Work Order Service': + begin + SourceRecordRef.SetTable(ServiceLine); + DestinationRecordRef.SetTable(FSWorkOrderService); + + UpdateQuantities(FSWorkOrderService, ServiceLine, true); + AdditionalFieldsWereModified := true; + + SourceRecordRef.GetTable(ServiceLine); + DestinationRecordRef.GetTable(FSWorkOrderService); + end; + 'FS Bookable Resource Booking-Service Line': begin @@ -399,54 +425,9 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - if (SourceFieldRef.Record().Number = Database::"Service Line") and - (DestinationFieldRef.Record().Number = Database::"FS Work Order Product") then - case DestinationFieldRef.Name() of - FSWorkOrderProduct.FieldName(EstimateQuantity): - begin - SourceFieldRef.Record().SetTable(ServiceLine); - DestinationFieldRef.Record().SetTable(FSWorkOrderProduct); - // only update estimated quantity if the line status is estimated - if FSWorkOrderProduct.LineStatus = FSWorkOrderProduct.LineStatus::Estimated then begin - ServiceLine.Validate("Qty. to Ship", 0); - ServiceLine.Validate("Qty. to Invoice", 0); - ServiceLine.Validate("Qty. to Consume", 0); - ServiceLine.Modify(true); - end else begin - NewValue := FSWorkOrderProduct.EstimateQuantity; - IsValueFound := true; - NeedsConversion := false; - end; - SourceFieldRef.Record().GetTable(ServiceLine); - end; - end; - if (SourceFieldRef.Record().Number = Database::"Service Line") and (DestinationFieldRef.Record().Number = Database::"FS Work Order Service") then case DestinationFieldRef.Name() of - FSWorkOrderService.FieldName(EstimateDuration): - begin - SourceFieldRef.Record().SetTable(ServiceLine); - DestinationFieldRef.Record().SetTable(FSWorkOrderService); - // only update estimated quantity if the line status is estimated - if FSWorkOrderService.LineStatus = FSWorkOrderService.LineStatus::Estimated then begin - ServiceLine.Validate("Qty. to Ship", 0); - ServiceLine.Validate("Qty. to Invoice", 0); - ServiceLine.Validate("Qty. to Consume", 0); - ServiceLine.Modify(true); - end else begin - NewValue := FSWorkOrderService.EstimateDuration; - IsValueFound := true; - NeedsConversion := false; - exit; - end; - - DurationInHours := ServiceLine.Quantity; - DurationInMinutes := DurationInHours * 60; - NewValue := DurationInMinutes; - IsValueFound := true; - NeedsConversion := false; - end; FSWorkOrderService.FieldName(DurationShipped): begin SourceRecordRef := SourceFieldRef.Record(); @@ -610,18 +591,46 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; - internal procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") + internal procedure UpdateQuantities(var FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line"; ToFieldService: Boolean) begin - ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.EstimateQuantity, FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill)); - ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill) - ServiceLine."Quantity Shipped"); - ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced"); + if FSWorkOrderProduct.LineStatus = FSWorkOrderProduct.LineStatus::Estimated then begin + if ToFieldService then + FSWorkOrderProduct.EstimateQuantity := ServiceLine.Quantity + else + ServiceLine.Validate(Quantity, FSWorkOrderProduct.EstimateQuantity); + + ServiceLine.Validate("Qty. to Ship", 0); + ServiceLine.Validate("Qty. to Invoice", 0); + ServiceLine.Validate("Qty. to Consume", 0); + + if ToFieldService then + ServiceLine.Modify(true); + end else begin + ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill)); + ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderProduct.Quantity, FSWorkOrderProduct.QtyToBill) - ServiceLine."Quantity Shipped"); + ServiceLine.Validate("Qty. to Invoice", FSWorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced"); + end; end; - internal procedure UpdateQuantities(FSWorkOrderService: Record "FS Work Order Service"; var ServiceLine: Record "Service Line") + internal procedure UpdateQuantities(var FSWorkOrderService: Record "FS Work Order Service"; var ServiceLine: Record "Service Line"; ToFieldService: Boolean) begin - ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderService.EstimateDuration, FSWorkOrderService.Duration, FSWorkOrderService.DurationToBill) / 60); - ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderService.Duration, FSWorkOrderService.DurationToBill) / 60 - ServiceLine."Quantity Shipped"); - ServiceLine.Validate("Qty. to Invoice", FSWorkOrderService.DurationToBill / 60 - ServiceLine."Quantity Invoiced"); + if FSWorkOrderService.LineStatus = FSWorkOrderService.LineStatus::Estimated then begin + if ToFieldService then + FSWorkOrderService.EstimateDuration := ServiceLine.Quantity * 60 + else + ServiceLine.Validate(Quantity, FSWorkOrderService.EstimateDuration / 60); + + ServiceLine.Validate("Qty. to Ship", 0); + ServiceLine.Validate("Qty. to Invoice", 0); + ServiceLine.Validate("Qty. to Consume", 0); + + if ToFieldService then + ServiceLine.Modify(true); + end else begin + ServiceLine.Validate(Quantity, GetMaxQuantity(FSWorkOrderService.Duration, FSWorkOrderService.DurationToBill) / 60); + ServiceLine.Validate("Qty. to Ship", GetMaxQuantity(FSWorkOrderService.Duration, FSWorkOrderService.DurationToBill) / 60 - ServiceLine."Quantity Shipped"); + ServiceLine.Validate("Qty. to Invoice", FSWorkOrderService.DurationToBill / 60 - ServiceLine."Quantity Invoiced"); + end; end; internal procedure UpdateQuantities(FSBookableResourceBooking: Record "FS Bookable Resource Booking"; var ServiceLine: Record "Service Line") diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al index b9588644eb..85f261ee34 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -819,13 +819,6 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo(Quantity), - FSWorkOrderProduct.FieldNo(EstimateQuantity), - IntegrationFieldMapping.Direction::Bidirectional, - '', true, false); - InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo("Quantity Shipped"), @@ -932,13 +925,6 @@ codeunit 6611 "FS Setup Defaults" IntegrationFieldMapping.Direction::Bidirectional, '', true, false); - InsertIntegrationFieldMapping( - IntegrationTableMappingName, - ServiceLine.FieldNo(Quantity), - FSWorkOrderService.FieldNo(EstimateDuration), - IntegrationFieldMapping.Direction::Bidirectional, - '', true, false); - InsertIntegrationFieldMapping( IntegrationTableMappingName, ServiceLine.FieldNo("Quantity Shipped"), From 7294528f2fb02c20a56bbfdcbe372cc40cabbd7e Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 24 Oct 2024 15:34:07 +0200 Subject: [PATCH 47/49] updated: unit tests --- .../src/FSIntegrationTestLibrary.Codeunit.al | 8 +- .../test/src/FSIntegrationTest.Codeunit.al | 99 ++++++++++++++++--- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al b/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al index abe02e449a..a445e2d306 100644 --- a/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al @@ -37,18 +37,18 @@ codeunit 139205 "FS Integration Test Library" FSSetupDefaults.ResetConfiguration(FSConnectionSetup); end; - procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line") + procedure UpdateQuantities(FSWorkOrderProduct: Record "FS Work Order Product"; var ServiceLine: Record "Service Line"; ToFieldService: Boolean) var FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; begin - FSIntTableSubscriber.UpdateQuantities(FSWorkOrderProduct, ServiceLine); + FSIntTableSubscriber.UpdateQuantities(FSWorkOrderProduct, ServiceLine, ToFieldService); end; - procedure UpdateQuantities(FSWorkOrderService: Record "FS Work Order Service"; var ServiceLine: Record "Service Line") + procedure UpdateQuantities(FSWorkOrderService: Record "FS Work Order Service"; var ServiceLine: Record "Service Line"; ToFieldService: Boolean) var FSIntTableSubscriber: Codeunit "FS Int. Table Subscriber"; begin - FSIntTableSubscriber.UpdateQuantities(FSWorkOrderService, ServiceLine); + FSIntTableSubscriber.UpdateQuantities(FSWorkOrderService, ServiceLine, ToFieldService); end; procedure UpdateQuantities(FSBookableResourceBooking: Record "FS Bookable Resource Booking"; var ServiceLine: Record "Service Line") diff --git a/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al b/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al index 9cf038226c..b44bd42025 100644 --- a/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/test/src/FSIntegrationTest.Codeunit.al @@ -752,7 +752,7 @@ codeunit 139204 "FS Integration Test" [Test] [TransactionModel(TransactionModel::AutoRollback)] - procedure UpdateWorkOrderProductDefault() + procedure UpdateWorkOrderProductDefaultEstimated() var WorkOrderProduct: Record "FS Work Order Product"; ServiceHeader: Record "Service Header"; @@ -769,15 +769,49 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderProduct.LineStatus := WorkOrderProduct.LineStatus::Estimated; WorkOrderProduct.EstimateQuantity := 3; WorkOrderProduct.Quantity := 2; WorkOrderProduct.QtyToBill := 1; // [WHEN] Update quantities on work order lines that are not posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine, false); // [THEN] Quantities should be updated accordingly. Assert.AreEqual(WorkOrderProduct.EstimateQuantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.EstimateQuantity)); + Assert.AreEqual(0, ServiceLine."Qty. to Ship", 'Qty. to Ship should be 0'); + Assert.AreEqual(0, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be 0'); + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderProductDefaultUsed() + var + WorkOrderProduct: Record "FS Work Order Product"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderProduct.LineStatus := WorkOrderProduct.LineStatus::Used; + WorkOrderProduct.EstimateQuantity := 3; + WorkOrderProduct.Quantity := 2; + WorkOrderProduct.QtyToBill := 1; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine, false); + + // [THEN] Quantities should be updated accordingly. + Assert.AreEqual(WorkOrderProduct.Quantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.Quantity)); Assert.AreEqual(WorkOrderProduct.Quantity, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderProduct.Quantity)); Assert.AreEqual(WorkOrderProduct.QtyToBill, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderProduct.QtyToBill)); end; @@ -801,12 +835,13 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderProduct.LineStatus := WorkOrderProduct.LineStatus::Used; WorkOrderProduct.EstimateQuantity := 3; WorkOrderProduct.Quantity := 10; WorkOrderProduct.QtyToBill := 1; // [WHEN] Update quantities on work order lines that are not posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine, false); // [THEN] Quantities should be updated accordingly. Qty to Ship increases Quantity. Assert.AreEqual(WorkOrderProduct.Quantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.Quantity)); @@ -833,12 +868,13 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderProduct.LineStatus := WorkOrderProduct.LineStatus::Used; WorkOrderProduct.EstimateQuantity := 3; WorkOrderProduct.Quantity := 2; WorkOrderProduct.QtyToBill := 10; // [WHEN] Update quantities on work order lines that are not posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine, false); // [THEN] Quantities should be updated accordingly. Qty to Invoice increases all other quantities. Assert.AreEqual(WorkOrderProduct.QtyToBill, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.QtyToBill)); @@ -865,6 +901,7 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderProduct.LineStatus := WorkOrderProduct.LineStatus::Used; WorkOrderProduct.EstimateQuantity := 5; WorkOrderProduct.Quantity := 3; WorkOrderProduct.QtyToBill := 2; @@ -872,14 +909,46 @@ codeunit 139204 "FS Integration Test" ServiceLine."Quantity Invoiced" := 1; // [WHEN] Update quantities on work order lines that are partly posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderProduct, ServiceLine, false); // [THEN] Quantities should be updated accordingly. Posted quantities are considered. - Assert.AreEqual(WorkOrderProduct.EstimateQuantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.EstimateQuantity)); + Assert.AreEqual(WorkOrderProduct.Quantity, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderProduct.Quantity)); Assert.AreEqual(WorkOrderProduct.Quantity - ServiceLine."Quantity Shipped", ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderProduct.Quantity - ServiceLine."Quantity Shipped")); Assert.AreEqual(WorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced", ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderProduct.QtyToBill - ServiceLine."Quantity Invoiced")); end; + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure UpdateWorkOrderServiceDefaultEstimated() + var + WorkOrderService: Record "FS Work Order Service"; + ServiceHeader: Record "Service Header"; + ServiceLine: Record "Service Line"; + begin + // [FEATURE] [UI] Service Order Integration + // [SCENARIO] User updates quantities on work order lines that are not posted in BC. + // [GIVEN] FS Connection Setup, where "Is Enabled" = Yes. + Initialize(); + InitSetup(true, ''); + + // [GIVEN] Existing Service Line. + LibraryService.CreateServiceHeader(ServiceHeader, ServiceHeader."Document Type"::Order, LibrarySales.CreateCustomerNo()); + LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); + + // [GIVEN] Quantities on work order lines. + WorkOrderService.LineStatus := WorkOrderService.LineStatus::Estimated; + WorkOrderService.EstimateDuration := 180; + WorkOrderService.Duration := 120; + WorkOrderService.DurationToBill := 60; + + // [WHEN] Update quantities on work order lines that are not posted in BC. + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine, false); + + // [THEN] Quantities should be updated accordingly. + Assert.AreEqual(WorkOrderService.EstimateDuration / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.EstimateDuration / 60)); + Assert.AreEqual(0, ServiceLine."Qty. to Ship", 'Qty. to Ship should be 0'); + Assert.AreEqual(0, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be 0'); + end; [Test] [TransactionModel(TransactionModel::AutoRollback)] @@ -900,15 +969,16 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderService.LineStatus := WorkOrderService.LineStatus::Used; WorkOrderService.EstimateDuration := 180; WorkOrderService.Duration := 120; WorkOrderService.DurationToBill := 60; // [WHEN] Update quantities on work order lines that are not posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine, false); // [THEN] Quantities should be updated accordingly. - Assert.AreEqual(WorkOrderService.EstimateDuration / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.EstimateDuration / 60)); + Assert.AreEqual(WorkOrderService.Duration / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.Duration / 60)); Assert.AreEqual(WorkOrderService.Duration / 60, ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderService.Duration / 60)); Assert.AreEqual(WorkOrderService.DurationToBill / 60, ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderService.DurationToBill / 60)); end; @@ -932,12 +1002,13 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderService.LineStatus := WorkOrderService.LineStatus::Used; WorkOrderService.EstimateDuration := 180; WorkOrderService.Duration := 240; WorkOrderService.DurationToBill := 60; // [WHEN] Update quantities on work order lines that are not posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine, false); // [THEN] Quantities should be updated accordingly. Qty to Ship increases Quantity. Assert.AreEqual(WorkOrderService.Duration / 60, ServiceLine.Quantity, 'Duration should be ' + Format(WorkOrderService.Duration / 60)); @@ -964,12 +1035,13 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderService.LineStatus := WorkOrderService.LineStatus::Used; WorkOrderService.EstimateDuration := 180; WorkOrderService.Duration := 120; WorkOrderService.DurationToBill := 240; // [WHEN] Update quantities on work order lines that are not posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine, false); // [THEN] Quantities should be updated accordingly. Qty to Invoice increases all other quantities. Assert.AreEqual(WorkOrderService.DurationToBill / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.DurationToBill / 60)); @@ -996,6 +1068,7 @@ codeunit 139204 "FS Integration Test" LibraryService.CreateServiceLine(ServiceLine, ServiceHeader, ServiceLine.Type::Item, LibraryInventory.CreateItemNo()); // [GIVEN] Quantities on work order lines. + WorkOrderService.LineStatus := WorkOrderService.LineStatus::Used; WorkOrderService.EstimateDuration := 300; WorkOrderService.Duration := 180; WorkOrderService.DurationToBill := 120; @@ -1003,10 +1076,10 @@ codeunit 139204 "FS Integration Test" ServiceLine."Quantity Invoiced" := 1; // [WHEN] Update quantities on work order lines that are partly posted in BC. - FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine); + FSIntegrationTestLibrary.UpdateQuantities(WorkOrderService, ServiceLine, false); // [THEN] Quantities should be updated accordingly. Posted Quantities are considered. - Assert.AreEqual(WorkOrderService.EstimateDuration / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.EstimateDuration / 60, ServiceLine.Quantity)); + Assert.AreEqual(WorkOrderService.Duration / 60, ServiceLine.Quantity, 'Quantity should be ' + Format(WorkOrderService.Duration / 60)); Assert.AreEqual(WorkOrderService.Duration / 60 - ServiceLine."Quantity Shipped", ServiceLine."Qty. to Ship", 'Qty. to Ship should be ' + Format(WorkOrderService.Duration / 60 - ServiceLine."Quantity Shipped")); Assert.AreEqual(WorkOrderService.DurationToBill / 60 - ServiceLine."Quantity Invoiced", ServiceLine."Qty. to Invoice", 'Qty. to Invoice should be ' + Format(WorkOrderService.DurationToBill / 60 - ServiceLine."Quantity Invoiced", ServiceLine."Qty. to Invoice")); end; @@ -1486,7 +1559,7 @@ codeunit 139204 "FS Integration Test" FSWorkOrderService.Insert(); // [WHEN] Update quantities on booking lines that are not posted in BC. - FSIntegrationTestLibrary.UpdateWorkOrderService(ServiceLineArchive, FSWorkORderService); + FSIntegrationTestLibrary.UpdateWorkOrderService(ServiceLineArchive, FSWorkOrderService); // [THEN] Quantities should be updated accordingly. Assert.AreEqual((ServiceLineArchive."Qty. to Ship" + ServiceLineArchive."Quantity Shipped") * 60, FSWorkOrderService.DurationShipped, 'Duration should be ' + Format(ServiceLineArchive."Qty. to Ship" + ServiceLineArchive."Quantity Shipped")); From b621466c07e61216684749025790edf5ffe02999 Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Thu, 24 Oct 2024 17:44:52 +0200 Subject: [PATCH 48/49] added: explicit validation of customer related fields --- .../src/Codeunits/FSIntTableSubscriber.Codeunit.al | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al index eefb287663..59b75644e7 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -883,6 +883,7 @@ codeunit 6610 "FS Int. Table Subscriber" end; 'FS Work Order-Service Header': begin + ValidateServiceHeaderAfterInsert(DestinationRecordRef); ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderProduct(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); ResetServiceOrderLineFromFSWorkOrderService(SourceRecordRef, DestinationRecordRef, ArchivedServiceOrders); @@ -992,6 +993,16 @@ codeunit 6610 "FS Int. Table Subscriber" end; end; + local procedure ValidateServiceHeaderAfterInsert(DestinationRecordRef: RecordRef) + var + ServiceHeader: Record "Service Header"; + begin + DestinationRecordRef.SetTable(ServiceHeader); + ServiceHeader.Validate("Customer No."); // explicit recalculation, as InitRecord() was called after setting the customer + ServiceHeader.Modify(true); + DestinationRecordRef.GetTable(ServiceHeader); + end; + local procedure ResetServiceOrderItemLineFromFSWorkOrderIncident(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; var ArchivedServiceOrders: List of [Code[20]]) var ServiceHeader: Record "Service Header"; From d603527e51a62e3c44eb40182e04f71877f49aad Mon Sep 17 00:00:00 2001 From: Niklas Plakolb Date: Wed, 6 Nov 2024 08:48:57 +0100 Subject: [PATCH 49/49] added: paid status in posted service invoice api --- .../Pages/FSPostedServiceInvoiceAPI.Page.al | 26 +++++++++++++++++++ .../Permissions/FSObjects.PermissionSet.al | 1 - 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al index 8fa2842c85..39cf297207 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSPostedServiceInvoiceAPI.Page.al @@ -12,6 +12,8 @@ using Microsoft.Foundation.Shipping; using Microsoft.Integration.Graph; using Microsoft.Service.Document; using Microsoft.API.V2; +using Microsoft.Integration.Entity; +using Microsoft.Sales.Receivables; page 6618 "FS Posted Service Invoice API" { @@ -249,6 +251,11 @@ page 6618 "FS Posted Service Invoice API" Caption = 'Total Amount Including Tax'; Editable = false; } + field(status; Status) + { + Caption = 'Status'; + Editable = false; + } field(lastModifiedDateTime; Rec.SystemModifiedAt) { Caption = 'Last Modified Date'; @@ -297,6 +304,7 @@ page 6618 "FS Posted Service Invoice API" Clear(PaymentTerms); if not ShipmentMethod.Get(Rec."Shipment Method Code") then Clear(ShipmentMethod); + CalculateStatus(); end; var @@ -308,4 +316,22 @@ page 6618 "FS Posted Service Invoice API" ShipmentMethod: Record "Shipment Method"; CurrencyCode: Code[10]; CachedCurrencyCode: Code[10]; + Status: Enum "Invoice Entity Aggregate Status"; + + local procedure CalculateStatus() + var + CustLedgerEntry: Record "Cust. Ledger Entry"; + begin + CustLedgerEntry.SetCurrentKey("Document No."); + CustLedgerEntry.SetRange("Document No.", Rec."No."); + CustLedgerEntry.SetRange("Document Type", CustLedgerEntry."Document Type"::Invoice); + CustLedgerEntry.SetRange("Posting Date", Rec."Posting Date"); + CustLedgerEntry.SetRange(Open, true); + + if CustLedgerEntry.IsEmpty() then + Status := Status::Paid + else + Status := Status::Open; + end; + } \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al index dd1135d0ae..f658f8093d 100644 --- a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al @@ -17,7 +17,6 @@ permissionset 6612 "FS - Objects" codeunit "FS Upgrade" = X, codeunit "FS Integration Mgt." = X, codeunit "FS Int. Table Subscriber" = X, - codeunit "FS Integration Mgt." = X, codeunit "FS Lookup FS Tables" = X, codeunit "FS Setup Defaults" = X, page "FS Bookable Resource List" = X,