From b462305a0f9479b1e09b1f8543636528cb732004 Mon Sep 17 00:00:00 2001 From: Jesper Schulz-Wedde Date: Tue, 28 May 2024 15:12:17 +0200 Subject: [PATCH] Syncing with version 25.0.20074.0 (#26546) Fixes [AB#420000](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/420000) --------- Co-authored-by: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> --- .github/AL-Go-Settings.json | 2 +- .../MatchBankPaymentHandlerCZZ.Codeunit.al | 13 + .../GenJnlPostLineHandlerCZL.Codeunit.al | 24 + .../app/Src/Reports/PurchaseOrder.rdl | 12 - .../app/Src/Reports/PurchaseOrderEmail.docx | Bin 23912 -> 23848 bytes .../app/Src/Reports/SalesInvoice.rdl | 9 - .../app/Src/Reports/SalesInvoiceEmail.docx | Bin 24513 -> 24470 bytes .../app/AppResources/FR_DataExchDefMap.xml | 4 +- .../src/IntrastatReportFRUpgrade.Codeunit.al | 33 + .../Codeunit/GSTBaseValidation.Codeunit.al | 22 + .../GSTPurchaseLineExt.TableExt.al | 71 +- .../GSTTransferOrderReceipt.Codeunit.al | 7 +- .../GSTTransferOrderShipment.Codeunit.al | 6 +- .../SubcontractingValidations.Codeunit.al | 4 +- .../src/Table/SubOrderCompListVend.Table.al | 2 +- .../src/Table/SubOrderComponentList.Table.al | 2 +- .../src/GSTStockTransferTests.Codeunit.al | 106 ++ .../src/GSTSubcontracting.Codeunit.al | 86 ++ .../TDSJournalsSubscribers.Codeunit.al | 8 + .../IRS1099BaseAppSubscribers.Codeunit.al | 10 +- .../test/src/IRS1099DocumentTests.Codeunit.al | 67 + .../src/codeunits/BankDepositPost.Codeunit.al | 28 +- .../app/src/pages/BankDepositSubform.Page.al | 5 + .../pages/PostedBankDepositSubform.Page.al | 5 + .../src/tables/PostedBankDepositLine.Table.al | 4 + .../src/BankDepositPostingTests.Codeunit.al | 62 + .../Copilot/EDocPOCopilotProp.Page.al | 1 + .../EDocOrderLineMatching.Page.al | 3 +- .../FixedAsset/FixedAssetDetailsExcel.xlsx | Bin 0 -> 50963 bytes .../EXRFixedAssetDetailsExcel.Report.al | 116 ++ .../EXRTrialBalanceBudgetExcel.Report.al | 78 +- .../Financials/EXRTrialBalanceExcel.Report.al | 78 +- .../EXRTrialBalbyPeriodExcel.Report.al | 4 +- .../src/Financials/TrialBalance.Codeunit.al | 13 +- .../app/.objidconfig | 0 .../app/ExtensionLogo.png | Bin 0 -> 5446 bytes Apps/W1/FieldServiceIntegration/app/app.json | 45 + .../FSAssistedSetupSubscriber.Codeunit.al | 50 + .../FSDataClassification.Codeunit.al | 39 + .../app/src/Codeunits/FSInstall.Codeunit.al | 131 ++ .../FSIntTableSubscriber.Codeunit.al | 1200 +++++++++++++++++ .../Codeunits/FSIntegrationMgt.Codeunit.al | 217 +++ .../Codeunits/FSLookupFSTables.Codeunit.al | 81 ++ .../src/Codeunits/FSSetupDefaults.Codeunit.al | 774 +++++++++++ .../src/Enums/FSWorkOrderLinePostRule.Enum.al | 24 + .../Enums/FSWorkOrderLineSynchRule.Enum.al} | 23 +- .../Page Extensions/FSJobJournal.PageExt.al | 33 + .../FSJobProjectManagerRC.PageExt.al | 42 + .../Page Extensions/FSJobTaskCard.PageExt.al | 167 +++ .../Page Extensions/FSJobTaskLines.PageExt.al | 179 +++ .../Page Extensions/FSJobTaskList.PageExt.al | 179 +++ .../FSProjectManagerActivities.PageExt.al | 51 + .../Page Extensions/FSResourceCard.PageExt.al | 213 +++ .../Page Extensions/FSResourceList.PageExt.al | 210 +++ .../FSServiceConnections.PageExt.al | 17 + .../FSServiceItemCard.PageExt.al | 186 +++ .../FSServiceItemList.PageExt.al | 200 +++ .../FSServiceManagerRC.PageExt.al | 42 + .../src/Pages/FSBookableResourceList.Page.al | 166 +++ .../app/src/Pages/FSConnectionSetup.Page.al | 505 +++++++ .../src/Pages/FSConnectionSetupWizard.Page.al | 517 +++++++ .../app/src/Pages/FSCustomerAssetList.Page.al | 184 +++ .../app/src/Pages/FSLocationList.PageExt.al | 172 +++ .../FSD365AUTOMATION.PermissionSetExt.al | 12 + .../FSD365DYNCRMMGT.PermissionSetExt.al | 12 + .../FSD365DYNCRMREAD.PermissionSetExt.al | 12 + .../FSD365READ.PermissionSetExt.al} | 11 +- .../FSD365TEAMMEMBER.PermissionSetExt.al | 12 + .../src/Permissions/FSEdit.PermissionSet.al | 30 + .../FSINTELLIGENTCLOUD.PermissionSetExt.al | 12 + .../Permissions/FSObjects.PermissionSet.al | 39 + .../src/Permissions/FSRead.PermissionSet.al | 30 + .../src/Table Extensions/FSJob.TableExt.al | 69 + .../src/Table Extensions/FSJobCue.TableExt.al | 28 + .../Table Extensions/FSJobTask.TableExt.al | 22 + .../Table Extensions/FSLocation.TableExt.al | 22 + .../FSServiceItem.TableExt.al | 22 + .../src/Tables/FSBookableResource.Table.al | 501 +++++++ .../Tables/FSBookableResourceBooking.Table.al | 636 +++++++++ .../FSBookableResourceBookingHdr.Table.al | 308 +++++ .../app/src/Tables/FSConnectionSetup.Table.al | 1054 +++++++++++++++ .../app/src/Tables/FSCustomerAsset.Table.al | 423 ++++++ .../Tables/FSCustomerAssetCategory.Table.al | 214 +++ .../app/src/Tables/FSProjectTask.Table.al | 259 ++++ .../app/src/Tables/FSResourcePayType.Table.al | 261 ++++ .../app/src/Tables/FSWarehouse.Table.al | 222 +++ .../app/src/Tables/FSWorkOrder.Table.al | 906 +++++++++++++ .../src/Tables/FSWorkOrderIncident.Table.al | 312 +++++ .../src/Tables/FSWorkOrderProduct.Table.al | 726 ++++++++++ .../src/Tables/FSWorkOrderService.Table.al | 767 +++++++++++ .../src/Tables/FSWorkOrderSubstatus.Table.al | 233 ++++ .../app/src/Tables/FSWorkOrderType.Table.al | 239 ++++ .../test library/ExtensionLogo.png | Bin 0 -> 5446 bytes .../test library/app.json | 43 + .../src/FSIntegrationTestLibrary.Codeunit.al | 30 + .../ExecuteNonCompanyUpgrade.Codeunit.al | 3 - .../Items/GPItemMigrator.codeunit.al | 3 +- Apps/W1/MasterDataManagement/app/app.json | 2 +- .../MSUniversalPrintAdmin.PermissionSet.al | 2 +- .../UniversalPrintEdit.PermissionSet.al | 2 +- .../UniversalPrintObjects.PermissionSet.al | 2 +- .../UniversalPrintRead.PermissionSet.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- ...icrosoftUniversalPrint.permissionsetext.al | 2 +- .../src/AddUniversalPrintersWizard.Page.al | 138 +- .../UniversalPrintDocumentReady.Codeunit.al | 52 +- .../src/UniversalPrintGraphHelper.Codeunit.al | 150 ++- .../src/UniversalPrintShareBuffer.Table.al | 8 +- .../app/src/UniversalPrintSharesList.Page.al | 2 +- .../src/UniversalPrinterManagement.PageExt.al | 4 +- .../src/UniversalPrinterOrientation.Enum.al | 2 +- .../app/src/UniversalPrinterPaperUnit.Enum.al | 2 +- .../app/src/UniversalPrinterSettings.Page.al | 44 +- .../app/src/UniversalPrinterSettings.Table.al | 4 +- .../app/src/UniversalPrinterSetup.Codeunit.al | 101 +- .../app/src/UniversalPrinterTrayList.Page.al | 2 +- .../src/Support/MSQBODataMigration.Page.al | 5 + Apps/W1/ReportLayouts/app/ExtensionLogo.png | Bin 7091 -> 0 bytes Apps/W1/ReportLayouts/app/app.json | 38 - .../app/src/ReportLayoutEditDialog.page.al | 164 --- .../app/src/ReportLayoutNewDialog.page.al | 156 --- .../app/src/ReportLayouts.page.al | 387 ------ .../app/src/ReportLayoutsImpl.codeunit.al | 526 -------- Apps/W1/ReportLayouts/test/ExtensionLogo.png | Bin 5095 -> 0 bytes Apps/W1/ReportLayouts/test/app.json | 55 - Apps/W1/ReportLayouts/test/appsourcecop.json | 3 - Apps/W1/ReportLayouts/test/mylayout.rdl | 77 -- .../codeunits/ReportLayoutsTest.Codeunit.al | 380 ------ .../reports/TestReportLayoutsReport.Report.al | 31 - .../app/src/Base/Pages/ShpfyShopCard.Page.al | 7 + .../app/src/Base/Tables/ShpfyShop.Table.al | 6 + .../Codeunits/ShpfyJsonHelper.Codeunit.al | 2 +- .../Codeunits/ShpfyImportOrder.Codeunit.al | 3 +- .../ShpfyObjects.PermissionSet.al | 350 ++++- .../Reports/ShpfySuggestPayments.Report.al | 20 +- .../test/Base/ShpfyInitializeTest.Codeunit.al | 11 + .../Helpers/ShpfyJsonHelperTest.Codeunit.al | 39 +- .../Products/ShpfyCreateItemTest.Codeunit.al | 7 + .../app/src/APIs/SustAccSubcategory.Page.al | 66 + .../src/APIs/SustAccountCategories.Page.al | 70 + .../src/APIs/SustainabilityAccounts.Page.al | 59 + .../APIs/SustainabilityJournalLine.Page.al | 123 ++ .../APIs/SustainabilityLedgEntries.Page.al | 102 ++ .../SustainabilityCalcMgt.Codeunit.al | 6 + .../Journal/SustainabilityJnlLine.Table.al | 8 + .../SustainabilityObjects.permissionset.al | 6 + .../SustainabilityJnlCheck.Codeunit.al | 8 +- .../app/src/TransactionStorageABS.Codeunit.al | 38 +- .../src/API/VATGroupSubmissionStatus.query.al | 2 +- .../VATGroupCommunication.Codeunit.al | 22 +- .../VATGroupSerialization.Codeunit.al | 4 +- .../src/Enums/VATGroupAuthTypeOnPrem.Enum.al | 6 + .../src/Enums/VATGroupAuthTypeSaas.Enum.al | 3 + .../app/src/Pages/VATGroupSetupGuide.Page.al | 46 +- .../Pages/VATReportSetupExtension.PageExt.al | 36 +- .../VATReportSetupExtension.TableExt.al | 1 + .../src/VATGroupSetupGuideTest.Codeunit.al | 154 +-- .../src/VATGroupSetupPageTest.Codeunit.al | 97 +- 164 files changed, 15125 insertions(+), 2539 deletions(-) create mode 100644 Apps/W1/ExcelReports/app/ReportLayouts/Excel/FixedAsset/FixedAssetDetailsExcel.xlsx create mode 100644 Apps/W1/ExcelReports/app/src/Financials/EXRFixedAssetDetailsExcel.Report.al rename Apps/W1/{ReportLayouts => FieldServiceIntegration}/app/.objidconfig (100%) create mode 100644 Apps/W1/FieldServiceIntegration/app/ExtensionLogo.png create mode 100644 Apps/W1/FieldServiceIntegration/app/app.json create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSAssistedSetupSubscriber.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSDataClassification.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSInstall.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Enums/FSWorkOrderLinePostRule.Enum.al rename Apps/W1/{ReportLayouts/app/Permissions/ReportLayoutsObjects.permissionset.al => FieldServiceIntegration/app/src/Enums/FSWorkOrderLineSynchRule.Enum.al} (50%) create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobJournal.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobProjectManagerRC.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskCard.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskLines.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskList.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSProjectManagerActivities.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceCard.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceList.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceConnections.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemCard.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemList.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceManagerRC.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSBookableResourceList.Page.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSCustomerAssetList.Page.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Pages/FSLocationList.PageExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365AUTOMATION.PermissionSetExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMMGT.PermissionSetExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMREAD.PermissionSetExt.al rename Apps/W1/{ReportLayouts/app/Entitlements/ReportLayouts.entitlement.al => FieldServiceIntegration/app/src/Permissions/FSD365READ.PermissionSetExt.al} (71%) create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365TEAMMEMBER.PermissionSetExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSINTELLIGENTCLOUD.PermissionSetExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJob.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobCue.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobTask.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSLocation.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItem.TableExt.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResource.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBookingHdr.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAsset.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAssetCategory.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSProjectTask.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSResourcePayType.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderSubstatus.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al create mode 100644 Apps/W1/FieldServiceIntegration/test library/ExtensionLogo.png create mode 100644 Apps/W1/FieldServiceIntegration/test library/app.json create mode 100644 Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al delete mode 100644 Apps/W1/ReportLayouts/app/ExtensionLogo.png delete mode 100644 Apps/W1/ReportLayouts/app/app.json delete mode 100644 Apps/W1/ReportLayouts/app/src/ReportLayoutEditDialog.page.al delete mode 100644 Apps/W1/ReportLayouts/app/src/ReportLayoutNewDialog.page.al delete mode 100644 Apps/W1/ReportLayouts/app/src/ReportLayouts.page.al delete mode 100644 Apps/W1/ReportLayouts/app/src/ReportLayoutsImpl.codeunit.al delete mode 100644 Apps/W1/ReportLayouts/test/ExtensionLogo.png delete mode 100644 Apps/W1/ReportLayouts/test/app.json delete mode 100644 Apps/W1/ReportLayouts/test/appsourcecop.json delete mode 100644 Apps/W1/ReportLayouts/test/mylayout.rdl delete mode 100644 Apps/W1/ReportLayouts/test/src/codeunits/ReportLayoutsTest.Codeunit.al delete mode 100644 Apps/W1/ReportLayouts/test/src/reports/TestReportLayoutsReport.Report.al create mode 100644 Apps/W1/Sustainability/app/src/APIs/SustAccSubcategory.Page.al create mode 100644 Apps/W1/Sustainability/app/src/APIs/SustAccountCategories.Page.al create mode 100644 Apps/W1/Sustainability/app/src/APIs/SustainabilityAccounts.Page.al create mode 100644 Apps/W1/Sustainability/app/src/APIs/SustainabilityJournalLine.Page.al create mode 100644 Apps/W1/Sustainability/app/src/APIs/SustainabilityLedgEntries.Page.al diff --git a/.github/AL-Go-Settings.json b/.github/AL-Go-Settings.json index 281e831311..667cbffc6b 100644 --- a/.github/AL-Go-Settings.json +++ b/.github/AL-Go-Settings.json @@ -5,7 +5,7 @@ "runs-on": "windows-latest", "cacheImageName": "", "UsePsSession": false, - "artifact": "https://bcinsider-fvh2ekdjecfjd6gk.b02.azurefd.net/sandbox/25.0.19547.0/base", + "artifact": "https://bcinsider-fvh2ekdjecfjd6gk.b02.azurefd.net/sandbox/25.0.20074.0/base", "country": "base", "useProjectDependencies": true, "repoVersion": "25.0", diff --git a/Apps/CZ/AdvancePaymentsLocalization/app/Src/Codeunits/MatchBankPaymentHandlerCZZ.Codeunit.al b/Apps/CZ/AdvancePaymentsLocalization/app/Src/Codeunits/MatchBankPaymentHandlerCZZ.Codeunit.al index 4b33288a23..59aff2de62 100644 --- a/Apps/CZ/AdvancePaymentsLocalization/app/Src/Codeunits/MatchBankPaymentHandlerCZZ.Codeunit.al +++ b/Apps/CZ/AdvancePaymentsLocalization/app/Src/Codeunits/MatchBankPaymentHandlerCZZ.Codeunit.al @@ -41,6 +41,7 @@ codeunit 31390 "Match Bank Payment Handler CZZ" SalesAdvLetterHeaderCZZ: Record "Sales Adv. Letter Header CZZ"; CustomerBankAccount: Record "Customer Bank Account"; begin + OnBeforeFillMatchBankPaymentBufferSalesAdvance(SalesAdvLetterHeaderCZZ, SearchRuleLineCZB, TempMatchBankPaymentBufferCZB, GenJournalLine); SalesAdvLetterHeaderCZZ.SetRange(Status, SalesAdvLetterHeaderCZZ.Status::"To Pay"); if (GenJournalLine."Account Type" = GenJournalLine."Account Type"::Customer) and (GenJournalLine."Account No." <> '') @@ -96,6 +97,7 @@ codeunit 31390 "Match Bank Payment Handler CZZ" PurchAdvLetterHeaderCZZ: Record "Purch. Adv. Letter Header CZZ"; VendorBankAccount: Record "Vendor Bank Account"; begin + OnBeforeFillMatchBankPaymentBufferPurchaseAdvance(PurchAdvLetterHeaderCZZ, SearchRuleLineCZB, TempMatchBankPaymentBufferCZB, GenJournalLine); PurchAdvLetterHeaderCZZ.SetRange(Status, PurchAdvLetterHeaderCZZ.Status::"To Pay"); if (GenJournalLine."Account Type" = GenJournalLine."Account Type"::Vendor) and (GenJournalLine."Account No." <> '') @@ -181,4 +183,15 @@ codeunit 31390 "Match Bank Payment Handler CZZ" AdvanceSearchRuleLineCZB.Validate("Search Scope", AdvanceSearchRuleLineCZB."Search Scope"::"Advance CZZ"); AdvanceSearchRuleLineCZB.Insert(true); end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeFillMatchBankPaymentBufferPurchaseAdvance(var PurchAdvLetterHeaderCZZ: Record "Purch. Adv. Letter Header CZZ"; SearchRuleLineCZB: Record "Search Rule Line CZB"; TempMatchBankPaymentBufferCZB: Record "Match Bank Payment Buffer CZB"; GenJournalLine: Record "Gen. Journal Line") + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeFillMatchBankPaymentBufferSalesAdvance(var SalesAdvLetterHeaderCZZ: Record "Sales Adv. Letter Header CZZ"; SearchRuleLineCZB: Record "Search Rule Line CZB"; TempMatchBankPaymentBufferCZB: Record "Match Bank Payment Buffer CZB"; GenJournalLine: Record "Gen. Journal Line") + begin + end; + } diff --git a/Apps/CZ/CoreLocalizationPack/app/Src/Codeunits/GenJnlPostLineHandlerCZL.Codeunit.al b/Apps/CZ/CoreLocalizationPack/app/Src/Codeunits/GenJnlPostLineHandlerCZL.Codeunit.al index 41ad74c344..74e4e35ee5 100644 --- a/Apps/CZ/CoreLocalizationPack/app/Src/Codeunits/GenJnlPostLineHandlerCZL.Codeunit.al +++ b/Apps/CZ/CoreLocalizationPack/app/Src/Codeunits/GenJnlPostLineHandlerCZL.Codeunit.al @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.Finance.GeneralLedger.Posting; +using Microsoft.Sales.Customer; using Microsoft.Finance.GeneralLedger.Journal; using Microsoft.Finance.GeneralLedger.Ledger; using Microsoft.Finance.GeneralLedger.Setup; @@ -375,6 +376,29 @@ codeunit 31315 "Gen.Jnl. Post Line Handler CZL" IsHandled := PersistConfirmResponseCZL.GetPersistentResponse(); end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Line", 'OnBeforePostDtldCVLedgEntry', '', false, false)] + local procedure OnBeforePostDtldCVLedgEntry(sender: Codeunit "Gen. Jnl.-Post Line"; var GenJournalLine: Record "Gen. Journal Line"; var DetailedCVLedgEntryBuffer: Record "Detailed CV Ledg. Entry Buffer"; var AccNo: Code[20]; var IsHandled: Boolean; AddCurrencyCode: Code[10]; MultiplePostingGroups: Boolean) + var + CustomerPostingGroup: Record "Customer Posting Group"; + OldCorrection: Boolean; + begin + if IsHandled then + exit; + + if MultiplePostingGroups and + (DetailedCVLedgEntryBuffer."Entry Type" = DetailedCVLedgEntryBuffer."Entry Type"::Application) + then begin + CustomerPostingGroup.Get(GenJournalLine."Posting Group"); + if AccNo = CustomerPostingGroup.GetReceivablesAccount() then begin + OldCorrection := GenJournalLine.Correction; + GenJournalLine.Correction := true; + sender.CreateGLEntry(GenJournalLine, AccNo, DetailedCVLedgEntryBuffer."Amount (LCY)", 0, DetailedCVLedgEntryBuffer."Currency Code" = AddCurrencyCode); + GenJournalLine.Correction := OldCorrection; + IsHandled := true; + end; + end; + end; + [IntegrationEvent(false, false)] local procedure OnBeforeUpdateVATAmountOnAfterInitVAT(var GenJournalLine: Record "Gen. Journal Line"; var GLEntry: Record "G/L Entry"; var IsHandled: Boolean) begin diff --git a/Apps/CZ/CoreLocalizationPack/app/Src/Reports/PurchaseOrder.rdl b/Apps/CZ/CoreLocalizationPack/app/Src/Reports/PurchaseOrder.rdl index d94cb4a4ff..c62fd71227 100644 --- a/Apps/CZ/CoreLocalizationPack/app/Src/Reports/PurchaseOrder.rdl +++ b/Apps/CZ/CoreLocalizationPack/app/Src/Reports/PurchaseOrder.rdl @@ -4376,9 +4376,6 @@ End Function UoMLbl - - CreatorLbl - SubtotalLbl @@ -4619,15 +4616,6 @@ End Function InvDiscountAmount_PurchaseLineFormat - - FullName_Employee - - - PhoneNo_Employee - - - CompanyEMail_Employee - DataSource diff --git a/Apps/CZ/CoreLocalizationPack/app/Src/Reports/PurchaseOrderEmail.docx b/Apps/CZ/CoreLocalizationPack/app/Src/Reports/PurchaseOrderEmail.docx index 929230931812165f0bb64edc22a7eff36afdf559..b8e07368e47d99079eeeaf72d9adebf9cc325de4 100644 GIT binary patch delta 2008 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 D6{jbc diff --git a/Apps/CZ/CoreLocalizationPack/app/Src/Reports/SalesInvoice.rdl b/Apps/CZ/CoreLocalizationPack/app/Src/Reports/SalesInvoice.rdl index 366384d979..cafdadfa8f 100644 --- a/Apps/CZ/CoreLocalizationPack/app/Src/Reports/SalesInvoice.rdl +++ b/Apps/CZ/CoreLocalizationPack/app/Src/Reports/SalesInvoice.rdl @@ -5714,15 +5714,6 @@ End Function VATLbl - - PrepayedLbl - - - TotalAfterPrepayedLbl - - - PaymentsLbl - DisplayAdditionalFeeNote diff --git a/Apps/CZ/CoreLocalizationPack/app/Src/Reports/SalesInvoiceEmail.docx b/Apps/CZ/CoreLocalizationPack/app/Src/Reports/SalesInvoiceEmail.docx index ea06d60656a10b554b08471d430962790c3eb96b..2ba5363722c5f3685e0233cdf64f3bbf7782ea40 100644 GIT binary patch delta 2384 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 Cti}=m delta 2469 zcmV;W30n4+zX8F&0kGR93R7JutrQ6W0J2q+{^0nfhCOQCDh1 zeN_`RQ*(8xZq&86v<}zE$6- z9rad!odDMFAUy;5UqN@OKQGiD+R`E9VH9seN%|O zl|*t>oUre~h1LfJTU~!OjKz9_rENa%CBR z_u=m;XkweW)}za@!+^KvT3a7Bmhn27$K~`=BjNl2QI5g-1o}1XMP`LEHs!phKI%F@ z)9u*DbJ6?~tk8bxKL)Qf;#^|Ki3RajYoS%Q zmUXFgaqPc?@ft^0Jj0a;^>Gb`Y;opcQQ}m>;-o`FT-Tu-j$e1qj7=7| zefq?*k3sAYu6Fz#pfJtY4JGq`ShU|0rg2!@9*-|UyE|bThsEva_!6|26Q*%k-2RO( zK|42L8i&R0*7y>%PZOqbSn=MBk)Zt&H`cjy7cAP+ z7h+6WpLO!cqt-$Xzp#dXdbI1g=i{ShRa>`;ZeK6|0%?>}gVCaeJlm}GGY;#eAEVWq ze1w=YG}BW3XF9p^C1U-%)wuCSxBbt0?Lk<4p21CNN^Q8Ta}s%b63^xdTl1uaxd<2g zUflhjN60oO#&)%2=sf!{KW8cB%@-~AUh8~<^1RY(Kt7%Dd5toEF0Kdp^b~Hqg5;!d zZOEsmaO2e^Cxz=oK0SpSuPo_Q%#5eCGoc=B;@D$-oF`!)HV&)T%Tn)3w|cNe;a-_- zg_d78;X{G4XVhT)RR;qiG7@Z|6WN)}7aGT>6peS=uK=5~ZE zV*4@5w(z4)9igVUy{SZ6XQ8}?4$Xq z7B7_M5Nqd-En1dQ-%PayBOJN|J8J})B#2Or~j}~I&H2Jfjbe=_( zoFYl?an{6t<(PTmI9BWNmXGOm%pBLnI4l>8ku%7%=bCXE@rfmUtdEd~C+^7kT-&CT zcX^-Fi-0`)S&a26EdQ~1BRtMWh`l$0=dt`2p!#v;w_~R{J9*x6l(W0}W7Hd3ne4h$`S0^<{^*VGqQE!Roe5hAYcyl`J zg-Up>nzRelYL&bD-4k>98`!V)o>{mTV|Dkn9=-#2>9o3ZOSO2dnsmXtb6-?;8`zfR2oyRG9l>UOp2p|DqcKO7f~@w9bZrT@>Csy*Z5BuJ z56fnM1WLPabu|@6MNMth>$+aGSRU<5s^)r;`uwb`!Tbs*t~Hsw>w(llYUU=>mh<{Q z_=I)Pq9x}ltMPXbLHITX$}RLeaOEfmhMyhmQH(;jVq+IjA=p7#j%*Uef&?n#Z7>p`8ham>Rw z?8j*JO4cGu?_An4l1kb=zV?!r>hrsnEhXG)F~0_{Sw*|6Z(j>f@$j8%+WShCS>L<0 zp^cGf{crxtN&w$;Uo21a^5b}j*Q!agu2!pKy@=<=BXD{=l=VQhH?qP~?9*?#6DF&F zM{}NsZ(Fp-$Oshobg_@k`aDgYsgA=ORSI<@KwWv7vfNwsmAUpJKH|-?NNMxdv*e+W+*>fzK1PswJrj`!*9GTOS=5as8UwB z*eJ>IjAXIE>&i>n?zGW+yrum(lJa|Xa}S^_@N=# zdosfN4y83~e2c`KyGy4>T@aHqUK#=*Pm>W(ACqog jDgmjJnO;Z%(Uam{J^@XWAzwWKT9aR2F$P*%00000p}p*z diff --git a/Apps/FR/IntrastatFR/app/AppResources/FR_DataExchDefMap.xml b/Apps/FR/IntrastatFR/app/AppResources/FR_DataExchDefMap.xml index a3c19a6e54..1b0666f7b7 100644 --- a/Apps/FR/IntrastatFR/app/AppResources/FR_DataExchDefMap.xml +++ b/Apps/FR/IntrastatFR/app/AppResources/FR_DataExchDefMap.xml @@ -14,8 +14,8 @@ - - + + diff --git a/Apps/FR/IntrastatFR/app/src/IntrastatReportFRUpgrade.Codeunit.al b/Apps/FR/IntrastatFR/app/src/IntrastatReportFRUpgrade.Codeunit.al index 3e97d12940..98c8995d01 100644 --- a/Apps/FR/IntrastatFR/app/src/IntrastatReportFRUpgrade.Codeunit.al +++ b/Apps/FR/IntrastatFR/app/src/IntrastatReportFRUpgrade.Codeunit.al @@ -5,6 +5,7 @@ codeunit 10857 IntrastatReportFRUpgrade trigger OnUpgradePerCompany() begin UpdateLinesType(); + UpdateColumnsDataExchangeDef(); end; local procedure UpdateLinesType() @@ -28,8 +29,40 @@ codeunit 10857 IntrastatReportFRUpgrade UpgradeTag.SetUpgradeTag(GetIntrastatTypeUpdateTag()); end; + local procedure UpdateColumnsDataExchangeDef() + var + DataExchColumnDef: Record "Data Exch. Column Def"; + UpgradeTag: Codeunit "Upgrade Tag"; + begin + //Set "Export If Not Blank" = true for partyType and partyRole columns + if UpgradeTag.HasUpgradeTag(GetUpdateColumnsDataExchangeDefTag()) then + exit; + + DataExchColumnDef.SetRange("Data Exch. Def Code", 'INTRA-2022-FR'); + DataExchColumnDef.SetRange("Data Exch. Line Def Code", '2-SENDER'); + DataExchColumnDef.SetFilter(Path, '%1|%2', '/Party[@partyType]', '/Party[@partyRole]'); + if DataExchColumnDef.IsEmpty() then + exit; + + DataExchColumnDef.ModifyAll("Export If Not Blank", true); + + UpgradeTag.SetUpgradeTag(GetUpdateColumnsDataExchangeDefTag()); + end; + internal procedure GetIntrastatTypeUpdateTag(): Code[250] begin exit('MS-481518-IntrastatTypeUpdateFR-20230818'); end; + + internal procedure GetUpdateColumnsDataExchangeDefTag(): Code[250] + begin + exit('MS-527324-UpdateColumnsDataExchangeDef-20240522'); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Upgrade Tag", 'OnGetPerCompanyUpgradeTags', '', false, false)] + local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]]) + begin + PerCompanyUpgradeTags.Add(GetUpdateColumnsDataExchangeDefTag()); + end; + } \ No newline at end of file diff --git a/Apps/IN/INGST/app/GSTBase/src/Codeunit/GSTBaseValidation.Codeunit.al b/Apps/IN/INGST/app/GSTBase/src/Codeunit/GSTBaseValidation.Codeunit.al index da40642dcb..64a5bb27ca 100644 --- a/Apps/IN/INGST/app/GSTBase/src/Codeunit/GSTBaseValidation.Codeunit.al +++ b/Apps/IN/INGST/app/GSTBase/src/Codeunit/GSTBaseValidation.Codeunit.al @@ -196,6 +196,8 @@ codeunit 18001 "GST Base Validation" SignFactor := Getsign(DocTypeEnum, TransTypeEnum); end; + OnAfterUpdateGSTLedgerEntrySignFactor(Rec, SignFactor, DocTypeEnum, TransTypeEnum); + if Rec."Transaction Type" = Rec."Transaction Type"::Sales then begin Rec."GST Base Amount" := (Rec."GST Base Amount") * SignFactor; Rec."GST Amount" := (Rec."GST Amount") * SignFactor; @@ -294,6 +296,8 @@ codeunit 18001 "GST Base Validation" SignFactor := Getsign(DocTypeEnum, TransTypeEnum); end; + OnAfterUpdateDetailedGSTLedgerEntrySignFactor(Rec, SignFactor, DocTypeEnum, TransTypeEnum); + if Rec."Transaction Type" = Rec."Transaction Type"::Sales then begin Rec."GST Base Amount" := (Rec."GST Base Amount") * SignFactor; Rec."GST Amount" := (Rec."GST Amount") * SignFactor; @@ -321,6 +325,8 @@ codeunit 18001 "GST Base Validation" if (Rec."Amount Loaded on Item" <> Rec."GST Amount") and (Rec."Amount Loaded on Item" <> 0) and (Rec."GST Credit" = Rec."GST Credit"::"Non-Availment") then Rec."Amount Loaded on Item" := Rec."GST Amount"; + OnAfterUpdateDetailedGstLedgerEntryAmountsField(Rec, SignFactor); + if (Rec."Transaction Type" = Rec."Transaction Type"::Sales) and (Rec."GST Place of Supply" = Rec."GST Place of Supply"::" ") then Rec."GST Place of Supply" := SalesReceivablesSetup."GST Dependency Type"; @@ -1421,4 +1427,20 @@ codeunit 18001 "GST Base Validation" local procedure OnAfterUpdateDetailedGstLedgerEntryOnafterInsertEventOnBeforeModify(var DetailedGSTLedgerEntry: Record "Detailed GST Ledger Entry"; GSTRegistrationNos: Record "GST Registration Nos."; var DetailedGSTLedgerEntryInfo: Record "Detailed GST Ledger Entry Info") begin end; + + [IntegrationEvent(false, false)] + local procedure OnAfterUpdateDetailedGstLedgerEntryAmountsField(var DetailedGSTLedgerEntry: Record "Detailed GST Ledger Entry"; SignFactor: Integer) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterUpdateDetailedGSTLedgerEntrySignFactor(var DetailedGSTLedgerEntry: Record "Detailed GST Ledger Entry"; var SignFactor: Integer; DocTypeEnum: Enum Microsoft.Finance.GST.Base."Document Type Enum"; TransTypeEnum: Enum Microsoft.Finance.GST.Base."Transaction Type Enum") + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterUpdateGSTLedgerEntrySignFactor(var GSTLedgerEntry: Record "GST Ledger Entry"; var SignFactor: Integer; DocTypeEnum: Enum Microsoft.Finance.GST.Base."Document Type Enum"; TransTypeEnum: Enum Microsoft.Finance.GST.Base."Transaction Type Enum") + begin + end; + } diff --git a/Apps/IN/INGST/app/GSTPurchase/src/tableextension/GSTPurchaseLineExt.TableExt.al b/Apps/IN/INGST/app/GSTPurchase/src/tableextension/GSTPurchaseLineExt.TableExt.al index 06df5e8eb1..1b7d1628b1 100644 --- a/Apps/IN/INGST/app/GSTPurchase/src/tableextension/GSTPurchaseLineExt.TableExt.al +++ b/Apps/IN/INGST/app/GSTPurchase/src/tableextension/GSTPurchaseLineExt.TableExt.al @@ -7,6 +7,7 @@ namespace Microsoft.Purchases.Document; using Microsoft.Finance.GST.Base; using Microsoft.Finance.GST.Purchase; using Microsoft.Finance.GST.Subcontracting; +using Microsoft.Foundation.UOM; using Microsoft.Manufacturing.Document; using Microsoft.Purchases.History; @@ -312,8 +313,8 @@ tableextension 18083 "GST Purchase Line Ext" extends "Purchase Line" SubOrderCompList.SetRange("Document Line No.", "Line No."); if SubOrderCompList.FindSet() then repeat - SubOrderCompList.Validate("Quantity To Send", ("Deliver Comp. For" * SubOrderCompList."Quantity per")); - if SubOrderCompList."Scrap %" <> 0 Then + SubOrderCompList.Validate("Quantity To Send", GetQuantityToSendForSubOrderCompList(SubOrderCompList, "Deliver Comp. For")); + if SubOrderCompList."Scrap %" <> 0 then SubOrderCompList."Quantity To Send" := SubOrderCompList."Quantity To Send" + (SubOrderCompList."Quantity To Send" / 100) * SubOrderCompList."Scrap %"; @@ -327,11 +328,11 @@ tableextension 18083 "GST Purchase Line Ext" extends "Purchase Line" SubOrderCompListVend.SetRange("Document Line No.", "Line No."); if SubOrderCompListVend.FindSet() then repeat - SubOrderCompListVend.Validate("Qty. to Consume", "Qty. to Receive" * SubOrderCompListVend."Quantity per" * SubOrderCompListVend."Qty. per Unit of Measure"); + SubOrderCompListVend.Validate("Qty. to Consume", GetQtytoConsumeForSubOrderCompListVend(SubOrderCompListVend, "Qty. to Receive")); SubOrderCompListVend.Validate("Qty. to Return (C.E.)", "Qty. to Reject (C.E.)" * SubOrderCompListVend."Quantity per"); SubOrderCompListVend.Validate("Qty. To Return (V.E.)", (SubOrderCompListVend."Quantity per" * "Qty. to Reject (V.E.)")); SubOrderCompListVend.Validate("Posting Date", "Posting Date"); - if SubOrderCompListVend."Scrap %" <> 0 Then begin + if SubOrderCompListVend."Scrap %" <> 0 then begin SubOrderCompListVend."Qty. to Consume" += (SubOrderCompListVend."Qty. to Consume" / 100) * SubOrderCompListVend."Scrap %"; SubOrderCompListVend."Qty. to Return (C.E.)" += (SubOrderCompListVend."Qty. to Return (C.E.)" / 100) * SubOrderCompListVend."Scrap %"; @@ -402,6 +403,68 @@ tableextension 18083 "GST Purchase Line Ext" extends "Purchase Line" Error(ProdOrdReopenErr, "Prod. Order No."); end; + procedure GetQuantityToSendForSubOrderCompList(SubOrderCompList: Record "Sub Order Component List"; DeliverCompFor: Decimal): Decimal + var + ProdOrderComponent: Record "Prod. Order Component"; + UOMMgt: Codeunit "Unit of Measure Management"; + CalculatedQuantity: Decimal; + begin + Clear(CalculatedQuantity); + ProdOrderComponent.Get(ProdOrderComponent.Status::Released, SubOrderCompList."Production Order No.", SubOrderCompList."Production Order Line No.", SubOrderCompList."Line No."); + case ProdOrderComponent."Calculation Formula" of + ProdOrderComponent."Calculation Formula"::" ": + CalculatedQuantity := ProdOrderComponent."Quantity per"; + ProdOrderComponent."Calculation Formula"::Length: + CalculatedQuantity := Round(ProdOrderComponent.Length * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::"Length * Width": + CalculatedQuantity := Round(ProdOrderComponent.Length * ProdOrderComponent.Width * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::"Length * Width * Depth": + CalculatedQuantity := Round(ProdOrderComponent.Length * ProdOrderComponent.Width * ProdOrderComponent.Depth * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::Weight: + CalculatedQuantity := Round(ProdOrderComponent.Weight * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::"Fixed Quantity": + CalculatedQuantity := ProdOrderComponent."Quantity per"; + else + CalculatedQuantity := ProdOrderComponent.Quantity; + end; + + if ProdOrderComponent."Calculation Formula" = ProdOrderComponent."Calculation Formula"::"Fixed Quantity" then + exit(CalculatedQuantity) + else + exit(CalculatedQuantity * DeliverCompFor); + end; + + procedure GetQtytoConsumeForSubOrderCompListVend(SubOrderCompListVend: Record "Sub Order Comp. List Vend"; QtytoReceive: Decimal): Decimal + var + ProdOrderComponent: Record "Prod. Order Component"; + UOMMgt: Codeunit "Unit of Measure Management"; + CalculatedQuantity: Decimal; + begin + Clear(CalculatedQuantity); + ProdOrderComponent.Get(ProdOrderComponent.Status::Released, SubOrderCompListVend."Production Order No.", SubOrderCompListVend."Production Order Line No.", SubOrderCompListVend."Line No."); + case ProdOrderComponent."Calculation Formula" of + ProdOrderComponent."Calculation Formula"::" ": + CalculatedQuantity := ProdOrderComponent."Quantity per"; + ProdOrderComponent."Calculation Formula"::Length: + CalculatedQuantity := Round(ProdOrderComponent.Length * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::"Length * Width": + CalculatedQuantity := Round(ProdOrderComponent.Length * ProdOrderComponent.Width * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::"Length * Width * Depth": + CalculatedQuantity := Round(ProdOrderComponent.Length * ProdOrderComponent.Width * ProdOrderComponent.Depth * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::Weight: + CalculatedQuantity := Round(ProdOrderComponent.Weight * ProdOrderComponent."Quantity per", UOMMgt.QtyRndPrecision()); + ProdOrderComponent."Calculation Formula"::"Fixed Quantity": + CalculatedQuantity := ProdOrderComponent."Quantity per"; + else + CalculatedQuantity := ProdOrderComponent.Quantity; + end; + + if ProdOrderComponent."Calculation Formula" = ProdOrderComponent."Calculation Formula"::"Fixed Quantity" then + exit(CalculatedQuantity) + else + exit(CalculatedQuantity * QtytoReceive * ProdOrderComponent."Qty. per Unit of Measure"); + end; + var ClosedStatusErr: Label 'No Transaction allowed; Status is Closed.'; InvalidQtyErr: label 'Quantity should be less than or equal to outstanding quantity.'; diff --git a/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderReceipt.Codeunit.al b/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderReceipt.Codeunit.al index 41253d2ea9..9604239169 100644 --- a/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderReceipt.Codeunit.al +++ b/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderReceipt.Codeunit.al @@ -46,6 +46,7 @@ codeunit 18390 "GST Transfer Order Receipt" FirstExecution: Boolean; GSTAmountLoaded: Decimal; TransferCost: Decimal; + TransferQuantity: Decimal; GSTAssessableErr: Label 'GST Assessable Value must be 0 if GST Group Type is Service while transferring from Bonded Warehouse location.'; GSTCustomDutyErr: Label 'Custom Duty Amount must be 0 if GST Group Type is Service while transferring from Bonded Warehouse location.'; GSTGroupServiceErr: Label 'You cannot select GST Group Type Service for transfer.'; @@ -99,8 +100,10 @@ codeunit 18390 "GST Transfer Order Receipt" local procedure GetTranfsrePrice(var ValueEntry: Record "Value Entry"; var ItemJournalLine: Record "Item Journal Line") begin if (ItemJournalLine."Entry Type" = ItemJournalLine."Entry Type"::Transfer) and - (ItemJournalLine."Value Entry Type" <> ItemJournalLine."Value Entry Type"::Revaluation) then + (ItemJournalLine."Value Entry Type" <> ItemJournalLine."Value Entry Type"::Revaluation) then begin TransferCost := ValueEntry."Cost Amount (Actual)"; + TransferQuantity := Abs(ValueEntry."Invoiced Quantity"); + end; end; [EventSubscriber(ObjectType::Codeunit, Codeunit::"TransferOrder-Post Receipt", 'OnBeforeTransRcptHeaderInsert', '', false, false)] @@ -788,7 +791,7 @@ codeunit 18390 "GST Transfer Order Receipt" TempTransferBufferStage."Dimension Set ID" := TransferLine."Dimension Set ID"; TempTransferBufferStage."Charges Amount" := TransferLine."Charges to Transfer"; TempTransferBufferStage."Amount Loaded on Inventory" := TransferLine."Amount Added to Inventory"; - TempTransferBufferStage.Amount := Round(TransferLine.Amount - (-TransferCost)); + TempTransferBufferStage.Amount := Round(TransferLine."Qty. to Receive" * (TransferLine."Transfer Price" - (-(TransferCost / TransferQuantity)))); if LocationBonded."Bonded warehouse" then TempTransferBufferStage."GST Amount" := -Round( RoundTotalGSTAmountQtyFactor( diff --git a/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderShipment.Codeunit.al b/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderShipment.Codeunit.al index 33564cc943..258995724d 100644 --- a/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderShipment.Codeunit.al +++ b/Apps/IN/INGST/app/GSTStockTransfer/src/codeunit/GSTTransferOrderShipment.Codeunit.al @@ -33,6 +33,7 @@ codeunit 18391 "GST Transfer Order Shipment" GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line"; GSTBaseValidation: Codeunit "GST Base Validation"; TransferCost: Decimal; + TransferQuantity: Decimal; GSTGroupServiceErr: Label 'You canNot select GST Group Type Service for transfer.'; LocGSTRegNoARNNoErr: Label 'Location must have either GST Registration No. or Location ARN No.'; TransferShipmentNoLbl: Label 'Transfer - %1', Comment = '%1= Transfer Shipment No.'; @@ -466,7 +467,7 @@ codeunit 18391 "GST Transfer Order Shipment" TempTransferBufferStage.Quantity := TransferLine."Qty. to Ship"; TempTransferBufferStage."Amount Loaded on Inventory" := Round(TransferLine."Amount Added to Inventory" * TransferLine."Qty. to Ship" / TransferLine.Quantity); TempTransferBufferStage."Charges Amount" := Round(TransferLine."Charges to Transfer" * TransferLine."Qty. to Ship" / TransferLine.Quantity); - TempTransferBufferStage."Excise Amount" := Round(TransferLine.Amount - -TransferCost); + TempTransferBufferStage."Excise Amount" := Round(TransferLine."Qty. to Ship" * (TransferLine."Transfer Price" - -(TransferCost / TransferQuantity))); TempTransferBufferStage.Amount := TempTransferBufferStage.Amount + TempTransferBufferStage."Excise Amount"; UpdTransferBuffer(TransferLine, TransferLine."Line No."); end; @@ -1150,7 +1151,8 @@ codeunit 18391 "GST Transfer Order Shipment" begin if GlobalItemLedgEntry."Entry Type" = GlobalItemLedgEntry."Entry Type"::Transfer then begin GlobalItemLedgEntry.CalcFields("Cost Amount (Actual)"); - TransferCost := GlobalItemLedgEntry."Cost Amount (Actual)" + TransferCost := GlobalItemLedgEntry."Cost Amount (Actual)"; + TransferQuantity := Abs(GlobalItemLedgEntry.Quantity); end; end; diff --git a/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingValidations.Codeunit.al b/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingValidations.Codeunit.al index c14d636131..2067594b99 100644 --- a/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingValidations.Codeunit.al +++ b/Apps/IN/INGST/app/GSTSubcontracting/src/Codeunit/SubcontractingValidations.Codeunit.al @@ -32,7 +32,7 @@ codeunit 18470 "Subcontracting Validations" SubOrderComponents.SetRange("Document Line No.", PurchaseLine."Line No."); SubOrderComponents.FindSet(); repeat - SubOrderComponents.Validate("Quantity To Send", (PurchaseLine."Deliver Comp. For" * SubOrderComponents."Quantity per")); + SubOrderComponents.Validate("Quantity To Send", PurchaseLine.GetQuantityToSendForSubOrderCompList(SubOrderComponents, PurchaseLine."Deliver Comp. For")); if SubOrderComponents."Scrap %" <> 0 then SubOrderComponents."Quantity To Send" := SubOrderComponents."Quantity To Send" + @@ -48,7 +48,7 @@ codeunit 18470 "Subcontracting Validations" SubOrderCompListVend.SetRange("Document Line No.", PurchaseLine."Line No."); SubOrderCompListVend.FindSet(); repeat - SubOrderCompListVend.Validate("Qty. to Consume", PurchaseLine."Qty. to Receive" * SubOrderCompListVend."Quantity per" * SubOrderCompListVend."Qty. per Unit of Measure"); + SubOrderCompListVend.Validate("Qty. to Consume", PurchaseLine.GetQtytoConsumeForSubOrderCompListVend(SubOrderCompListVend, PurchaseLine."Qty. to Receive")); SubOrderCompListVend.Validate("Qty. to Return (C.E.)", PurchaseLine."Qty. to Reject (C.E.)" * SubOrderCompListVend."Quantity per"); SubOrderCompListVend.Validate("Qty. To Return (V.E.)", (SubOrderCompListVend."Quantity per" * PurchaseLine."Qty. to Reject (V.E.)")); SubOrderCompListVend.Validate("Posting Date", PurchaseLine."Posting Date"); diff --git a/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al b/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al index 2c85aecaf4..ac00f493f0 100644 --- a/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al +++ b/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderCompListVend.Table.al @@ -470,7 +470,7 @@ table 18478 "Sub Order Comp. List Vend" SubOrderCompListVend.SetRange("Document Line No.", PurchLine."Line No."); SubOrderCompListVend.FindSet(); repeat - SubOrderCompListVend.Validate("Qty. to Consume", (PurchLine."Qty. to Receive" * SubOrderCompListVend."Quantity per" * SubOrderCompListVend."Qty. per Unit of Measure")); + SubOrderCompListVend.Validate("Qty. to Consume", PurchLine.GetQtytoConsumeForSubOrderCompListVend(SubOrderCompListVend, PurchLine."Qty. to Receive")); SubOrderCompListVend.Validate("Qty. to Return (C.E.)", (PurchLine."Qty. to Reject (C.E.)" * SubOrderCompListVend."Quantity per")); SubOrderCompListVend.Validate("Qty. To Return (V.E.)", (SubOrderCompListVend."Quantity per" * PurchLine."Qty. to Reject (V.E.)")); diff --git a/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderComponentList.Table.al b/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderComponentList.Table.al index 9f1228a51c..b59eb371e5 100644 --- a/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderComponentList.Table.al +++ b/Apps/IN/INGST/app/GSTSubcontracting/src/Table/SubOrderComponentList.Table.al @@ -337,7 +337,7 @@ table 18479 "Sub Order Component List" SubOrderComponents.FindSet(); repeat SubOrderComponents.Validate( - "Quantity To Send", ("Deliver Comp. For" * SubOrderComponents."Quantity per")); + "Quantity To Send", PurchLine.GetQuantityToSendForSubOrderCompList(SubOrderComponents, "Deliver Comp. For")); SubOrderComponents.Validate( "Qty. for Rework", (SubOrderComponents."Quantity per" * "Qty. to Reject (Rework)")); if SubOrderComponents."Scrap %" <> 0 then begin diff --git a/Apps/IN/INGST/test/GSTBase/src/GSTStockTransferTests.Codeunit.al b/Apps/IN/INGST/test/GSTBase/src/GSTStockTransferTests.Codeunit.al index c1e5666a9d..12314eea52 100644 --- a/Apps/IN/INGST/test/GSTBase/src/GSTStockTransferTests.Codeunit.al +++ b/Apps/IN/INGST/test/GSTBase/src/GSTStockTransferTests.Codeunit.al @@ -245,6 +245,30 @@ codeunit 18427 "GST Stock Transfer Tests" Assert.IsTrue(GSTApplicable, 'GSTApplicable'); end; + [Test] + [HandlerFunctions('TaxRatesPage')] + procedure PostTransferOrderWithIterStateGSTAndSerialItem() + var + FromLocation, ToLocation, InTransitLocation : Record Location; + TransferHeader: Record "Transfer Header"; + TransferLine: Record "Transfer Line"; + GSTGroupType: Enum "GST Group Type"; + PostedDocumentNo: Code[20]; + begin + // [SCENARIO] [536053] Check is system considers last serial numbers cost for calculation of unrealized profit during transfer shipment + // [GIVEN] Created GST Setup ,Transfer Locations + CreateTransferLocations(FromLocation, ToLocation, InTransitLocation); + CreateGSTSetup(GSTGroupType::Goods, false, true); + + // [WHEN] Create and Post Interstate Transfer Order + PostedDocumentNo := CreateandPostTransferOrderForSerialItem( + TransferHeader, + TransferLine); + + // [THEN] Verify G/L Entries + LibraryGST.VerifyGLEntries("Gen. Journal Document Type"::Invoice, PostedDocumentNo, 2); + end; + local procedure CreateItemWithInventory(): Code[20] var Item: Record Item; @@ -307,6 +331,44 @@ codeunit 18427 "GST Stock Transfer Tests" Codeunit.Run(Codeunit::"Item Jnl.-Post Batch", ItemJournalLine); end; + local procedure CreateSerialItemWithInventory(var Item: Record Item; var SerialNo: Code[50]; var SerialNo1: Code[50]) + var + ItemJournalLine: Record "Item Journal Line"; + ReservationEntry: Record "Reservation Entry"; + NoSeries: Codeunit "No. Series"; + InputCreditAvailment: Boolean; + begin + InputCreditAvailment := StorageBoolean.Get(AvailmentLbl); + + CreateNoVatSetup(); + LibraryItemTracking.CreateSerialItem(Item); + + Item.Validate("GST Group Code", LibraryStorage.Get(GSTGroupCodeLbl)); + Item.Validate("HSN/SAC Code", LibraryStorage.Get(HSNSACCodeLbl)); + if InputCreditAvailment then + Item.Validate("GST Credit", Item."GST Credit"::Availment) + else + Item.Validate("GST Credit", Item."GST Credit"::"Non-Availment"); + Item.Modify(true); + + UpdateInventoryPostingSetup((LibraryStorage.Get(InTransitLocationLbl)), Item."Inventory Posting Group"); + UpdateInventoryPostingSetup((LibraryStorage.Get(FromLocationLbl)), Item."Inventory Posting Group"); + UpdateInventoryPostingSetup((LibraryStorage.Get(ToLocationLbl)), Item."Inventory Posting Group"); + LibraryInventory.CreateItemJournalLineInItemTemplate( + ItemJournalLine, Item."No.", + (LibraryStorage.Get(FromLocationLbl)), + '', 2); + ItemJournalLine.Validate("Unit Amount", LibraryRandom.RandDec(100, 2)); + ItemJournalLine.Modify(); + + SerialNo := NoSeries.GetNextNo(Item."Serial Nos."); + LibraryItemTracking.CreateItemJournalLineItemTracking(ReservationEntry, ItemJournalLine, SerialNo, '', 1); + SerialNo1 := NoSeries.GetNextNo(Item."Serial Nos."); + LibraryItemTracking.CreateItemJournalLineItemTracking(ReservationEntry, ItemJournalLine, SerialNo1, '', 1); + + Codeunit.Run(Codeunit::"Item Jnl.-Post Batch", ItemJournalLine); + end; + local procedure CreateNoVatSetup() var VATPostingSetup: Record "VAT Posting Setup"; @@ -365,6 +427,26 @@ codeunit 18427 "GST Stock Transfer Tests" exit(PostedDocumentNo); end; + local procedure CreateandPostTransferOrderForSerialItem(var TransferHeader: Record "Transfer Header"; + var TransferLine: Record "Transfer Line"): Code[20] + var + DocumentNo: Code[20]; + PostedDocumentNo: Code[20]; + begin + LibraryWarehouse.CreateTransferHeader( + TransferHeader, + (LibraryStorage.Get(FromLocationLbl)), + (LibraryStorage.Get(ToLocationLbl)), + (LibraryStorage.Get(InTransitLocationLbl))); + + CreateTransferLineWithSerialItemAndGST(TransferHeader, TransferLine, StorageBoolean.Get(AvailmentLbl)); + DocumentNo := TransferHeader."No."; + + LibraryWarehouse.PostTransferOrder(TransferHeader, true, false); + PostedDocumentNo := GetPostedTransferShipmentNo(DocumentNo); + exit(PostedDocumentNo); + end; + local procedure CreateTransferLineWithGST(var TransferHeader: Record "Transfer Header"; var TransferLine: Record "Transfer Line"; Availment: Boolean) @@ -405,6 +487,30 @@ codeunit 18427 "GST Stock Transfer Tests" LibraryItemTracking.CreateTransferOrderItemTracking(ReservationEntry, TransferLine, '', LotNo, TransferLine.Quantity); end; + local procedure CreateTransferLineWithSerialItemAndGST(var TransferHeader: Record "Transfer Header"; + var TransferLine: Record "Transfer Line"; + Availment: Boolean) + var + Item: Record Item; + ReservationEntry: Record "Reservation Entry"; + SerialNo, SerialNo1 : Code[50]; + begin + CreateSerialItemWithInventory(Item, SerialNo, SerialNo1); + LibraryWarehouse.CreateTransferLine( + TransferHeader, + Transferline, + Item."No.", + 2); + if Availment then + Transferline.Validate("GST Credit", Transferline."GST Credit"::Availment) + else + TransferLine.Validate("GST Credit", TransferLine."GST Credit"::"Non-Availment"); + TransferLine.Validate("Transfer Price"); + TransferLine.Modify(true); + LibraryItemTracking.CreateTransferOrderItemTracking(ReservationEntry, TransferLine, SerialNo, '', 1); + LibraryItemTracking.CreateTransferOrderItemTracking(ReservationEntry, TransferLine, SerialNo1, '', 1); + end; + local procedure GetPostedTransferShipmentNo(DocumentNo: Code[20]): Code[20] var TransferShipmentHeader: Record "Transfer Shipment Header"; diff --git a/Apps/IN/INGST/test/GSTSubcontracting/src/GSTSubcontracting.Codeunit.al b/Apps/IN/INGST/test/GSTSubcontracting/src/GSTSubcontracting.Codeunit.al index bd05253ca1..411ce1d43f 100644 --- a/Apps/IN/INGST/test/GSTSubcontracting/src/GSTSubcontracting.Codeunit.al +++ b/Apps/IN/INGST/test/GSTSubcontracting/src/GSTSubcontracting.Codeunit.al @@ -902,6 +902,30 @@ codeunit 18479 "GST Subcontracting" Assert.Equal(OutstandingQtyBase, 0); end; + [Test] + [HandlerFunctions('TaxRatePageHandler')] + procedure SubconOrderToRegVendInterStateWithCalcFormulaInProdBOM() + var + ProductionOrder: Record "Production Order"; + PurchaseLine: Record "Purchase Line"; + GSTGroupType: Enum "GST Group Type"; + GSTVendorType: Enum "GST Vendor Type"; + begin + // [SCENARIO] [535618] Check if the system on subcontracting order for Registered Vendor is calculating correct Qty. to send when Prod BOM Calculation Formula is Length * Width * Depth + // [GIVEN] Setups created + CreateGSTSubconSetups(GSTVendorType::Registered, GSTGroupType::Goods, false); + + // [GIVEN] Update Production BOM with Calculation Formula as Length * Width * Depth + UpdateProductionBOM(); + + // [WHEN] Create Subcontracting Order from Released Purchase Order, Update Deliver Comp. For In Order Subcon. Details Delivery + CreateSubcontractingOrderFromReleasedProdOrder(ProductionOrder, PurchaseLine); + UpdateDeliverCompForFieldInPurchLine(PurchaseLine); + + // [THEN] Verify Quantity To Send In Sub Order Component List + VerifyQuantityToSendInSubOrderComponentList(ProductionOrder); + end; + local procedure CreateGSTSubconSetups( GSTVendorType: Enum "GST Vendor Type"; GSTGroupType: Enum "GST Group Type"; @@ -1298,6 +1322,47 @@ codeunit 18479 "GST Subcontracting" LibraryItemTracking.AddSerialNoTrackingInfo(MainItem); end; + local procedure UpdateProductionBOM() + var + MainItem: Record Item; + ProductionBOMHeader: Record "Production BOM Header"; + MainItemNo: Code[20]; + begin + MainItemNo := (Storage.Get(XMainItemNoTok)); + MainItem.Get(MainItemNo); + ProductionBOMHeader.Get(MainItem."Production BOM No."); + LibraryMfg.UpdateProductionBOMStatus(ProductionBOMHeader, ProductionBOMHeader.Status::"Under Development"); + UpdateProductionBOMLineWithCalculationFormula(ProductionBOMHeader); + LibraryMfg.UpdateProductionBOMStatus(ProductionBOMHeader, ProductionBOMHeader.Status::Certified); + end; + + local procedure UpdateProductionBOMLineWithCalculationFormula(ProductionBOMHeader: Record "Production BOM Header") + var + ProductionBOMLine: Record "Production BOM Line"; + ComponentItemNo: Code[20]; + begin + ComponentItemNo := (Storage.Get(XComponentItemNoTok)); + + ProductionBOMLine.SetRange("Production BOM No.", ProductionBOMHeader."No."); + ProductionBOMLine.SetRange(Type, ProductionBOMLine.Type::Item); + ProductionBOMLine.SetRange("No.", ComponentItemNo); + ProductionBOMLine.FindFirst(); + + ProductionBOMLine.Length := LibraryRandom.RandDec(10, 0); + ProductionBOMLine.Width := LibraryRandom.RandDec(10, 0); + ProductionBOMLine.Depth := LibraryRandom.RandDec(10, 0); + ProductionBOMLine."Calculation Formula" := ProductionBOMLine."Calculation Formula"::"Length * Width * Depth"; + ProductionBOMLine.Modify(); + end; + + local procedure UpdateDeliverCompForFieldInPurchLine(var PurchaseLine: Record "Purchase Line") + begin + PurchaseLine.FindFirst(); + + PurchaseLine.Validate("Deliver Comp. For", PurchaseLine.Quantity); + PurchaseLine.Modify(); + end; + local procedure CreateSubcontractingOrderFromReleasedProdOrder(var ProductionOrder: Record "Production Order"; var PurchaseLine: Record "Purchase Line") var ProdOrderLine: Record "Prod. Order Line"; @@ -1909,6 +1974,27 @@ codeunit 18479 "GST Subcontracting" PurchaseHeader.TestField("Buy-from Vendor No.", PurchaseLine."Buy-from Vendor No."); end; + local procedure VerifyQuantityToSendInSubOrderComponentList(ProductionOrder: Record "Production Order") + var + ProdOrderComponent: Record "Prod. Order Component"; + SubOrderComponentList: Record "Sub Order Component List"; + ComponentItemNo: Code[20]; + begin + ComponentItemNo := (Storage.Get(XComponentItemNoTok)); + + ProdOrderComponent.SetRange(Status, ProductionOrder.Status); + ProdOrderComponent.SetRange("Prod. Order No.", ProductionOrder."No."); + ProdOrderComponent.SetRange("Item No.", ComponentItemNo); + ProdOrderComponent.FindFirst(); + + SubOrderComponentList.SetRange("Production Order No.", ProductionOrder."No."); + SubOrderComponentList.SetRange("Parent Item No.", ProductionOrder."Source No."); + SubOrderComponentList.SetRange("Item No.", ComponentItemNo); + SubOrderComponentList.FindFirst(); + + Assert.Equal(SubOrderComponentList."Quantity To Send", ProdOrderComponent."Expected Quantity"); + end; + local procedure CreateAndInsertPurchaseLineForSubContractingOrder(ProductionOrder: Record "Production Order"; var PurchaseLine: Record "Purchase Line"; MainItemNo: Code[20]) var Item: Record Item; diff --git a/Apps/IN/INTDS/app/TDSOnPayments/src/codeunit/TDSJournalsSubscribers.Codeunit.al b/Apps/IN/INTDS/app/TDSOnPayments/src/codeunit/TDSJournalsSubscribers.Codeunit.al index d1aaa788ca..360a192e4a 100644 --- a/Apps/IN/INTDS/app/TDSOnPayments/src/codeunit/TDSJournalsSubscribers.Codeunit.al +++ b/Apps/IN/INTDS/app/TDSOnPayments/src/codeunit/TDSJournalsSubscribers.Codeunit.al @@ -71,7 +71,15 @@ codeunit 18767 "TDS Journals Subscribers" [EventSubscriber(ObjectType::Codeunit, Codeunit::"Tax Base Subscribers", 'OnBeforeCallingTaxEngineFromGenJnlLine', '', false, false)] local procedure OnBeforeCallingTaxEngineFromGenJnlLine(var GenJnlLine: Record "Gen. Journal Line") + var + VendorLedgerEntry: Record "Vendor Ledger Entry"; begin + VendorLedgerEntry.SetLoadFields("Applies-to ID", Prepayment); + VendorLedgerEntry.SetRange("Applies-to ID", GenJnlLine."Applies-to ID"); + VendorLedgerEntry.SetRange(Prepayment, true); + if not VendorLedgerEntry.IsEmpty() then + exit; + ValidateGenJnlLine(GenJnlLine); end; diff --git a/Apps/US/IRSForms/app/src/Extensions/IRS1099BaseAppSubscribers.Codeunit.al b/Apps/US/IRSForms/app/src/Extensions/IRS1099BaseAppSubscribers.Codeunit.al index 73ed525c62..de78659372 100644 --- a/Apps/US/IRSForms/app/src/Extensions/IRS1099BaseAppSubscribers.Codeunit.al +++ b/Apps/US/IRSForms/app/src/Extensions/IRS1099BaseAppSubscribers.Codeunit.al @@ -159,8 +159,7 @@ codeunit 10032 "IRS 1099 BaseApp Subscribers" GenJnlLine.Validate("IRS 1099 Reporting Period", PeriodNo); GenJnlLine.Validate("IRS 1099 Form No.", IRS1099VendorFormBoxSetup."Form No."); GenJnlLine.Validate("IRS 1099 Form Box No.", IRS1099VendorFormBoxSetup."Form Box No."); - if GenJnlLine."Line No." <> 0 then - GenJnlLine.Modify(true); + SaveChangesInGenJnlLine(GenJnlLine); end; local procedure GetIRS1099VendorFormBoxSetupFromGenJnlLine(var IRS1099VendorFormBoxSetup: Record "IRS 1099 Vendor Form Box Setup"; GenJnlLine: Record "Gen. Journal Line"; PeriodNo: Code[20]) @@ -180,8 +179,13 @@ codeunit 10032 "IRS 1099 BaseApp Subscribers" exit; #endif GenJnlLine.Validate("IRS 1099 Reporting Amount", GenJnlLine.Amount); + SaveChangesInGenJnlLine(GenJnlLine); + end; + + local procedure SaveChangesInGenJnlLine(var GenJnlLine: Record "Gen. Journal Line") + begin if GenJnlLine."Line No." <> 0 then - GenJnlLine.Modify(true); + if GenJnlLine.Modify(true) then; end; } diff --git a/Apps/US/IRSForms/test/src/IRS1099DocumentTests.Codeunit.al b/Apps/US/IRSForms/test/src/IRS1099DocumentTests.Codeunit.al index 310e7b15bf..70aaee2493 100644 --- a/Apps/US/IRSForms/test/src/IRS1099DocumentTests.Codeunit.al +++ b/Apps/US/IRSForms/test/src/IRS1099DocumentTests.Codeunit.al @@ -5,6 +5,7 @@ namespace Microsoft.Finance.VAT.Reporting; using Microsoft.Purchases.Document; +using Microsoft.Finance.GeneralLedger.Journal; using Microsoft.Purchases.Payables; codeunit 148010 "IRS 1099 Document Tests" @@ -542,6 +543,72 @@ codeunit 148010 "IRS 1099 Document Tests" // [THEN] There is only one form document line for MISC-01 and "X" Assert.RecordCount(IRS1099FormDocLine, 1); +#if not CLEAN25 + UnbindSubscription(IRSFormsEnableFeature); +#endif + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ValidateNotInsertedGenJnlLinePostingDateWithLineNo() + var + GenJnlLine: Record "Gen. Journal Line"; +#if not CLEAN25 +#pragma warning disable AL0432 + IRSFormsEnableFeature: Codeunit "IRS Forms Enable Feature"; +#pragma warning restore AL0432 +#endif + PeriodNo: Code[20]; + begin + // [FEATURE] [UT] + // [SCENARIO 536496] It is possible to validate the posting date in the not inserted general journal line with line no. already specified + + Initialize(); +#if not CLEAN25 + BindSubscription(IRSFormsEnableFeature); +#endif + + // [GIVEN] "IRS Reporting Period" = "X" with "Starting Date" = work date + PeriodNo := LibraryIRSReportingPeriod.CreateOneDayReportingPeriod(WorkDate()); + // [GIVEN] "Gen. Journal Line" with "Document Type" = Invoice and "Line No." = 1 + GenJnlLine."Document Type" := GenJnlLine."Document Type"::Invoice; + GenJnlLine."Line No." := 1; + // [WHEN] Validate posting date with work date + GenJnlLine.Validate("Posting Date", WorkDate()); + // [THEN] The IRS 1099 Reporting Period is "X" + GenJnlLine.TestField("IRS 1099 Reporting Period", PeriodNo); + +#if not CLEAN25 + UnbindSubscription(IRSFormsEnableFeature); +#endif + end; + + [Test] + [TransactionModel(TransactionModel::AutoRollback)] + procedure ValidateNotInsertedGenJnlLineAmountWithLineNo() + var + GenJnlLine: Record "Gen. Journal Line"; +#if not CLEAN25 +#pragma warning disable AL0432 + IRSFormsEnableFeature: Codeunit "IRS Forms Enable Feature"; +#pragma warning restore AL0432 +#endif + begin + // [FEATURE] [UT] + // [SCENARIO 536496] It is possible to validate the amount in the not inserted general journal line with line no. already specified + + Initialize(); +#if not CLEAN25 + BindSubscription(IRSFormsEnableFeature); +#endif + + // [GIVEN] "Gen. Journal Line" with "Line No." = 1 + GenJnlLine."Line No." := 1; + // [WHEN] Validate Amount field with 100 + GenJnlLine.Validate(Amount, 100); + // [THEN] The IRS 1099 Reporting Amount is 100 + GenJnlLine.TestField("IRS 1099 Reporting Amount", 100); + #if not CLEAN25 UnbindSubscription(IRSFormsEnableFeature); #endif diff --git a/Apps/W1/BankDeposits/app/src/codeunits/BankDepositPost.Codeunit.al b/Apps/W1/BankDeposits/app/src/codeunits/BankDepositPost.Codeunit.al index 1272ac542d..f8e944a0a2 100644 --- a/Apps/W1/BankDeposits/app/src/codeunits/BankDepositPost.Codeunit.al +++ b/Apps/W1/BankDeposits/app/src/codeunits/BankDepositPost.Codeunit.al @@ -115,7 +115,11 @@ codeunit 1690 "Bank Deposit-Post" Commit(); BankDepositPost.SetCurrentDeposit(Rec); BindSubscription(BankDepositPost); - GenJnlPostBatch.Run(GenJournalLine); + if not GenJnlPostBatch.Run(GenJournalLine) then begin + CleanPostedBankDepositHeaderAndLines(Rec."No."); + Commit(); + Error(GetLastErrorText()); + end; UnbindSubscription(BankDepositPost); if Rec."Post as Lump Sum" then begin BankDepositPost.GetLumpSumBalanceEntry(GLEntry); @@ -202,6 +206,8 @@ codeunit 1690 "Bank Deposit-Post" GenJournalLine."Bal. Account Type" := GenJournalLine."Bal. Account Type"::"Bank Account"; GenJournalLine."Bal. Account No." := BankDepositHeader."Bank Account No."; end; + if GenJournalLine."Credit Amount" < 0 then + GenJournalLine.Correction := false; GenJournalLine.Validate(Amount); AssignVATDateIfEmpty(GenJournalLine); TotalAmountLCY += GenJournalLine."Amount (LCY)"; @@ -392,6 +398,23 @@ codeunit 1690 "Bank Deposit-Post" end; end; + local procedure CleanPostedBankDepositHeaderAndLines(BankDepositNo: Code[20]) + var + BankAccCommentLine: Record "Bank Acc. Comment Line"; + PostedBankDepositHeaderLocal: Record "Posted Bank Deposit Header"; + PostedBankDepositLine: Record "Posted Bank Deposit Line"; + begin + PostedBankDepositLine.SetRange("Bank Deposit No.", BankDepositNo); + PostedBankDepositLine.DeleteAll(); + if not PostedBankDepositHeaderLocal.Get(BankDepositNo) then + exit; + BankAccCommentLine.SetRange("Table Name", BankAccCommentLine."Table Name"::"Posted Bank Deposit Header"); + BankAccCommentLine.SetRange("Bank Account No.", PostedBankDepositHeaderLocal."Bank Account No."); + BankAccCommentLine.SetRange("No.", PostedBankDepositHeaderLocal."No."); + BankAccCommentLine.DeleteAll(); + PostedBankDepositHeaderLocal.Delete(); + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Batch", 'OnBeforePostGenJnlLine', '', false, false)] local procedure OnBeforePostGenJnlLine(var GenJournalLine: Record "Gen. Journal Line"; CommitIsSuppressed: Boolean; var Posted: Boolean; var GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line"; var PostingGenJournalLine: Record "Gen. Journal Line") begin @@ -427,12 +450,13 @@ codeunit 1690 "Bank Deposit-Post" PostedBankDepositLine."Document No." := PostingGenJournalLine."Document No."; PostedBankDepositLine.Description := PostingGenJournalLine.Description; PostedBankDepositLine."Currency Code" := PostingGenJournalLine."Currency Code"; - PostedBankDepositLine.Amount := PostingGenJournalLine."Credit Amount"; + PostedBankDepositLine.Amount := -PostingGenJournalLine.Amount; PostedBankDepositLine."Posting Group" := PostingGenJournalLine."Posting Group"; PostedBankDepositLine."Shortcut Dimension 1 Code" := PostingGenJournalLine."Shortcut Dimension 1 Code"; PostedBankDepositLine."Shortcut Dimension 2 Code" := PostingGenJournalLine."Shortcut Dimension 2 Code"; PostedBankDepositLine."Dimension Set ID" := PostingGenJournalLine."Dimension Set ID"; PostedBankDepositLine."Posting Date" := CurrentBankDepositHeader."Posting Date"; + PostedBankDepositLine."External Document No." := PostingGenJournalLine."External Document No."; case PostingGenJournalLine."Account Type" of PostingGenJournalLine."Account Type"::"G/L Account", PostingGenJournalLine."Account Type"::"Bank Account": diff --git a/Apps/W1/BankDeposits/app/src/pages/BankDepositSubform.Page.al b/Apps/W1/BankDeposits/app/src/pages/BankDepositSubform.Page.al index c255280119..cce9c1cb02 100644 --- a/Apps/W1/BankDeposits/app/src/pages/BankDepositSubform.Page.al +++ b/Apps/W1/BankDeposits/app/src/pages/BankDepositSubform.Page.al @@ -95,6 +95,11 @@ page 1693 "Bank Deposit Subform" ToolTip = 'Specifies the number of the bank deposit document.'; Editable = not DepositIsLumpSum; } + field("External Document No."; Rec."External Document No.") + { + ApplicationArea = Basic, Suite; + ToolTip = 'Specifies the external document number for this line, such as a check number.'; + } field("Credit Amount"; Rec."Credit Amount") { ApplicationArea = Basic, Suite; diff --git a/Apps/W1/BankDeposits/app/src/pages/PostedBankDepositSubform.Page.al b/Apps/W1/BankDeposits/app/src/pages/PostedBankDepositSubform.Page.al index 0ee7ed137f..9da669f001 100644 --- a/Apps/W1/BankDeposits/app/src/pages/PostedBankDepositSubform.Page.al +++ b/Apps/W1/BankDeposits/app/src/pages/PostedBankDepositSubform.Page.al @@ -46,6 +46,11 @@ page 1697 "Posted Bank Deposit Subform" ApplicationArea = Basic, Suite; ToolTip = 'Specifies the number of the document (usually a check) that was deposited.'; } + field("External Document No."; Rec."External Document No.") + { + ApplicationArea = Basic, Suite; + ToolTip = 'Specifies the external document number for this line, such as a check number.'; + } field(Amount; Rec.Amount) { ApplicationArea = Basic, Suite; diff --git a/Apps/W1/BankDeposits/app/src/tables/PostedBankDepositLine.Table.al b/Apps/W1/BankDeposits/app/src/tables/PostedBankDepositLine.Table.al index 8f3a50c352..492021e0ca 100644 --- a/Apps/W1/BankDeposits/app/src/tables/PostedBankDepositLine.Table.al +++ b/Apps/W1/BankDeposits/app/src/tables/PostedBankDepositLine.Table.al @@ -117,6 +117,10 @@ table 1692 "Posted Bank Deposit Line" else if ("Account Type" = const("Bank Account")) "Bank Account Ledger Entry"; } + field(17; "External Document No."; Code[35]) + { + Caption = 'External Document No.'; + } field(480; "Dimension Set ID"; Integer) { Caption = 'Dimension Set ID'; diff --git a/Apps/W1/BankDeposits/test/src/BankDepositPostingTests.Codeunit.al b/Apps/W1/BankDeposits/test/src/BankDepositPostingTests.Codeunit.al index 438a5d29df..c055d285f4 100644 --- a/Apps/W1/BankDeposits/test/src/BankDepositPostingTests.Codeunit.al +++ b/Apps/W1/BankDeposits/test/src/BankDepositPostingTests.Codeunit.al @@ -231,6 +231,9 @@ codeunit 139769 "Bank Deposit Posting Tests" var Vendor: Record Vendor; BankDepositHeader: Record "Bank Deposit Header"; + PostedBankDepositLine: Record "Posted Bank Deposit Line"; + PostedBankDepositHeader: Record "Posted Bank Deposit Header"; + BankAccCommentLine: Record "Bank Acc. Comment Line"; GenJournalLine: Record "Gen. Journal Line"; begin // Verify Error while posting Bank Deposit with different default Dimension on Vendor. @@ -246,6 +249,14 @@ codeunit 139769 "Bank Deposit Posting Tests" // Verify: Verify Error while posting Bank Deposit with different Dimension. Assert.ExpectedError(DimensionErr); + // Verify: No Posted Bank Deposit Header, Lines or Comments with the same Deposit "No." exist. + Assert.IsFalse(PostedBankDepositHeader.Get(BankDepositHeader."No."), 'The Posted Bank Deposit Header should not exist.'); + PostedBankDepositLine.SetRange("Bank Deposit No.", BankDepositHeader."No."); + Assert.IsTrue(PostedBankDepositLine.IsEmpty(), 'The Posted Bank Deposit Line should be empty.'); + BankAccCommentLine.SetRange("Bank Account No.", BankDepositHeader."Bank Account No."); + BankAccCommentLine.SetRange("Table Name", BankAccCommentLine."Table Name"::"Posted Bank Deposit Header"); + BankAccCommentLine.SetRange("No.", BankDepositHeader."No."); + Assert.IsTrue(BankAccCommentLine.IsEmpty(), 'The Bank Account Comment Line should be empty.'); end; [Test] @@ -254,6 +265,9 @@ codeunit 139769 "Bank Deposit Posting Tests" var Customer: Record Customer; BankDepositHeader: Record "Bank Deposit Header"; + PostedBankDepositLine: Record "Posted Bank Deposit Line"; + PostedBankDepositHeader: Record "Posted Bank Deposit Header"; + BankAccCommentLine: Record "Bank Acc. Comment Line"; GenJournalLine: Record "Gen. Journal Line"; begin // Verify Error while posting Bank Deposit with different default Dimension on Customer. @@ -269,6 +283,15 @@ codeunit 139769 "Bank Deposit Posting Tests" // Verify: Verify Error while posting Bank Deposit with different Dimension. Assert.ExpectedError(DimensionErr); + // Verify: No Posted Bank Deposit Header, Lines or Comments with the same Deposit "No." exist. + Assert.IsFalse(PostedBankDepositHeader.Get(BankDepositHeader."No."), 'The Posted Bank Deposit Header should not exist.'); + PostedBankDepositLine.SetRange("Bank Deposit No.", BankDepositHeader."No."); + Assert.IsTrue(PostedBankDepositLine.IsEmpty(), 'The Posted Bank Deposit Line should be empty.'); + BankAccCommentLine.SetRange("Bank Account No.", BankDepositHeader."Bank Account No."); + BankAccCommentLine.SetRange("Table Name", BankAccCommentLine."Table Name"::"Posted Bank Deposit Header"); + BankAccCommentLine.SetRange("No.", BankDepositHeader."No."); + Assert.IsTrue(BankAccCommentLine.IsEmpty(), 'The Bank Account Comment Line should be empty.'); + end; [Test] @@ -429,6 +452,45 @@ codeunit 139769 "Bank Deposit Posting Tests" StrSubstNo(BatchNameErr, PreviousBatchName, GenJournalTemplate.FieldCaption(Name))); end; + [Test] + [HandlerFunctions('GeneralJournalBatchesPageHandler,ConfirmHandler')] + procedure PostingNegativeAndPositiveLinesShouldBePossible() + var + GLAccount: Record "G/L Account"; + BankDepositHeader: Record "Bank Deposit Header"; + GenJournalBatch: Record "Gen. Journal Batch"; + GenJournalLine: Record "Gen. Journal Line"; + GenJournalTemplate: Record "Gen. Journal Template"; + PostedBankDepositHeader: Record "Posted Bank Deposit Header"; + GenJournalDocumentType: Enum "Gen. Journal Document Type"; + Amount: Decimal; + begin + // [SCENARIO 535786] A bank deposit can be posted even if it has negative "Credit Amount" lines that have been marked as "Correction" + Initialize(); + LibraryERM.CreateGLAccount(GLAccount); + CreateGenJournalBatch(GenJournalBatch, GenJournalTemplate.Type::"Bank Deposits"); + CreateBankDepositHeaderWithBankAccount(BankDepositHeader, GenJournalBatch); + // [GIVEN] A deposit with positive and negative Credit Amount lines. + Amount := 100; + LibraryERM.CreateGeneralJnlLine( + GenJournalLine, BankDepositHeader."Journal Template Name", BankDepositHeader."Journal Batch Name", GenJournalDocumentType::" ", + GenJournalLine."Account Type"::"G/L Account", GLAccount."No.", -Amount); + LibraryERM.CreateGeneralJnlLine( + GenJournalLine, BankDepositHeader."Journal Template Name", BankDepositHeader."Journal Batch Name", GenJournalDocumentType::" ", + GenJournalLine."Account Type"::"G/L Account", GLAccount."No.", 0); + GenJournalLine.Validate("Credit Amount", -2 * Amount); + GenJournalLine.Modify(); + UpdateBankDepositHeaderWithAmount(BankDepositHeader); + Commit(); + // [THEN] It should be possible to post the deposit. + PostBankDeposit(BankDepositHeader); + // [THEN] The total amount of the deposit should be the sum of the lines. + PostedBankDepositHeader.SetAutoCalcFields("Total Deposit Lines"); + PostedBankDepositHeader.Get(BankDepositHeader."No."); + Assert.AreEqual(-Amount, PostedBankDepositHeader."Total Deposit Lines", 'The total amount of the deposit should be the sum of the lines'); + end; + + local procedure Initialize() var InventorySetup: Record "Inventory Setup"; diff --git a/Apps/W1/EDocument/app/src/Processing/OrderMatching/Copilot/EDocPOCopilotProp.Page.al b/Apps/W1/EDocument/app/src/Processing/OrderMatching/Copilot/EDocPOCopilotProp.Page.al index 3933282ebd..db776f4cee 100644 --- a/Apps/W1/EDocument/app/src/Processing/OrderMatching/Copilot/EDocPOCopilotProp.Page.al +++ b/Apps/W1/EDocument/app/src/Processing/OrderMatching/Copilot/EDocPOCopilotProp.Page.al @@ -17,6 +17,7 @@ page 6166 "E-Doc. PO Copilot Prop" Editable = true; InherentPermissions = X; InherentEntitlements = X; + ContextSensitiveHelpPage = 'map-edocuments-with-copilot'; layout { diff --git a/Apps/W1/EDocument/app/src/Processing/OrderMatching/EDocOrderLineMatching.Page.al b/Apps/W1/EDocument/app/src/Processing/OrderMatching/EDocOrderLineMatching.Page.al index cd7dcdd086..275e4665c4 100644 --- a/Apps/W1/EDocument/app/src/Processing/OrderMatching/EDocOrderLineMatching.Page.al +++ b/Apps/W1/EDocument/app/src/Processing/OrderMatching/EDocOrderLineMatching.Page.al @@ -8,7 +8,7 @@ using System.Telemetry; page 6167 "E-Doc. Order Line Matching" { Caption = 'Purchase Order Matching'; - DataCaptionExpression = 'Purchase Order ' + Rec."Order No."; + DataCaptionExpression = StrSubstNo(GlobalDataCaptionExpressionTxt, Rec."Order No."); PageType = Card; ApplicationArea = All; SourceTable = "E-Document"; @@ -225,6 +225,7 @@ page 6167 "E-Doc. Order Line Matching" LineDiscountVaryMatchMsg: Label 'Matched e-document lines (%1) has Line Discount % different from matched purchase order line. Please verify matches are correct.', Comment = '%1 - Line number'; LineCostVaryMatchMsg: Label 'Matched e-document lines (%1) has Direct Unit Cost different from matched purchase order line. Please verify matches are correct.', Comment = '%1 - Line number'; NoMatchesFoundMsg: Label 'Copilot could not find any line matches. Please review manually'; + GlobalDataCaptionExpressionTxt: Label 'Purchase Order %1', Comment = '%1 - Purchase order number'; trigger OnOpenPage() var diff --git a/Apps/W1/ExcelReports/app/ReportLayouts/Excel/FixedAsset/FixedAssetDetailsExcel.xlsx b/Apps/W1/ExcelReports/app/ReportLayouts/Excel/FixedAsset/FixedAssetDetailsExcel.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..17594016fc0014bea2573dc6a1ccfe825709f052 GIT binary patch literal 50963 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 '' then + FixedAssetLedgerEntry.SetRange("Depreciation Book Code", DepreciationBookCode); + FixedAssetLedgerEntry.SetFilter("FA Posting Date", FixedAssetData.GetFilter("FA Posting Date Filter")); + if not PrintReversedEntries then + FixedAssetLedgerEntry.SetRange("Reversed", false); + end; + } + trigger OnAfterGetRecord() + begin + if (not IncludeInactive) and FixedAssetData.Inactive then + CurrReport.Skip(); + end; + } + } + requestpage + { + SaveValues = true; + AboutTitle = 'Fixed Asset Details Excel'; + AboutText = 'This report shows ledger entries for one or more fixed assets.'; + layout + { + area(content) + { + group(Options) + { + Caption = 'Options'; + field(DepreciationBook; DepreciationBookCode) + { + ApplicationArea = All; + Caption = 'Depreciation Book'; + TableRelation = "Depreciation Book"; + ToolTip = 'Specifies the code for the depreciation book to be included in the report or batch job.'; + } + field(IncludeReversedEntries; PrintReversedEntries) + { + ApplicationArea = All; + Caption = 'Include Reversed Entries'; + ToolTip = 'Specifies if you want to include reversed fixed asset entries in the report.'; + } + field(SkipInactive; IncludeInactive) + { + ApplicationArea = All; + Caption = 'Include Inactive Fixed Assets'; + ToolTip = 'Specifies if you want to include inactive fixed assets in the report.'; + } + } + } + } + } + rendering + { + layout(FixedAssetDetailsExcel) + { + Type = Excel; + LayoutFile = './ReportLayouts/Excel/FixedAsset/FixedAssetDetailsExcel.xlsx'; + Caption = 'Fixed Asset Details Excel'; + Summary = 'Built in layout for Fixed Asset Details.'; + } + } + labels + { + DataRetrieved = 'Data retrieved:'; + FixedAssetDetails = 'Fixed Asset Details'; + } + + var + DepreciationBookCode: Code[20]; + PrintReversedEntries, IncludeInactive : Boolean; +} \ No newline at end of file diff --git a/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceBudgetExcel.Report.al b/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceBudgetExcel.Report.al index ae6b091272..96211067b5 100644 --- a/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceBudgetExcel.Report.al +++ b/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceBudgetExcel.Report.al @@ -48,12 +48,29 @@ report 4406 "EXR Trial BalanceBudgetExcel" column(BudgetBalPct; "% of Budget Bal.") { IncludeCaption = true; } } trigger OnAfterGetRecord() + var + TrialBalance: Codeunit "Trial Balance"; begin - Clear(EXRTrialBalanceBuffer); - EXRTrialBalanceBuffer.DeleteAll(); IndentedAccountName := PadStr('', TrialBalanceBudgetData.Indentation * 2, ' ') + TrialBalanceBudgetData.Name; + TrialBalance.InsertBreakdownForGLAccount(TrialBalanceBudgetData, Dimension1Values, Dimension2Values, EXRTrialBalanceBuffer); + end; - BuildDataset(TrialBalanceBudgetData); + trigger OnPreDataItem() + var + DimensionValue: Record "Dimension Value"; + begin + DimensionValue.SetRange("Global Dimension No.", 1); + if DimensionValue.FindSet() then + repeat + Dimension1Values.Add(DimensionValue.Code); + until DimensionValue.Next() = 0; + Dimension1Values.Add(''); + DimensionValue.SetRange("Global Dimension No.", 2); + if DimensionValue.FindSet() then + repeat + Dimension2Values.Add(DimensionValue.Code); + until DimensionValue.Next() = 0; + Dimension2Values.Add(''); end; } dataitem(Dimension1; "Dimension Value") @@ -101,59 +118,12 @@ report 4406 "EXR Trial BalanceBudgetExcel" CompanyInformation.Get(); end; + var + Dimension1Values: List of [Code[20]]; + Dimension2Values: List of [Code[20]]; + protected var CompanyInformation: Record "Company Information"; IndentedAccountName: Text; - local procedure BuildDataset(var GLAccount: Record "G/L Account") - var - DimensionValue1: Record "Dimension Value"; - DimensionValue2: Record "Dimension Value"; - begin - DimensionValue1.SetRange("Global Dimension No.", 1); - DimensionValue2.SetRange("Global Dimension No.", 2); - - InsertGLAccountData(GLAccount, DimensionValue1, DimensionValue2); - end; - - local procedure InsertGLAccountData(var GLAccount: Record "G/L Account"; var DimensionValue1: Record "Dimension Value"; var DimensionValue2: Record "Dimension Value") - begin - AddGLToDataset(GLAccount, '', ''); - - if DimensionValue1.FindSet() then - repeat - AddGLToDataset(GLAccount, DimensionValue1."Code", ''); - if DimensionValue2.FindSet() then - repeat - AddGLToDataset(GLAccount, DimensionValue1."Code", DimensionValue2."Code"); - until DimensionValue2.Next() = 0; - until DimensionValue1.Next() = 0; - - if DimensionValue2.FindSet() then - repeat - AddGLToDataset(GLAccount, '', DimensionValue2."Code"); - until DimensionValue2.Next() = 0; - end; - - local procedure AddGLToDataset(var GLAccount: Record "G/L Account"; Dimension1Code: Code[20]; Dimension2Code: Code[20]) - var - LocalGLAccount: Record "G/L Account"; - begin - LocalGLAccount.Copy(GLAccount); - LocalGLAccount.SetFilter("Global Dimension 1 Code", Dimension1Code); - LocalGLAccount.SetFilter("Global Dimension 2 Code", Dimension2Code); - - LocalGLAccount.CalcFields("Net Change", "Balance at Date", "Budgeted Amount", "Budget at Date"); - - Clear(EXRTrialBalanceBuffer); - EXRTrialBalanceBuffer."G/L Account No." := LocalGLAccount."No."; - EXRTrialBalanceBuffer."Dimension 1 Code" := Dimension1Code; - EXRTrialBalanceBuffer."Dimension 2 Code" := Dimension2Code; - EXRTrialBalanceBuffer.Validate("Net Change", LocalGLAccount."Net Change"); - EXRTrialBalanceBuffer.Validate(Balance, LocalGLAccount."Balance at Date"); - EXRTrialBalanceBuffer.Validate("Budget (Net)", LocalGLAccount."Budgeted Amount"); - EXRTrialBalanceBuffer.Validate("Budget (Bal. at Date)", LocalGLAccount."Budget at Date"); - EXRTrialBalanceBuffer.CalculateBudgetComparisons(); - EXRTrialBalanceBuffer.Insert(true); - end; } \ No newline at end of file diff --git a/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceExcel.Report.al b/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceExcel.Report.al index 8882a18f26..931cc28e11 100644 --- a/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceExcel.Report.al +++ b/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalanceExcel.Report.al @@ -56,13 +56,31 @@ report 4405 "EXR Trial Balance Excel" } trigger OnAfterGetRecord() + var + TrialBalance: Codeunit "Trial Balance"; begin - Clear(EXRTrialBalanceBuffer); - EXRTrialBalanceBuffer.DeleteAll(); IndentedAccountName := PadStr('', TrialBalanceData.Indentation * 2, ' ') + TrialBalanceData.Name; + TrialBalance.InsertBreakdownForGLAccount(TrialBalanceData, Dimension1Values, Dimension2Values, EXRTrialBalanceBuffer); + end; - BuildDataset(TrialBalanceData); + trigger OnPreDataItem() + var + DimensionValue: Record "Dimension Value"; + begin + DimensionValue.SetRange("Global Dimension No.", 1); + if DimensionValue.FindSet() then + repeat + Dimension1Values.Add(DimensionValue.Code); + until DimensionValue.Next() = 0; + Dimension1Values.Add(''); + DimensionValue.SetRange("Global Dimension No.", 2); + if DimensionValue.FindSet() then + repeat + Dimension2Values.Add(DimensionValue.Code); + until DimensionValue.Next() = 0; + Dimension2Values.Add(''); end; + } dataitem(Dimension1; "Dimension Value") { @@ -109,59 +127,13 @@ report 4405 "EXR Trial Balance Excel" CompanyInformation.Get(); end; + var + Dimension1Values: List of [Code[20]]; + Dimension2Values: List of [Code[20]]; + protected var CompanyInformation: Record "Company Information"; IndentedAccountName: Text; - local procedure BuildDataset(var GLAccount: Record "G/L Account") - var - DimensionValue1: Record "Dimension Value"; - DimensionValue2: Record "Dimension Value"; - begin - DimensionValue1.SetRange("Global Dimension No.", 1); - DimensionValue2.SetRange("Global Dimension No.", 2); - - InsertGLAccountData(GLAccount, DimensionValue1, DimensionValue2); - end; - - local procedure InsertGLAccountData(var GLAccount: Record "G/L Account"; var DimensionValue1: Record "Dimension Value"; var DimensionValue2: Record "Dimension Value") - begin - AddGLToDataset(GLAccount, '', ''); - - if DimensionValue1.FindSet() then - repeat - AddGLToDataset(GLAccount, DimensionValue1."Code", ''); - if DimensionValue2.FindSet() then - repeat - AddGLToDataset(GLAccount, DimensionValue1."Code", DimensionValue2."Code"); - until DimensionValue2.Next() = 0; - until DimensionValue1.Next() = 0; - - if DimensionValue2.FindSet() then - repeat - AddGLToDataset(GLAccount, '', DimensionValue2."Code"); - until DimensionValue2.Next() = 0; - end; - - local procedure AddGLToDataset(var GLAccount: Record "G/L Account"; Dimension1Code: Code[20]; Dimension2Code: Code[20]) - var - LocalGLAccount: Record "G/L Account"; - begin - LocalGLAccount.Copy(GLAccount); - LocalGLAccount.SetFilter("Global Dimension 1 Code", Dimension1Code); - LocalGLAccount.SetFilter("Global Dimension 2 Code", Dimension2Code); - - LocalGLAccount.CalcFields("Net Change", "Balance at Date", "Additional-Currency Net Change", "Add.-Currency Balance at Date"); - - Clear(EXRTrialBalanceBuffer); - EXRTrialBalanceBuffer."G/L Account No." := LocalGLAccount."No."; - EXRTrialBalanceBuffer."Dimension 1 Code" := Dimension1Code; - EXRTrialBalanceBuffer."Dimension 2 Code" := Dimension2Code; - EXRTrialBalanceBuffer.Validate("Net Change", LocalGLAccount."Net Change"); - EXRTrialBalanceBuffer.Validate(Balance, LocalGLAccount."Balance at Date"); - EXRTrialBalanceBuffer.Validate("Net Change (ACY)", LocalGLAccount."Additional-Currency Net Change"); - EXRTrialBalanceBuffer.Validate("Balance (ACY)", LocalGLAccount."Add.-Currency Balance at Date"); - EXRTrialBalanceBuffer.Insert(true); - end; } diff --git a/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalbyPeriodExcel.Report.al b/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalbyPeriodExcel.Report.al index d0a73ee9b9..c4ee25d9bc 100644 --- a/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalbyPeriodExcel.Report.al +++ b/Apps/W1/ExcelReports/app/src/Financials/EXRTrialBalbyPeriodExcel.Report.al @@ -177,8 +177,8 @@ report 4408 "EXR Trial Bal by Period Excel" LocalGLAccount: Record "G/L Account"; begin LocalGLAccount.Copy(GLAccount); - LocalGLAccount.SetFilter("Global Dimension 1 Code", Dimension1Code); - LocalGLAccount.SetFilter("Global Dimension 2 Code", Dimension2Code); + LocalGLAccount.SetFilter("Global Dimension 1 Filter", Dimension1Code); + LocalGLAccount.SetFilter("Global Dimension 2 Filter", Dimension2Code); LocalGLAccount.CalcFields("Net Change", "Balance at Date"); Clear(EXRTrialBalanceBuffer); diff --git a/Apps/W1/ExcelReports/app/src/Financials/TrialBalance.Codeunit.al b/Apps/W1/ExcelReports/app/src/Financials/TrialBalance.Codeunit.al index 66dae74d05..807b3f1363 100644 --- a/Apps/W1/ExcelReports/app/src/Financials/TrialBalance.Codeunit.al +++ b/Apps/W1/ExcelReports/app/src/Financials/TrialBalance.Codeunit.al @@ -46,8 +46,8 @@ codeunit 4410 "Trial Balance" local procedure SetGLAccountFilters(var GLAccount: Record "G/L Account"; Dimension1ValueCode: Code[20]; Dimension2ValueCode: Code[20]) begin - GLAccount.SetFilter("Global Dimension 1 Code", Dimension1ValueCode); - GLAccount.SetFilter("Global Dimension 2 Code", Dimension2ValueCode); + GLAccount.SetFilter("Global Dimension 1 Filter", Dimension1ValueCode); + GLAccount.SetFilter("Global Dimension 2 Filter", Dimension2ValueCode); end; local procedure SetGLAccountFilters(var GLAccount: Record "G/L Account"; Dimension1ValueCode: Code[20]; Dimension2ValueCode: Code[20]; BusinessUnitCode: Code[20]) @@ -58,15 +58,18 @@ codeunit 4410 "Trial Balance" local procedure InsertTrialBalanceDataForGLAccountWithFilters(var GLAccount: Record "G/L Account"; var EXRTrialBalanceBuffer: Record "EXR Trial Balance Buffer") begin - GlAccount.CalcFields("Net Change", "Balance at Date", "Additional-Currency Net Change", "Add.-Currency Balance at Date"); + GlAccount.CalcFields("Net Change", "Balance at Date", "Additional-Currency Net Change", "Add.-Currency Balance at Date", "Budgeted Amount", "Budget at Date"); EXRTrialBalanceBuffer."G/L Account No." := GlAccount."No."; - EXRTrialBalanceBuffer."Dimension 1 Code" := CopyStr(GLAccount.GetFilter("Global Dimension 1 Code"), 1, MaxStrLen(EXRTrialBalanceBuffer."Dimension 1 Code")); - EXRTrialBalanceBuffer."Dimension 2 Code" := CopyStr(GLAccount.GetFilter("Global Dimension 2 Code"), 1, MaxStrLen(EXRTrialBalanceBuffer."Dimension 2 Code")); + EXRTrialBalanceBuffer."Dimension 1 Code" := CopyStr(GLAccount.GetFilter("Global Dimension 1 Filter"), 1, MaxStrLen(EXRTrialBalanceBuffer."Dimension 1 Code")); + EXRTrialBalanceBuffer."Dimension 2 Code" := CopyStr(GLAccount.GetFilter("Global Dimension 2 Filter"), 1, MaxStrLen(EXRTrialBalanceBuffer."Dimension 2 Code")); EXRTrialBalanceBuffer."Business Unit Code" := CopyStr(GLAccount.GetFilter("Business Unit Filter"), 1, MaxStrLen(EXRTrialBalanceBuffer."Business Unit Code")); EXRTrialBalanceBuffer.Validate("Net Change", GLAccount."Net Change"); EXRTrialBalanceBuffer.Validate(Balance, GLAccount."Balance at Date"); EXRTrialBalanceBuffer.Validate("Net Change (ACY)", GLAccount."Additional-Currency Net Change"); EXRTrialBalanceBuffer.Validate("Balance (ACY)", GLAccount."Add.-Currency Balance at Date"); + EXRTrialBalanceBuffer.Validate("Budget (Net)", GLAccount."Budgeted Amount"); + EXRTrialBalanceBuffer.Validate("Budget (Bal. at Date)", GLAccount."Budget at Date"); + EXRTrialBalanceBuffer.CalculateBudgetComparisons(); EXRTrialBalanceBuffer.Insert(true); end; } \ No newline at end of file diff --git a/Apps/W1/ReportLayouts/app/.objidconfig b/Apps/W1/FieldServiceIntegration/app/.objidconfig similarity index 100% rename from Apps/W1/ReportLayouts/app/.objidconfig rename to Apps/W1/FieldServiceIntegration/app/.objidconfig diff --git a/Apps/W1/FieldServiceIntegration/app/ExtensionLogo.png b/Apps/W1/FieldServiceIntegration/app/ExtensionLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..4d2c9a626cb9617350617c40cd73904129d4c108 GIT binary patch literal 5446 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 literal 0 HcmV?d00001 diff --git a/Apps/W1/FieldServiceIntegration/app/app.json b/Apps/W1/FieldServiceIntegration/app/app.json new file mode 100644 index 0000000000..3b530945a2 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/app.json @@ -0,0 +1,45 @@ +{ + "id": "1ba1031e-eae9-4f20-b9d2-d19b6d1e3f29", + "name": "Field Service Integration", + "publisher": "Microsoft", + "version": "25.0.0.0", + "brief": "Field Service Integration", + "description": "Field Service Integration", + "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009", + "EULA": "https://go.microsoft.com/fwlink/?linkid=2182906", + "help": "https://go.microsoft.com/fwlink/?linkid=2206519", + "contextSensitiveHelpUrl": "https://learn.microsoft.com/dynamics365/business-central/", + "url": "https://go.microsoft.com/fwlink/?LinkId=724011", + "logo": "ExtensionLogo.png", + "dependencies": [ + + ], + "internalsVisibleTo": [ + { + "id": "41b3ab6e-3f20-47c7-a67f-feccc4d58a55", + "name": "Field Service Integration Test Library", + "publisher": "Microsoft" + } + ], + "screenshots": [ + + ], + "platform": "25.0.0.0", + "idRanges": [ + { + "from": 6610, + "to": 6640 + } + ], + "target": "OnPrem", + "resourceExposurePolicy": { + "allowDebugging": true, + "allowDownloadingSource": true, + "includeSourceInSymbolFile": true + }, + "application": "25.0.0.0", + "features": [ + "NoImplicitWith", + "TranslationFile" + ] +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSAssistedSetupSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSAssistedSetupSubscriber.Codeunit.al new file mode 100644 index 0000000000..1e06de493d --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSAssistedSetupSubscriber.Codeunit.al @@ -0,0 +1,50 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Environment.Configuration; +using System.Globalization; +using System.Media; +using Microsoft.Integration.D365Sales; + +codeunit 6613 "FS Assisted Setup Subscriber" +{ + var + CRMConnectionSetupTitleTxt: Label 'Set up a connection to %1', Comment = '%1 = CRM product name'; + CRMConnectionSetupShortTitleTxt: Label 'Connect to %1', Comment = '%1 = CRM product name', MaxLength = 32; + FSConnectionSetupHelpTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2270903', Locked = true; + CRMConnectionSetupDescriptionTxt: Label 'Connect your Dynamics 365 services for better insights. Data is exchanged between the apps for better productivity.'; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Guided Experience", 'OnRegisterAssistedSetup', '', false, false)] + local procedure RegisterFSAssistedSetup() + var + ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade"; + begin + if not ApplicationAreaMgmtFacade.IsBasicOnlyEnabled() then + RegisterAssistedSetup(); + end; + + internal procedure RegisterAssistedSetup() + var + GuidedExperience: Codeunit "Guided Experience"; + Language: Codeunit Language; + CRMProductName: Codeunit "CRM Product Name"; + AssistedSetupGroup: Enum "Assisted Setup Group"; + VideoCategory: Enum "Video Category"; + GuidedExperienceType: Enum "Guided Experience Type"; + CurrentGlobalLanguage: Integer; + begin + CurrentGlobalLanguage := GlobalLanguage; + if not GuidedExperience.Exists(GuidedExperienceType::"Assisted Setup", ObjectType::Page, Page::"FS Connection Setup Wizard") then begin + GuidedExperience.InsertAssistedSetup(StrSubstNo(CRMConnectionSetupTitleTxt, CRMProductName.FSServiceName()), + StrSubstNo(CRMConnectionSetupShortTitleTxt, CRMProductName.FSServiceName()), CRMConnectionSetupDescriptionTxt, 10, ObjectType::Page, + Page::"FS Connection Setup Wizard", AssistedSetupGroup::Connect, '', VideoCategory::Connect, FSConnectionSetupHelpTxt); + GlobalLanguage(Language.GetDefaultApplicationLanguageId()); + GuidedExperience.AddTranslationForSetupObjectTitle(GuidedExperienceType::"Assisted Setup", ObjectType::Page, + Page::"FS Connection Setup Wizard", Language.GetDefaultApplicationLanguageId(), StrSubstNo(CRMConnectionSetupTitleTxt, CRMProductName.FSServiceName())); + GlobalLanguage(CurrentGlobalLanguage); + end; + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSDataClassification.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSDataClassification.Codeunit.al new file mode 100644 index 0000000000..8b333e2c78 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSDataClassification.Codeunit.al @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Utilities; +using System.Privacy; + +codeunit 6614 "FS Data Classification" +{ + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Data Classification Eval. Data", 'OnCreateEvaluationDataOnAfterClassifyTablesToNormal', '', false, false)] + local procedure OnClassifyTables() + begin + ClassifyTables(); + end; + + + local procedure ClassifyTables() + var + DataClassificationMgt: Codeunit "Data Classification Mgt."; + begin + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Connection Setup"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Bookable Resource"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Bookable Resource Booking"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS BookableResourceBookingHdr"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Customer Asset"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Customer Asset Category"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Project Task"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Resource Pay Type"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Warehouse"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Work Order"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Work Order Product"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Work Order Service"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Work Order Incident"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Work Order Substatus"); + DataClassificationMgt.SetTableFieldsToNormal(Database::"FS Work Order Type"); + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSInstall.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSInstall.Codeunit.al new file mode 100644 index 0000000000..dad4a55041 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSInstall.Codeunit.al @@ -0,0 +1,131 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Upgrade; +using Microsoft.Integration.SyncEngine; +using System.Environment.Configuration; + +codeunit 6616 "FS Install" +{ + Subtype = Install; + + trigger OnInstallAppPerCompany() + begin + UpgradeConnectionSetup(); + UpgradeIntegrationTableMappings(); + UpgradeAssistedSetup(); + end; + + local procedure UpgradeConnectionSetup() + var + FSConnectionSetupOld: Record Microsoft.Integration.FieldService."FS Connection Setup"; + FSConnectionSetupNew: Record "FS Connection Setup"; + UpgradeTag: Codeunit "Upgrade Tag"; + begin + if UpgradeTag.HasUpgradeTag(GetConnectionSetupUpgradeTag()) then + exit; + + if FSConnectionSetupOld.FindFirst() then begin + FSConnectionSetupNew.Init(); + FSConnectionSetupNew.TransferFields(FSConnectionSetupOld); + FSConnectionSetupNew.Insert(); + FSConnectionSetupOld."Is Enabled" := false; + FSConnectionSetupOld.Modify(); + end; + + UpgradeTag.SetUpgradeTag(GetConnectionSetupUpgradeTag()); + end; + + local procedure UpgradeIntegrationTableMappings() + var + IntegrationTableMapping: Record "Integration Table Mapping"; + UpgradeTag: Codeunit "Upgrade Tag"; + begin + if UpgradeTag.HasUpgradeTag(GetIntegrationTableMappingsUpgradeTag()) then + exit; + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Bookable Resource"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Bookable Resource"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Bookable Resource Booking"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Bookable Resource Booking"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS BookableResourceBookingHdr"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS BookableResourceBookingHdr"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Customer Asset"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Customer Asset"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Customer Asset Category"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Customer Asset Category"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Project Task"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Project Task"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Resource Pay Type"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Resource Pay Type"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Work Order"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Work Order"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Work Order Incident"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Work Order Incident"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Work Order Product"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Work Order Product"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Work Order Service"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Work Order Service"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Work Order Substatus"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Work Order Substatus"); + + IntegrationTableMapping.SetRange("Integration Table ID", Database::Microsoft.Integration.FieldService."FS Work Order Type"); + IntegrationTableMapping.ModifyAll("Integration Table ID", Database::"FS Work Order Type"); + + UpgradeTag.SetUpgradeTag(GetIntegrationTableMappingsUpgradeTag()); + end; + + local procedure UpgradeAssistedSetup() + var + GuidedExperience: Codeunit "Guided Experience"; + UpgradeTag: Codeunit "Upgrade Tag"; + begin + if UpgradeTag.HasUpgradeTag(GetAssistedSetupUpgradeTag()) then + exit; + +#if not CLEAN25 + GuidedExperience.Remove("Guided Experience Type"::"Assisted Setup", ObjectType::Page, Page::Microsoft.Integration.FieldService."FS Connection Setup Wizard"); +#else + GuidedExperience.Remove("Guided Experience Type"::"Assisted Setup", ObjectType::Page, 6421); // 6421 is the ID of the FS Connection Setup Wizard page in Base Application +#endif + + UpgradeTag.SetUpgradeTag(GetAssistedSetupUpgradeTag()); + end; + + local procedure GetConnectionSetupUpgradeTag(): Code[250] + begin + exit('MS-527221-ConnectionSetupUpgradeTag-20240510'); + end; + + local procedure GetIntegrationTableMappingsUpgradeTag(): Code[250] + begin + exit('MS-527221-IntegrationTableMappingsUpgradeTag-20240510'); + end; + + local procedure GetAssistedSetupUpgradeTag(): Code[250] + begin + exit('MS-527221-AssistedSetupUpgradeTag-20240510'); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Upgrade Tag", 'OnGetPerCompanyUpgradeTags', '', false, false)] + local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]]) + begin + PerCompanyUpgradeTags.Add(GetConnectionSetupUpgradeTag()); + PerCompanyUpgradeTags.Add(GetIntegrationTableMappingsUpgradeTag()); + PerCompanyUpgradeTags.Add(GetAssistedSetupUpgradeTag()); + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al new file mode 100644 index 0000000000..7ec11ca2a4 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntTableSubscriber.Codeunit.al @@ -0,0 +1,1200 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Project.Job; +using Microsoft.Foundation.NoSeries; +using Microsoft.Projects.Project.Setup; +using Microsoft.Integration.SyncEngine; +using Microsoft.Sales.Customer; +using System.Telemetry; +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.Projects.Project.Planning; +using Microsoft.Projects.Project.Ledger; +using Microsoft.Sales.History; + +codeunit 6610 "FS Int. Table Subscriber" +{ + SingleInstance = true; + + var + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + CRMSynchHelper: Codeunit "CRM Synch. Helper"; + RecordMustBeCoupledErr: Label '%1 %2 must be coupled to %3.', Comment = '%1 = table caption, %2 = primary key value, %3 - service name'; + RecordCoupledToDeletedErr: Label '%1 %2 is coupled to a deleted record.', Comment = '%1 = table caption, %2 = primary key value'; + JobJournalIncorrectSetupErr: Label 'You must set up %1 correctly on %2.', Comment = '%1 = a table name, %2 = a table name'; + CategoryTok: Label 'AL Field Service Integration', Locked = true; + MustBeCoupledErr: Label '%1 %2 must be coupled to a Business Central %3', Comment = '%1 = a table name, %2 - a guid, %3 = a table name'; + DoesntExistErr: Label '%1 %2 doesn''t exist in %3', Comment = '%1 = a table name, %2 - a guid, %3 = Field Service service name'; + CoupledToDeletedErr: Label '%1 %2 is coupled to a deleted Business Central %3. You must re-couple it.', Comment = '%1 = a table name, %2 - a guid, %3 = a table name'; + CoupledToNonServiceErr: Label 'To synchronize this work order service, %1 %2 must be coupled to an item whose type is not set to Inventory. It is curretly coupled to item %3.', Comment = '%1 = a table name, %2 - a guid, %3 = an item name'; + CoupledToBlockedItemErr: Label 'To synchronize this work order service, %1 %2 must be coupled to an item that is not blocked. It is curretly coupled to item %3.', Comment = '%1 = a table name, %2 - a guid, %3 = an item name'; + CoupledToItemWithWrongUOMErr: Label 'To synchronize this work order service, %1 %2 must be coupled to an item that has base unit of measure set to %4. It is curretly coupled to item %3.', Comment = '%1 = a table name, %2 - a guid, %3 = an item name, %4 - unit of measure name'; + UnableToModifyWOSTxt: Label 'Unable to update work order service.', Locked = true; + UnableToModifyWOPTxt: Label 'Unable to update work order product.', Locked = true; + TestServerAddressTok: Label '@@test@@', Locked = true; + FSConnEnabledTelemetryErr: Label 'User is trying to set up the connection with Dataverse, while the existing connections with Field Service is enabled.', Locked = true; + FSConnEnabledErr: Label 'To set up the connection with Dataverse, you must first disable the existing connection with Field Service.'; + ShowFSConnectionSetupLbl: Label 'Field Service Integration Setup'; + ShowFSConnectionSetupDescLbl: Label 'Shows Field Service Integration Setup page where you can disable the connection.'; + CompanyFilterStrengthenedQst: Label 'This will make the synchronization engine process only %1 entities that correspond to the current company. Do you want to continue?', Comment = '%1 - a table caption'; + CompanyFilterStrengthenedFSMsg: Label 'The synchronization will consider only %1 entities whose work order in %3 has an External Project that belongs to company %2.', Comment = '%1 - a table caption; %2 - current company name; %3 - Dynamics 365 service name'; + InsufficientPermissionsTxt: Label 'Insufficient permissions.', Locked = true; + NoProjectUsageLinkTxt: Label 'Unable to find Project Usage Link.', Locked = true; + NoProjectPlanningLineTxt: Label 'Unable to find Project Planning Line.', Locked = true; + MultiCompanySyncEnabledTxt: Label 'Multi-Company Synch Enabled', Locked = true; + FSEntitySynchTxt: Label 'Synching a field service entity.', Locked = true; + + + [EventSubscriber(ObjectType::Table, Database::"CDS Connection Setup", 'OnEnsureConnectionSetupIsDisabled', '', false, false)] + local procedure OnEnsureConnectionSetupIsDisabled() + var + FSConnectionSetup: Record "FS Connection Setup"; + ErrorInfo: ErrorInfo; + begin + if FSConnectionSetup.Get() then + if FSConnectionSetup.IsEnabled() then + if FSConnectionSetup."Server Address" <> TestServerAddressTok then begin + Session.LogMessage('0000MQW', FSConnEnabledTelemetryErr, Verbosity::Warning, DataClassification::CustomerContent, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + ErrorInfo.Message := FSConnEnabledErr; + ErrorInfo.AddNavigationAction(ShowFSConnectionSetupLbl, ShowFSConnectionSetupDescLbl); + ErrorInfo.PageNo(Page::"FS Connection Setup"); + Error(ErrorInfo); + end; + end; + + [EventSubscriber(ObjectType::Table, Database::"Integration Table Mapping", 'OnEnableMultiCompanySynchronization', '', false, false)] + local procedure OnEnableMultiCompanySynchronization(var IntegrationTableMapping: Record "Integration Table Mapping"; var IsHandled: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + CDSCompany: Record "CDS Company"; + FSSetupDefaults: Codeunit "FS Setup Defaults"; + CRMProductName: Codeunit "CRM Product Name"; + IntegrationRecordRef: RecordRef; + CompanyIdFieldRef: FieldRef; + MessageTxt: Text; + begin + if IsHandled then + exit; + + if IntegrationTableMapping.Type <> IntegrationTableMapping.Type::Dataverse then + exit; + + if not FSConnectionSetup.IsEnabled() then + exit; + + if IntegrationTableMapping."Table ID" = Database::"Job Journal Line" then begin + IsHandled := true; + IntegrationRecordRef.Open(IntegrationTableMapping."Integration Table ID"); + + if not CDSIntegrationMgt.FindCompanyIdField(IntegrationRecordRef, CompanyIdFieldRef) then + exit; + + if not CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + exit; + + if GuiAllowed() then + if not Confirm(StrSubstNo(CompanyFilterStrengthenedQst, IntegrationRecordRef.Caption())) then + Error(''); + + IntegrationRecordRef.SetView(IntegrationTableMapping.GetIntegrationTableFilter()); + CompanyIdFieldRef.SetRange(CDSCompany.CompanyId); + IntegrationTableMapping.SetIntegrationTableFilter(FSSetupDefaults.GetTableFilterFromView(IntegrationTableMapping."Integration Table ID", IntegrationRecordRef.Caption(), IntegrationRecordRef.GetView())); + MessageTxt := StrSubstNo(CompanyFilterStrengthenedFSMsg, IntegrationRecordRef.Caption(), CompanyName(), CRMProductName.FSServiceName()); + if IntegrationTableMapping.Modify() then + if GuiAllowed() then + if MessageTxt <> '' then + Message(MessageTxt); + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeTransferRecordFields', '', false, false)] + local procedure OnBeforeTransferRecordFields(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + FSProjectTask: Record "FS Project Task"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + CRMIntegrationRecord: Record "CRM Integration Record"; + JobJournalLine: Record "Job Journal Line"; + JobTask: Record "Job Task"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of + 'FS Work Order Product-Job Journal Line', + 'FS Work Order Service-Job Journal Line': + begin + if SourceRecordRef.Number = Database::"FS Work Order Product" then begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + FSProjectTask.Get(FSWorkOrderProduct.ProjectTask); + end + else begin + SourceRecordRef.SetTable(FSWorkOrderService); + FSProjectTask.Get(FSWorkOrderService.ProjectTask); + end; + + if not CRMIntegrationRecord.FindByCRMID(FSProjectTask.ProjectTaskId) then + Error(RecordMustBeCoupledErr, FSProjectTask.TableCaption, Format(FSProjectTask.ProjectTaskId), 'Business Central'); + + if not JobTask.GetBySystemId(CRMIntegrationRecord."Integration ID") then + Error(RecordCoupledToDeletedErr, FSProjectTask.TableCaption, Format(FSProjectTask.ProjectTaskId)); + + DestinationRecordRef.SetTable(JobJournalLine); + JobJournalLine."Job No." := JobTask."Job No."; + JobJournalLine."Job Task No." := JobTask."Job Task No."; + DestinationRecordRef.GetTable(JobJournalLine); + 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 + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderService: Record "FS Work Order Service"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + JobJournalLine: Record "Job Journal Line"; + SourceRecordRef: RecordRef; + DestinationRecordRef: RecordRef; + DurationInHours: Decimal; + DurationInMinutes: Decimal; + Quantity: Decimal; + QuantityToTransferToInvoice: Decimal; + QuantityCurrentlyConsumed: Decimal; + QuantityCurrentlyInvoiced: Decimal; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + if IsValueFound then + exit; + + if SourceFieldRef.Number() = DestinationFieldRef.Number() then + if SourceFieldRef.Record().Number() = DestinationFieldRef.Record().Number() then + exit; + + if SourceFieldRef.Record().Number = Database::"FS Work Order Service" then + case SourceFieldRef.Name() of + 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 + DurationInMinutes := FSWorkOrderService.Duration; + DurationInHours := (DurationInMinutes / 60); + NewValue := DurationInHours - QuantityCurrentlyConsumed; + end; + 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; + end; + IsValueFound := true; + NeedsConversion := false; + exit; + end; + FSWorkOrderService.FieldName(Description): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrderService); + DestinationRecordRef := DestinationFieldRef.Record(); + DestinationRecordRef.SetTable(JobJournalLine); + + if JobJournalLine.Type = JobJournalLine.Type::Resource then + if FSBookableResourceBooking.Get(FSWorkOrderService.Booking) then + if SourceFieldRef.Name() = FSWorkOrderService.FieldName(Description) then begin + NewValue := FSBookableResourceBooking.Name; + IsValueFound := true; + NeedsConversion := false; + exit; + end; + + if JobJournalLine.Type = JobJournalLine.Type::Item then + if SourceFieldRef.Name() = FSWorkOrderService.FieldName(Description) then begin + NewValue := FSWorkOrderService.Name; + IsValueFound := true; + NeedsConversion := false; + exit; + end; + end; + end; + + if SourceFieldRef.Record().Number = Database::"FS Work Order Product" then + case SourceFieldRef.Name() of + FSWorkOrderProduct.FieldName(Quantity), + FSWorkOrderProduct.FieldName(QtyToBill): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrderProduct); + SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + if SourceFieldRef.Name() = FSWorkOrderProduct.FieldName(Quantity) then begin + Quantity := FSWorkOrderProduct.Quantity - QuantityCurrentlyConsumed; + NewValue := Quantity; + end; + if SourceFieldRef.Name() = FSWorkOrderProduct.FieldName(QtyToBill) then begin + QuantityToTransferToInvoice := FSWorkOrderProduct.QtyToBill - QuantityCurrentlyInvoiced; + NewValue := QuantityToTransferToInvoice; + end; + IsValueFound := true; + NeedsConversion := false; + exit; + end; + FSWorkOrderProduct.FieldName(Description): + begin + SourceRecordRef := SourceFieldRef.Record(); + SourceRecordRef.SetTable(FSWorkOrderProduct); + + NewValue := FSWorkOrderProduct.Name; + IsValueFound := true; + NeedsConversion := false; + exit; + end; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterInsertRecord', '', false, false)] + local procedure HandleOnAfterInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + JobJournalLine: Record "Job Journal Line"; + BudgetJobJournalLine: Record "Job Journal Line"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + CRMIntegrationRecord: Record "CRM Integration Record"; + SalesInvoiceHeader: Record "Sales Invoice Header"; + JobPlanningLineInvoice: Record "Job Planning Line Invoice"; + JobUsageLink: Record "Job Usage Link"; + SourceDestCode: Text; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of + 'FS Work Order Product-Job Journal Line': + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + DestinationRecordRef.SetTable(JobJournalLine); + ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderProduct, JobJournalLine); + end; + 'FS Work Order Service-Job Journal Line': + begin + SourceRecordRef.SetTable(FSWorkOrderService); + DestinationRecordRef.SetTable(JobJournalLine); + BudgetJobJournalLine.ReadIsolation(IsolationLevel::ReadCommitted); + if BudgetJobJournalLine.Get(JobJournalLine."Journal Template Name", JobJournalLine."Journal Batch Name", JobJournalLine."Line No." - BudgetJobJournalLineNoOffset()) then + if BudgetJobJournalLine."Line Type" = BudgetJobJournalLine."Line Type"::Budget then + if not CRMIntegrationRecord.Get(FSWorkOrderService.WorkOrderServiceId, BudgetJobJournalLine.SystemId) then begin + // Budget job journal line is created in the OnBeforeInsertRecord subscriber. We must couple it here. + // If we try to couple it in the OnBeforeInsert subscriber, the synch engine will find it and just update the Integration Id with the other journal line's system id. + // We want to commit the coupling of the budget journal line before we attempt posting, because posting could fail and roll back the coupling of the budget line + CRMIntegrationRecord.InsertRecord(FSWorkOrderService.WorkOrderServiceId, BudgetJobJournalLine.SystemId, Database::"Job Journal Line"); + Commit(); + end; + ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderService, JobJournalLine); + end; + 'Sales Invoice Header-CRM Invoice': + begin + SourceRecordRef.SetTable(SalesInvoiceHeader); + JobPlanningLineInvoice.ReadIsolation := JobPlanningLineInvoice.ReadIsolation::ReadCommitted; + JobPlanningLineInvoice.SetRange("Document Type", JobPlanningLineInvoice."Document Type"::"Posted Invoice"); + JobPlanningLineInvoice.SetRange("Document No.", SalesInvoiceHeader."No."); + if JobPlanningLineInvoice.FindSet() then + repeat + JobUsageLink.SetRange("Job No.", JobPlanningLineInvoice."Job No."); + JobUsageLink.SetRange("Job Task No.", JobPlanningLineInvoice."Job Task No."); + JobUsageLink.SetRange("Line No.", JobPlanningLineInvoice."Job Planning Line No."); + if JobUsageLink.FindFirst() then + if not IsNullGuid(JobUsageLink."External Id") then + if FSWorkorderProduct.Get(JobUsageLink."External Id") then begin + FSWorkOrderProduct.QuantityInvoiced += JobPlanningLineInvoice."Quantity Transferred"; + if not TryModifyWorkOrderProduct(FSWorkOrderProduct) then begin + Session.LogMessage('0000MMV', UnableToModifyWOPTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + ClearLastError(); + end; + end + else + if FSWorkorderService.Get(JobUsageLink."External Id") then begin + FSWorkorderService.DurationInvoiced += (JobPlanningLineInvoice."Quantity Transferred" * 60); + if not TryModifyWorkOrderService(FSWorkOrderService) then begin + Session.LogMessage('0000MMW', UnableToModifyWOSTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + ClearLastError(); + end; + end; + until JobPlanningLineInvoice.Next() = 0; + end; + end; + end; + + local procedure BudgetJobJournalLineNoOffset(): Integer + begin + // When a Work Order Service that has a coupled bookable resource is synchronized to BC + // Two project journal lines are coupled: one billable line for the service item and one budget line for the resource + // This is the offset number of the second line no. from the first line no. + exit(37); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeIgnoreUnchangedRecordHandled', '', false, false)] + local procedure HandleOnBeforeIgnoreUnchangedRecordHandled(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + JobJournalLine: Record "Job Journal Line"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + SourceDestCode: Text; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of + 'FS Work Order Product-Job Journal Line': + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + DestinationRecordRef.SetTable(JobJournalLine); + ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderProduct, JobJournalLine); + end; + 'FS Work Order Service-Job Journal Line': + begin + SourceRecordRef.SetTable(FSWorkOrderService); + DestinationRecordRef.SetTable(JobJournalLine); + UpdateCorrelatedJobJournalLine(SourceRecordRef, DestinationRecordRef); + ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderService, JobJournalLine); + end; + end; + 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 + 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-Job Journal Line': + UpdateCorrelatedJobJournalLine(SourceRecordRef, DestinationRecordRef); + end; + end; + + local procedure UpdateCorrelatedJobJournalLine(var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) + var + JobJournalLine: Record "Job Journal Line"; + FSWorkOrderService: Record "FS Work Order Service"; + CorrelatedJobJournalLine: Record "Job Journal Line"; + CRMIntegrationRecord: Record "CRM Integration Record"; + CorrelatedJobJournalLineId: Guid; + QuantityCurrentlyConsumed: Decimal; + QuantityCurrentlyInvoiced: Decimal; + DurationInHours: Decimal; + begin + // Work Order Services with coupled bookable resources couple to two Project Journal Lines: one budget (for the resource) and the other billable (for the item of type service) + // Scheduled delta synch updates only one of them (that is how it is designed, it finds the first coupled one) + // Therefore, we must find the other correlated line and update it here + SourceRecordRef.SetTable(FSWorkOrderService); + DestinationRecordRef.SetTable(JobJournalLine); + SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + CRMIntegrationRecord.SetRange("Table ID", Database::"Job Journal Line"); + CRMIntegrationRecord.SetRange("CRM ID", FSWorkOrderService.WorkOrderServiceId); + if CRMIntegrationRecord.FindSet() then + repeat + if CRMIntegrationRecord."Integration ID" <> JobJournalLine.SystemId then + CorrelatedJobJournalLineId := CRMIntegrationRecord."Integration ID"; + until CRMIntegrationRecord.Next() = 0; + + if IsNullGuid(CorrelatedJobJournalLineId) then + exit; + + if not CorrelatedJobJournalLine.GetBySystemId(CorrelatedJobJournalLineId) then + exit; + + DurationInHours := FSWorkOrderService.Duration; + DurationInHours := (DurationInHours / 60); + DurationInHours := DurationInHours - QuantityCurrentlyConsumed; + if (CorrelatedJobJournalLine.Quantity <> DurationInHours) then begin + CorrelatedJobJournalLine.Quantity := DurationInHours; + CorrelatedJobJournalLine.Modify(); + end; + + case CorrelatedJobJournalLine."Line Type" of + CorrelatedJobJournalLine."Line Type"::Budget, + CorrelatedJobJournalLine."Line Type"::" ": + if CorrelatedJobJournalLine."Qty. to Transfer to Invoice" <> 0 then begin + CorrelatedJobJournalLine."Qty. to Transfer to Invoice" := 0; + CorrelatedJobJournalLine.Modify(); + end; + CorrelatedJobJournalLine."Line Type"::Billable, + CorrelatedJobJournalLine."Line Type"::"Both Budget and Billable": + begin + DurationInHours := FSWorkOrderService.DurationToBill; + DurationInHours := (DurationInHours / 60); + DurationInHours := DurationInHours - QuantityCurrentlyInvoiced; + if CorrelatedJobJournalLine."Qty. to Transfer to Invoice" <> DurationInHours then begin + CorrelatedJobJournalLine."Qty. to Transfer to Invoice" := DurationInHours; + CorrelatedJobJournalLine.Modify(); + end; + end; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnAfterModifyRecord', '', false, false)] + local procedure HandleOnAfterModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + JobJournalLine: Record "Job Journal Line"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + SourceDestCode: Text; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of + 'FS Work Order Product-Job Journal Line': + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + DestinationRecordRef.SetTable(JobJournalLine); + ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderProduct, JobJournalLine); + end; + 'FS Work Order Service-Job Journal Line': + begin + SourceRecordRef.SetTable(FSWorkOrderService); + DestinationRecordRef.SetTable(JobJournalLine); + ConditionallyPostJobJournalLine(FSConnectionSetup, FSWorkOrderService, JobJournalLine); + end; + end; + end; + + local procedure ConditionallyPostJobJournalLine(var FSConnectionSetup: Record "FS Connection Setup"; var FSWorkOrderProduct: Record "FS Work Order Product"; var JobJournalLine: Record "Job Journal Line") + var + JobJnlPostLine: Codeunit "Job Jnl.-Post Line"; + begin + case FSConnectionSetup."Line Post Rule" of + "FS Work Order Line Post Rule"::LineUsed: + if FSWorkOrderProduct.LineStatus = FSWorkOrderProduct.LineStatus::Used then + JobJnlPostLine.RunWithCheck(JobJournalLine); + "FS Work Order Line Post Rule"::WorkOrderCompleted: + if FSWorkOrderProduct.WorkOrderStatus in [FSWorkOrderProduct.WorkOrderStatus::Completed] then + JobJnlPostLine.RunWithCheck(JobJournalLine); + else + exit; + end; + end; + + local procedure ConditionallyPostJobJournalLine(var FSConnectionSetup: Record "FS Connection Setup"; var FSWorkOrderService: Record "FS Work Order Service"; var JobJournalLine: Record "Job Journal Line") + begin + case FSConnectionSetup."Line Post Rule" of + "FS Work Order Line Post Rule"::LineUsed: + if FSWorkOrderService.LineStatus = FSWorkOrderService.LineStatus::Used then + PostJobJournalLine(FSWorkOrderService, JobJournalLine); + "FS Work Order Line Post Rule"::WorkOrderCompleted: + if FSWorkOrderService.WorkOrderStatus in [FSWorkOrderService.WorkOrderStatus::Completed] then + PostJobJournalLine(FSWorkOrderService, JobJournalLine); + else + exit; + end; + end; + + local procedure PostJobJournalLine(var FSWorkOrderService: Record "FS Work Order Service"; var JobJournalLine: Record "Job Journal Line") + var + CRMIntegrationRecord: Record "CRM Integration Record"; + CorrelatedJobJournalLine: Record "Job Journal Line"; + JobJnlPostLine: Codeunit "Job Jnl.-Post Line"; + JobJournalLineId: Guid; + begin + JobJournalLineId := JobJournalLine.SystemId; + JobJnlPostLine.RunWithCheck(JobJournalLine); + + // Work Order Services couple to two Project Journal Lines (one budget line for the resource and one billable line for the item of type service) + // we must find the other coupled lines and post them as well. + CRMIntegrationRecord.SetRange("Table ID", Database::"Job Journal Line"); + CRMIntegrationRecord.SetRange("CRM ID", FSWorkOrderService.WorkOrderServiceId); + if CRMIntegrationRecord.FindSet() then + repeat + if CRMIntegrationRecord."Integration ID" <> JobJournalLine.SystemId then + if CorrelatedJobJournalLine.GetBySystemId(CRMIntegrationRecord."Integration ID") then + JobJnlPostLine.RunWithCheck(CorrelatedJobJournalLine); + until CRMIntegrationRecord.Next() = 0; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeInsertRecord', '', false, false)] + local procedure HandleOnBeforeInsertRecord(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef) + var + FSProjectTask: Record "FS Project Task"; + Customer: Record Customer; + CRMIntegrationRecord: Record "CRM Integration Record"; + Job: Record Job; + JobTask: Record "Job Task"; + JobJournalLine: Record "Job Journal Line"; + FSConnectionSetup: Record "FS Connection Setup"; + JobJournalBatch: Record "Job Journal Batch"; + JobJournalTemplate: Record "Job Journal Template"; + JobsSetup: Record "Jobs Setup"; + Resource: Record Resource; + FSBookableResource: Record "FS Bookable Resource"; + LastJobJournalLine: Record "Job Journal Line"; + CRMProductName: Codeunit "CRM Product Name"; + NoSeries: Codeunit "No. Series"; + RecID: RecordId; + SourceDestCode: Text; + BillingAccId: Guid; + ServiceAccId: Guid; + Handled: Boolean; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + SourceDestCode := GetSourceDestCode(SourceRecordRef, DestinationRecordRef); + + case SourceDestCode of + 'Service Item-FS Customer Asset': + SetCompanyId(DestinationRecordRef); + 'FS Customer Asset-Service Item': + begin + SetCompanyId(SourceRecordRef); + SourceRecordRef.Modify() + end; + 'Resource-FS Bookable Resource': + begin + SetCompanyId(DestinationRecordRef); + SourceRecordRef.SetTable(Resource); + DestinationRecordRef.SetTable(FSBookableResource); + FSBookableResource.TimeZone := 92; + case Resource.Type of + Resource.Type::Machine: + FSBookableResource.ResourceType := FSBookableResource.ResourceType::Equipment; + Resource.Type::Person: + if Resource."Vendor No." <> '' then + FSBookableResource.ResourceType := FSBookableResource.ResourceType::Account + else + FSBookableResource.ResourceType := FSBookableResource.ResourceType::Generic; + end; + DestinationRecordRef.GetTable(FSBookableResource); + end; + 'FS Bookable Resource-Resource': + begin + SetCompanyId(SourceRecordRef); + SourceRecordRef.SetTable(FSBookableResource); + DestinationRecordRef.SetTable(Resource); + case FSBookableResource.ResourceType of + FSBookableResource.ResourceType::Equipment: + Resource.Type := Resource.Type::Machine; + FSBookableResource.ResourceType::Account, + FSBookableResource.ResourceType::Generic: + Resource.Type := Resource.Type::Person; + end; + Resource."Base Unit of Measure" := FSConnectionSetup."Hour Unit of Measure"; + DestinationRecordRef.GetTable(Resource); + SourceRecordRef.Modify(); + end; + 'Job Task-FS Project Task': + begin + SetCompanyId(DestinationRecordRef); + SourceRecordRef.SetTable(JobTask); + if Job.Get(JobTask."Job No.") then begin + DestinationRecordRef.Field(FSProjectTask.FieldNo(ProjectDescription)).Value := Job.Description; + if Job."Bill-to Customer No." <> '' then + if CRMSynchHelper.FindRecordIDByPK(Database::Customer, Job."Bill-to Customer No.", RecID) then + if CRMIntegrationRecord.FindIDFromRecordID(RecID, BillingAccId) then + DestinationRecordRef.Field(FSProjectTask.FieldNo(BillingAccountId)).Value := BillingAccId + else + Error(RecordMustBeCoupledErr, Customer.TableCaption(), Job."Bill-to Customer No.", CRMProductName.CDSServiceName()); + if Job."Sell-to Customer No." <> '' then + if CRMSynchHelper.FindRecordIDByPK(Database::Customer, Job."Sell-to Customer No.", RecID) then + if CRMIntegrationRecord.FindIDFromRecordID(RecID, ServiceAccId) then + DestinationRecordRef.Field(FSProjectTask.FieldNo(ServiceAccountId)).Value := ServiceAccId + else + Error(RecordMustBeCoupledErr, Customer.TableCaption(), Job."Bill-to Customer No.", CRMProductName.CDSServiceName()) + else + DestinationRecordRef.Field(FSProjectTask.FieldNo(ServiceAccountId)).Value := BillingAccId; + end; + end; + 'FS Work Order Product-Job Journal Line', + 'FS Work Order Service-Job Journal Line': + begin + DestinationRecordRef.SetTable(JobJournalLine); + OnSetUpNewLineOnNewLine(JobJournalLine, JobJournalTemplate, JobJournalBatch, Handled); + if not Handled then begin + FSConnectionSetup.Get(); + JobsSetup.Get(); + Job.Get(JobJournalLine."Job No."); + if not JobJournalTemplate.Get(FSConnectionSetup."Job Journal Template") then + Error(JobJournalIncorrectSetupErr, JobJournalTemplate.TableCaption(), FSConnectionSetup.TableCaption()); + if not JobJournalBatch.Get(FSConnectionSetup."Job Journal Template", FSConnectionSetup."Job Journal Batch") then + Error(JobJournalIncorrectSetupErr, JobJournalBatch.TableCaption(), FSConnectionSetup.TableCaption()); + JobJournalLine."Journal Template Name" := JobJournalTemplate.Name; + JobJournalLine."Journal Batch Name" := JobJournalBatch.Name; + LastJobJournalLine.SetRange("Journal Template Name", JobJournalTemplate.Name); + LastJobJournalLine.SetRange("Journal Batch Name", JobJournalBatch.Name); + if LastJobJournalLine.FindLast() then begin + JobJournalLine."Posting Date" := LastJobJournalLine."Posting Date"; + JobJournalLine."Document Date" := LastJobJournalLine."Posting Date"; + if JobsSetup."Document No. Is Job No." and (LastJobJournalLine."Document No." = '') then + JobJournalLine."Document No." := JobJournalLine."Job No." + else + JobJournalLine."Document No." := LastJobJournalLine."Document No."; + end else begin + JobJournalLine."Posting Date" := WorkDate(); + JobJournalLine."Document Date" := WorkDate(); + if JobsSetup."Document No. Is Job No." then begin + if JobJournalLine."Document No." = '' then + JobJournalLine."Document No." := JobJournalLine."Job No."; + end else + if JobJournalBatch."No. Series" <> '' then begin + Clear(NoSeries); + JobJournalLine."Document No." := NoSeries.GetNextNo(JobJournalBatch."No. Series", JobJournalLine."Posting Date"); + end; + end; + JobJournalLine."Line No." := LastJobJournalLine."Line No." + 10000; + JobJournalLine."Source Code" := JobJournalTemplate."Source Code"; + JobJournalLine."Reason Code" := JobJournalBatch."Reason Code"; + JobJournalLine."Posting No. Series" := JobJournalBatch."Posting No. Series"; + JobJournalLine."Price Calculation Method" := Job.GetPriceCalculationMethod(); + JobJournalLine."Cost Calculation Method" := Job.GetCostCalculationMethod(); + SetJobJournalLineTypesAndNo(FSConnectionSetup, SourceRecordRef, JobJournalLine); + end; + DestinationRecordRef.GetTable(JobJournalLine); + end; + 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 + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + CRMIntegrationRecord: Record "CRM Integration Record"; + JobJournalLine: Record "Job Journal Line"; + IntegrationRecSynchInvoke: Codeunit "Integration Rec. Synch. Invoke"; + FSQuantityToBill: Decimal; + FSQuantity: Decimal; + QuantityCurrentlyConsumed: Decimal; + QuantityCurrentlyInvoiced: Decimal; + CRMId: Guid; + begin + if DeletionConflictHandled then + exit; + + if not FSConnectionSetup.IsEnabled() then + exit; + + if IntegrationTableMapping."Table ID" <> Database::"Job Journal Line" then + exit; + + if ((IntegrationTableMapping."Integration Table ID" <> Database::"FS Work Order Service") and (IntegrationTableMapping."Integration Table ID" <> Database::"FS Work Order Product")) then + exit; + + SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + case SourceRecordRef.Number() of + Database::"FS Work Order Product": + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + CRMId := FSWorkOrderProduct.WorkOrderProductId; + FSQuantity := FSWorkOrderProduct.Quantity; + FSQuantityToBill := FSWorkOrderProduct.QtyToBill; + end; + Database::"FS Work Order Service": + begin + SourceRecordRef.SetTable(FSWorkOrderService); + CRMId := FSWorkOrderService.WorkOrderServiceId; + FSQuantity := FSWorkOrderService.Duration; + FSQuantity := (FSQuantity / 60); + FSQuantityToBill := FSWorkOrderService.DurationToBill; + FSQuantityToBill := (FSQuantityToBill / 60); + end; + else + exit; + end; + + // if quantities are equal to the current quantities in Project Planning Lines, then there is no need to create a new Project Journal Line. + // The Project Journal LIne has been deleted because of posting, and that is fine. Just tell the synch engine to do nothing (skip) + if (FSQuantity = QuantityCurrentlyConsumed) and (FSQuantityToBill = QuantityCurrentlyInvoiced) then begin + RecordState := RecordState::NotFound; + SynchAction := SynchAction::Skip; + DeletionConflictHandled := true; + exit; + end; + + // there is a difference between currently consumed/invoiced quantities and Quantity/Quantity to Bill + // we must instruct the synch engine to insert a new Project Journal Line (like the "Restore Record" deletion conflict strategy) + // The OnBeforeInsert subscriber will make sure that the Quantities on the new Project Journal Line take the current quantities in consideration + IntegrationRecSynchInvoke.PrepareNewDestination(IntegrationTableMapping, SourceRecordRef, CoupledRecordRef); + RecordState := RecordState::Coupled; + SynchAction := SynchAction::Insert; + + // delete the broken couplings (those journal lines that are coupled to this CRM Id but they can't be found by SystemId) + CRMIntegrationRecord.SetRange("CRM ID", CRMId); + CRMIntegrationRecord.SetRange("Table ID", Database::"Job Journal Line"); + if CRMIntegrationRecord.FindSet() then + repeat + if not JobJournalLine.GetBySystemId(CRMIntegrationRecord."Integration ID") then + CRMIntegrationRecord.Delete(); + until CRMIntegrationRecord.Next() = 0; + + DeletionConflictHandled := true; + end; + + local procedure SetCurrentProjectPlanningQuantities(var SourceRecordRef: RecordRef; var QuantityCurrentlyConsumed: Decimal; var QuantityCurrentlyInvoiced: Decimal) + var + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + CRMIntegrationRecord: Record "CRM Integration Record"; + JobUsageLink: Record "Job Usage Link"; + JobPlanningLine: Record "Job Planning Line"; + ExternalId: Guid; + ConsideronlyBudgetLineForConsumption: Boolean; + begin + QuantityCurrentlyConsumed := 0; + QuantityCurrentlyInvoiced := 0; + case SourceRecordRef.Number() of + Database::"FS Work Order Product": + begin + SourceRecordRef.SetTable(FSWorkOrderProduct); + ExternalId := FSWorkOrderProduct.WorkOrderProductId; + end; + Database::"FS Work Order Service": + begin + SourceRecordRef.SetTable(FSWorkOrderService); + ExternalId := FSWorkOrderService.WorkOrderServiceId; + if not IsNullGuid(FSWorkOrderService.Booking) then + if FSBookableResourceBooking.Get(FSWorkOrderService.Booking) then + if CRMIntegrationRecord.FindByCRMID(FSBookableResourceBooking.Resource) then + ConsideronlyBudgetLineForConsumption := true; + end; + else + exit; + end; + JobUsageLink.SetRange("External Id", ExternalId); + if JobUsageLink.FindSet() then + repeat + if JobPlanningLine.Get(JobUsageLink."Job No.", JobUsageLink."Job Task No.", JobUsageLink."Line No.") then begin + JobPlanningLine.CalcFields("Qty. Invoiced", "Qty. Transferred to Invoice"); + case ConsideronlyBudgetLineForConsumption of + true: + if JobPlanningLine."Line Type" = JobPlanningLine."Line Type"::Budget then + QuantityCurrentlyConsumed += JobPlanningLine.Quantity; + false: + QuantityCurrentlyConsumed += JobPlanningLine.Quantity; + end; + if JobPlanningLine."Qty. Invoiced" > 0 then + QuantityCurrentlyInvoiced += JobPlanningLine."Qty. Invoiced" + else begin + // try other invoicing quantities + ; + if JobPlanningLine."Qty. Transferred to Invoice" > 0 then + QuantityCurrentlyInvoiced += JobPlanningLine."Qty. Transferred to Invoice" + else + QuantityCurrentlyInvoiced += JobPlanningLine."Qty. to Transfer to Invoice"; + end; + end; + until JobUsageLink.Next() = 0; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Job Link Usage", 'OnAfterApplyUsage', '', false, false)] + local procedure HandleOnAfterApplyUsage(var JobLedgerEntry: Record "Job Ledger Entry"; var JobJournalLine: Record "Job Journal Line") + var + FSConnectionSetup: Record "FS Connection Setup"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + CRMIntegrationRecord: Record "CRM Integration Record"; + JobPlanningLine: Record "Job Planning Line"; + JobUsageLink: Record "Job Usage Link"; + begin + if not FSConnectionSetup.ReadPermission() then begin + Session.LogMessage('0000MMX', InsufficientPermissionsTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit; + end; + + if not FSConnectionSetup.IsEnabled() then + exit; + + JobUsageLink.SetRange("Entry No.", JobLedgerEntry."Entry No."); + if not JobUsageLink.FindFirst() then begin + Session.LogMessage('0000MN8', NoProjectUsageLinkTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit; + end; + + if not JobPlanningLine.Get(JobUsageLink."Job No.", JobUsageLink."Job Task No.", JobUsageLink."Line No.") then begin + Session.LogMessage('0000MN9', NoProjectPlanningLineTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit; + end; + + // set "Qty. to Transfer to Invoice" on Job Planning Line + if JobJournalLine."Qty. to Transfer to Invoice" <> 0 then begin + JobPlanningLine."Qty. to Transfer to Invoice" := JobJournalLine."Qty. to Transfer to Invoice"; + JobPlanningLine.Modify(); + end; + + // in Project Usage Link, save the id of the entity coupled to the job journal line + if not CRMIntegrationRecord.ReadPermission() then begin + Session.LogMessage('0000MMY', InsufficientPermissionsTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit; + end; + if not CRMIntegrationRecord.FindByRecordID(JobJournalLine.RecordId()) then + exit; + JobUsageLink."External Id" := CRMIntegrationRecord."CRM ID"; + JobUsageLink.Modify(); + + // write back consumption data to Field Service + if not FSWorkOrderProduct.WritePermission() then + exit; + if not FSWorkOrderService.WritePermission() then + exit; + Codeunit.Run(Codeunit::"CRM Integration Management"); + if FSWorkOrderProduct.Get(CRMIntegrationRecord."CRM ID") then begin + FSWorkOrderProduct.QuantityConsumed += JobPlanningLine.Quantity; + if not TryModifyWorkOrderProduct(FSWorkOrderProduct) then begin + Session.LogMessage('0000MMZ', UnableToModifyWOPTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + ClearLastError(); + end; + exit; + end; + if FSWorkOrderService.Get(CRMIntegrationRecord."CRM ID") then begin + // if the work order service has a bookable resource that is coupled to a Business Central resource + // then we only register consumption for the budget line + // not for the Billable line, as this will lead to double consumption registering + if not IsNullGuid(FSWorkOrderService.Booking) then + if FSBookableResourceBooking.Get(FSWorkOrderService.Booking) then + if CRMIntegrationRecord.FindByCRMID(FSBookableResourceBooking.Resource) then + if JobPlanningLine."Line Type" <> JobPlanningLine."Line Type"::Budget then + exit; + + FSWorkOrderService.DurationConsumed += (60 * JobPlanningLine.Quantity); + if not TryModifyWorkOrderService(FSWorkOrderService) then begin + Session.LogMessage('0000MN0', UnableToModifyWOSTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + ClearLastError(); + end; + end; + end; + + [TryFunction] + local procedure TryModifyWorkOrderProduct(var FSWorkOrderProduct: Record "FS Work Order Product") + begin + FSWorkOrderProduct.Modify(); + end; + + [TryFunction] + local procedure TryModifyWorkOrderService(var FSWorkOrderService: Record "FS Work Order Service") + begin + FSWorkOrderService.Modify(); + end; + + local procedure SetJobJournalLineTypesAndNo(var FSConnectionSetup: Record "FS Connection Setup"; var SourceRecordRef: RecordRef; var JobJournalLine: Record "Job Journal Line") + var + CRMIntegrationRecord: Record "CRM Integration Record"; + FSBookableResourceBooking: Record "FS Bookable Resource Booking"; + FSBookableResource: Record "FS Bookable Resource"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + Resource: Record Resource; + Item: Record Item; + CRMProduct: Record "CRM Product"; + BudgetJobJournalLine: Record "Job Journal Line"; + CRMProductName: Codeunit "CRM Product Name"; + BookableResourceCoupled: Boolean; + BookableResourceCoupledToDeleted: Boolean; + FSQuantity: Decimal; + FSQuantityToBill: Decimal; + QuantityCurrentlyConsumed: Decimal; + QuantityCurrentlyInvoiced: Decimal; + begin + SetCurrentProjectPlanningQuantities(SourceRecordRef, QuantityCurrentlyConsumed, QuantityCurrentlyInvoiced); + case SourceRecordRef.Number of + Database::"FS Work Order Product": + begin + JobJournalLine.Validate(Type, JobJournalLine.Type::Item); + SourceRecordRef.SetTable(FSWorkOrderProduct); + FSQuantity := FSWorkOrderProduct.Quantity; + FSQuantityToBill := FSWorkOrderProduct.QtyToBill; + + if not CRMIntegrationRecord.FindByCRMID(FSWorkOrderProduct.Product) then + Error(MustBeCoupledErr, FSWorkOrderProduct.FieldCaption(Product), Format(FSWorkOrderProduct.Product), Item.TableCaption()); + + if not CRMProduct.Get(FSWorkOrderProduct.Product) then + Error(DoesntExistErr, CRMProduct.TableCaption(), Format(FSWorkOrderProduct.Product), CRMProductName.FSServiceName()); + + if not Item.GetBySystemId(CRMIntegrationRecord."Integration ID") then + Error(CoupledToDeletedErr, FSWorkOrderProduct.FieldCaption(Product), Format(FSWorkOrderProduct.Product), Item.TableCaption()); + + JobJournalLine.Validate("Entry Type", JobJournalLine."Entry Type"::Usage); + if Item.Type = Item.Type::"Non-Inventory" then + JobJournalLine.Validate("Line Type", JobJournalLine."Line Type"::" ") + else + JobJournalLine.Validate("Line Type", JobJournalLine."Line Type"::Billable); + // set Item, but for work order products we must keep its Business Central Unit Cost + JobJournalLine.Validate("No.", Item."No."); + JobJournalLine.Validate("Unit Cost", Item."Unit Cost"); + JobJournalLine.Validate(Quantity, FSQuantity - QuantityCurrentlyConsumed); + JobJournalLine.Validate("Unit Price", Item."Unit Price"); + JobJournalLine.Validate("Qty. to Transfer to Invoice", FSQuantityToBill - QuantityCurrentlyInvoiced); + end; + Database::"FS Work Order Service": + begin + SourceRecordRef.SetTable(FSWorkOrderService); + FSQuantity := FSWorkOrderService.Duration; + FSQuantityToBill := FSWorkOrderService.DurationToBill; + FSQuantity := (FSQuantity / 60); + FSQuantityToBill := (FSQuantityToBill / 60); + + if not CRMProduct.Get(FSWorkOrderService.Service) then + Error(DoesntExistErr, FSWorkOrderService.FieldCaption(Service), Format(FSWorkOrderService.Service), FSConnectionSetup."Server Address"); + + if not CRMIntegrationRecord.FindByCRMID(FSWorkOrderService.Service) then + Error(MustBeCoupledErr, FSWorkOrderService.FieldCaption(Service), CRMProduct.ProductNumber, Item.TableCaption()); + + if not Item.GetBySystemId(CRMIntegrationRecord."Integration ID") then + Error(CoupledToDeletedErr, FSWorkOrderService.FieldCaption(Service), CRMProduct.ProductNumber, Item.TableCaption()); + + if Item.Type = Item.Type::Inventory then + Error(CoupledToNonServiceErr, FSWorkOrderService.FieldCaption(Service), CRMProduct.ProductNumber, Item."No."); + + if Item.Blocked then + Error(CoupledToBlockedItemErr, FSWorkOrderService.FieldCaption(Service), CRMProduct.ProductNumber, Item."No."); + + if Item.Type = Item.Type::Service then + if Item."Base Unit of Measure" <> FSConnectionSetup."Hour Unit of Measure" then + Error(CoupledToItemWithWrongUOMErr, FSWorkOrderService.FieldCaption(Service), CRMProduct.ProductNumber, Item."No.", FSConnectionSetup."Hour Unit of Measure"); + + JobJournalLine.Validate("Entry Type", JobJournalLine."Entry Type"::Usage); + + // if the work order service has a booking with a resource that is coupled to Business Central resource + // in this case, make an extra Budget line for the resource + // the extra line will be coupled in OnAfterInsertRecord subscriber + if FSBookableResourceBooking.Get(FSWorkOrderService.Booking) then begin + Clear(CRMIntegrationRecord); + if CRMIntegrationRecord.FindByCRMID(FSBookableResourceBooking.Resource) then + BookableResourceCoupled := true; + + if not Resource.GetBySystemId(CRMIntegrationRecord."Integration ID") then + BookableResourceCoupledToDeleted := true; + + if not FSBookableResource.Get(FSBookableResourceBooking.Resource) then + BookableResourceCoupledToDeleted := true; + + if Item.Type = Item.Type::Service then + if BookableResourceCoupled then + if not BookableResourceCoupledToDeleted then begin + // insert and couple an additional budget Project Journal Line, for posting cost of the resource who is performing the service + BudgetJobJournalLine.TransferFields(JobJournalLine, true); + BudgetJobJournalLine."Line No." := JobJournalLine."Line No." - BudgetJobJournalLineNoOffset(); + BudgetJobJournalLine."Line Type" := JobJournalLine."Line Type"::Budget; + BudgetJobJournalLine.Validate(Type, JobJournalLine.Type::Resource); + BudgetJobJournalLine.Validate("No.", Resource."No."); + BudgetJobJournalLine.Validate(Description, FSBookableResourceBooking.Name); + BudgetJobJournalLine.Validate("Unit of Measure Code", FSConnectionSetup."Hour Unit of Measure"); + BudgetJobJournalLine.Validate("Unit Cost", Resource."Unit Cost"); + BudgetJobJournalLine.Validate(Quantity, FSQuantity - QuantityCurrentlyConsumed); + BudgetJobJournalLine.Validate("Unit Price", 0); + BudgetJobJournalLine.Validate("Qty. to Transfer to Invoice", 0); + BudgetJobJournalLine.Insert(true); + end; + end; + + if Item.Type = Item.Type::"Non-Inventory" then + JobJournalLine."Line Type" := JobJournalLine."Line Type"::" " + else + JobJournalLine.Validate("Line Type", JobJournalLine."Line Type"::Billable); + JobJournalLine.Validate(Type, JobJournalLine.Type::Item); + // set Item, but must keep its Business Central Unit Cost + JobJournalLine.Validate("No.", Item."No."); + JobJournalLine.Validate(Description, CopyStr(FSWorkOrderService.Name, 1, MaxStrLen(JobJournalLine.Description))); + JobJournalLine.Validate("Unit of Measure Code", Item."Base Unit of Measure"); + JobJournalLine.Validate("Unit Cost", Item."Unit Cost"); + JobJournalLine.Validate(Quantity, FSQuantity - QuantityCurrentlyConsumed); + JobJournalLine.Validate("Unit Price", Item."Unit Price"); + JobJournalLine.Validate("Qty. to Transfer to Invoice", FSQuantityToBill - QuantityCurrentlyInvoiced); + end; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Integration Mgt.", 'OnHasCompanyIdField', '', false, false)] + local procedure HandleOnHasCompanyIdField(TableId: Integer; var HasField: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + case TableId of + Database::"FS Work Order", + Database::"FS Bookable Resource", + Database::"FS Customer Asset", + Database::"FS Work Order Product", + Database::"FS Work Order Service", + Database::"FS Resource Pay Type", + Database::"FS Project Task", + Database::"FS Warehouse": + HasField := true; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Int. Rec. Uncouple Invoke", 'OnBeforeUncoupleRecord', '', false, false)] + local procedure HandleOnBeforeUncoupleRecord(IntegrationTableMapping: Record "Integration Table Mapping"; var LocalRecordRef: RecordRef; var IntegrationRecordRef: RecordRef) + var + FSConnectionSetup: Record "FS Connection Setup"; + HasField: Boolean; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + CDSIntegrationMgt.OnHasCompanyIdField(IntegrationRecordRef.Number(), HasField); + if not HasField then + exit; + + if IntegrationRecordRef.IsEmpty() then + exit; + + CDSIntegrationMgt.ResetCompanyId(IntegrationRecordRef); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Table Synch.", 'OnQueryPostFilterIgnoreRecord', '', false, false)] + local procedure OnQueryPostFilterIgnoreRecord(SourceRecordRef: RecordRef; var IgnoreRecord: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + FSBookableResource: Record "FS Bookable Resource"; + JobTask: Record "Job Task"; + Job: Record Job; + begin + if IgnoreRecord then + exit; + + if FSConnectionSetup.IsEnabled() then + exit; + + case SourceRecordRef.Number() of + Database::"FS Bookable Resource": + begin + SourceRecordRef.SetTable(FSBookableResource); + IgnoreRecord := (FSBookableResource.ResourceType in [FSBookableResource.ResourceType::Contact, FSBookableResource.ResourceType::Crew, FSBookableResource.ResourceType::Facility, FSBookableResource.ResourceType::Pool]); + end; + Database::"Job Task": + begin + SourceRecordRef.SetTable(JobTask); + if not Job.Get(JobTask."Job No.") then begin + IgnoreRecord := true; + exit; + end; + + if Job.Blocked <> Job.Blocked::" " then begin + IgnoreRecord := true; + exit; + end; + + if Job.Status <> Job.Status::Open then begin + IgnoreRecord := true; + exit; + end; + + IgnoreRecord := (not Job."Apply Usage Link"); + end; + end; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Table Synch.", 'OnAfterInitSynchJob', '', true, true)] + local procedure LogTelemetryOnAfterInitSynchJob(ConnectionType: TableConnectionType; IntegrationTableID: Integer) + var + FSConnectionSetup: Record "FS Connection Setup"; + IntegrationTableMapping: Record "Integration Table Mapping"; + FeatureTelemetry: Codeunit "Feature Telemetry"; + IntegrationRecordRef: RecordRef; + TelemetryCategories: Dictionary of [Text, Text]; + IntegrationTableName: Text; + begin + if ConnectionType <> TableConnectionType::CRM then + exit; + + if FSConnectionSetup.IsEnabled() then + exit; + + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + IntegrationTableMapping.SetRange("Multi Company Synch. Enabled", true); + IntegrationTableMapping.SetRange("Table ID", IntegrationTableID); + if not IntegrationTableMapping.IsEmpty() then begin + FeatureTelemetry.LogUptake('0000LCO', 'Dataverse Multi-Company Synch', Enum::"Feature Uptake Status"::Used); + FeatureTelemetry.LogUsage('0000LCQ', 'Dataverse Multi-Company Synch', 'Entity sync'); + Session.LogMessage('0000LCS', MultiCompanySyncEnabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + end; + IntegrationTableMapping.SetRange("Table ID"); + IntegrationTableMapping.SetRange("Integration Table ID", IntegrationTableID); + if not IntegrationTableMapping.IsEmpty() then begin + FeatureTelemetry.LogUptake('0000LCP', 'Dataverse Multi-Company Synch', Enum::"Feature Uptake Status"::Used); + FeatureTelemetry.LogUsage('0000LCR', 'Dataverse Multi-Company Synch', 'Entity sync'); + Session.LogMessage('0000LCT', MultiCompanySyncEnabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + end; + + TelemetryCategories.Add('Category', CategoryTok); + TelemetryCategories.Add('IntegrationTableID', Format(IntegrationTableID)); + if TryCalculateTableName(IntegrationRecordRef, IntegrationTableID, IntegrationTableName) then + TelemetryCategories.Add('IntegrationTableName', IntegrationTableName); + + if IntegrationTableID in [ + Database::"FS Project Task", + Database::"FS Work Order Product", + Database::"FS Work Order Service", + Database::"FS Customer Asset", + Database::"FS Bookable Resource", + Database::"FS Resource Pay Type", + Database::"FS Warehouse"] then begin + Session.LogMessage('0000M9F', FSEntitySynchTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryCategories); + FeatureTelemetry.LogUsage('0000M9E', 'Field Service Integration', 'Entity synch'); + FeatureTelemetry.LogUptake('0000M9D', 'Field Service Integration', Enum::"Feature Uptake Status"::Used); + exit; + end; + end; + + [TryFunction] + local procedure TryCalculateTableName(var IntegrationRecordRef: RecordRef; TableId: Integer; var TableName: Text) + begin + IntegrationRecordRef.Open(TableId); + TableName := IntegrationRecordRef.Name(); + end; + + local procedure SetCompanyId(DestinationRecordRef: RecordRef) + begin + if CDSIntegrationImpl.CheckCompanyIdNoTelemetry(DestinationRecordRef) then + exit; + + CDSIntegrationMgt.SetCompanyId(DestinationRecordRef); + end; + + local procedure GetSourceDestCode(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef): Text + begin + if (SourceRecordRef.Number() <> 0) and (DestinationRecordRef.Number() <> 0) then + exit(SourceRecordRef.Name() + '-' + DestinationRecordRef.Name()); + exit(''); + 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 + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al new file mode 100644 index 0000000000..10eeca4245 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSIntegrationMgt.Codeunit.al @@ -0,0 +1,217 @@ +// ------------------------------------------------------------------------------------------------ +// 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.D365Sales; +using System; +using Microsoft.Utilities; + +codeunit 6615 "FS Integration Mgt." +{ + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + OAuthConnectionStringFormatTok: Label 'Url=%1; AccessToken=%2; ProxyVersion=%3; %4', Locked = true; + ConnectionStringFormatTok: Label 'Url=%1; UserName=%2; Password=%3; ProxyVersion=%4; %5', Locked = true; + UserDoesNotExistCRMErr: Label 'There is no user with email address %1 in %2. Enter a valid email address.', Comment = '%1 = User email address, %2 = Dataverse service name'; + MicrosoftDynamicsFSIntegrationTxt: Label 'bcbi_FieldServiceIntegration', Locked = true; + TeamNotFoundErr: Label 'Cannot find the default owning team for the coupled business unit %1 selected on page %2. To continue, you can select another business unit or revert to the default business unit that was created during setup.', Comment = '%1 = business unit name, %2 = setup page caption'; + TeamNotFoundTxt: Label 'The team was not found.', Locked = true; + CategoryTok: Label 'AL Field Service Integration', Locked = true; + RoleNotFoundForBusinessUnitTxt: Label 'Integration role is not found for business unit.', Locked = true; + IntegrationRoleNotFoundErr: Label 'There is no integration role %1 for business unit %2.', Comment = '%1 = role name, %2 = business unit name'; + CannotAssignRoleToTeamTxt: Label 'Cannot assign role to team.', Locked = true; + CannotAssignRoleToTeamErr: Label 'Cannot assign role %3 to team %1 for business unit %2.', Comment = '%1 = team name, %2 = business unit name, %3 = security role name'; + FieldServiceAdministratorProfileIdLbl: label '8d988915-e392-e111-9d8c-000c2959f9b8', Locked = true; + CannotAssignFieldSecurityProfileToUserTelemetryLbl: Label 'Cannot assign field security profile to integration user.', Locked = true; + CannotAssignFieldSecurityProfileToUserQst: Label 'To enable the setup, you must sign in to %1 as administrator and assign the column security profile "Field Service - Administrator" to the Business Central integration user. Do you want to open the Business Central integration user card in %1?', Comment = '%1 - Dataverse environment URL'; + NoPermissionsTxt: Label 'No permissions.', Locked = true; + + [TryFunction] + internal procedure ImportFSSolution(ServerAddress: Text; IntegrationUserEmail: Text; AdminUserEmail: Text; AdminUserPassword: SecretText; AccessToken: SecretText; AdminADDomain: Text; ProxyVersion: Integer; ForceRedeploy: Boolean; ImportSolutionFailed: Boolean) + var + CDSConnectionSetup: Record "CDS Connection Setup"; + CRMRole: Record "CRM Role"; + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + CRMProductName: Codeunit "CRM Product Name"; + PageCDSConnectionSetup: Page "CDS Connection Setup"; + CRMHelper: DotNet CrmHelper; + UserGUID: Guid; + IntegrationRoleGUID: Guid; + FieldSecurityProfileGUID: Guid; + DefaultOwningTeamGUID: Guid; + TempConnectionStringWithPlaceholders: Text; + TempConnectionString: SecretText; + SolutionInstalled: Boolean; + SolutionOutdated: Boolean; + ImportSolution: Boolean; + begin + CRMIntegrationManagement.CheckConnectRequiredFields(ServerAddress, IntegrationUserEmail); + CDSConnectionSetup.Get(); + if not AccessToken.IsEmpty() then begin + TempConnectionStringWithPlaceholders := + StrSubstNo(OAuthConnectionStringFormatTok, ServerAddress, '%1', ProxyVersion, CDSIntegrationImpl.GetAuthenticationTypeToken(CDSConnectionSetup)); + TempConnectionString := SecretStrSubstNo(TempConnectionStringWithPlaceholders, AccessToken); + end + else + if AdminADDomain <> '' then begin + TempConnectionStringWithPlaceholders := StrSubstNo( + ConnectionStringFormatTok, ServerAddress, AdminUserEmail, '%1', ProxyVersion, CDSIntegrationImpl.GetAuthenticationTypeToken(CDSConnectionSetup, AdminADDomain)); + TempConnectionString := SecretStrSubstNo(TempConnectionStringWithPlaceholders, AdminUserPassword); + end + else begin + TempConnectionStringWithPlaceholders := StrSubstNo( + ConnectionStringFormatTok, ServerAddress, AdminUserEmail, '%1', ProxyVersion, CDSIntegrationImpl.GetAuthenticationTypeToken(CDSConnectionSetup)); + TempConnectionString := SecretStrSubstNo(TempConnectionStringWithPlaceholders, AdminUserPassword); + end; + + if CDSConnectionSetup."Authentication Type" = CDSConnectionSetup."Authentication Type"::OAuth then + TempConnectionString := CDSIntegrationImpl.ReplaceUserNamePasswordInConnectionstring(CDSConnectionSetup, AdminUserEmail, AdminUserPassword); + + if not InitializeFSConnection(CRMHelper, TempConnectionString) then + CRMIntegrationManagement.ProcessConnectionFailures(); + + UserGUID := CRMHelper.GetUserId(IntegrationUserEmail); + if IsNullGuid(UserGUID) then + Error(UserDoesNotExistCRMErr, IntegrationUserEmail, CRMProductName.CDSServiceName()); + + SolutionInstalled := CRMHelper.CheckSolutionPresence(MicrosoftDynamicsFSIntegrationTxt); + if SolutionInstalled then + SolutionOutdated := CRMIntegrationManagement.IsSolutionOutdated(TempConnectionStringWithPlaceholders, MicrosoftDynamicsFSIntegrationTxt); + + if ForceRedeploy then + ImportSolution := (not SolutionInstalled) or SolutionOutdated + else + ImportSolution := not SolutionInstalled; + + if ImportSolution then + if not ImportDefaultFSSolution(CRMHelper) then begin + ImportSolutionFailed := true; + CRMIntegrationManagement.ProcessConnectionFailures(); + end; + + IntegrationRoleGUID := CRMHelper.GetRoleId(GetFieldServiceIntegrationRoleID()); + if not CRMHelper.CheckRoleAssignedToUser(UserGUID, IntegrationRoleGUID) then + CRMHelper.AssociateUserWithRole(UserGUID, IntegrationRoleGUID); + + if CDSIntegrationImpl.IsIntegrationEnabled() then begin + CDSIntegrationImpl.RegisterConnection(); + CDSIntegrationImpl.ActivateConnection(); + CDSConnectionSetup.Get(); + DefaultOwningTeamGUID := CDSIntegrationImpl.GetOwningTeamId(CDSConnectionSetup); + if IsNullGuid(DefaultOwningTeamGUID) then begin + Session.LogMessage('0000MWY', TeamNotFoundTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + Error(TeamNotFoundErr, CDSIntegrationImpl.GetDefaultBusinessUnitName(), PageCDSConnectionSetup.Caption); + end; + CRMRole.SetRange(ParentRoleId, IntegrationRoleGUID); + CRMRole.SetRange(BusinessUnitId, CDSIntegrationImpl.GetCoupledBusinessUnitId()); + if not CRMRole.FindFirst() then begin + Session.LogMessage('0000MWZ', RoleNotFoundForBusinessUnitTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + Error(IntegrationRoleNotFoundErr, IntegrationRoleGUID, CDSIntegrationImpl.GetDefaultBusinessUnitName()); + end; + if not CDSIntegrationImpl.AssignTeamRole(CrmHelper, DefaultOwningTeamGUID, CRMRole.RoleId) then begin + Session.LogMessage('0000MX0', CannotAssignRoleToTeamTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + Error(CannotAssignRoleToTeamErr, DefaultOwningTeamGUID, CDSIntegrationImpl.GetDefaultBusinessUnitName(), CRMRole.Name); + end; + if not CDSIntegrationImpl.AssignTeamRole(CrmHelper, DefaultOwningTeamGUID, CRMRole.RoleId) then begin + Session.LogMessage('0000MX1', CannotAssignRoleToTeamTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + Error(CannotAssignRoleToTeamErr, DefaultOwningTeamGUID, CDSIntegrationImpl.GetDefaultBusinessUnitName(), CRMRole.Name); + end; + end; + + FieldSecurityProfileGUID := TextToGuid(FieldServiceAdministratorProfileIdLbl); + if not CRMHelper.CheckFieldSecurityProfileAssignedToUser(UserGUID, FieldSecurityProfileGUID) then + CRMHelper.AssociateUserWithFieldSecurityProfile(UserGUID, FieldSecurityProfileGUID); + + if not CRMHelper.CheckFieldSecurityProfileAssignedToUser(UserGUID, FieldSecurityProfileGUID) then begin + Session.LogMessage('0000MX2', CannotAssignFieldSecurityProfileToUserTelemetryLbl, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + if Confirm(StrSubstNo(CannotAssignFieldSecurityProfileToUserQst, CDSConnectionSetup."Server Address")) then + CDSIntegrationImpl.ShowIntegrationUser(CDSConnectionSetup); + Error(''); + end + end; + + [TryFunction] + local procedure TryTouchFSSolutionEntities() + var + FSProjectTask: Record "FS Project Task"; + Cnt: Integer; + begin + Cnt := FSProjectTask.Count(); + if Cnt > 0 then + exit; + end; + + internal procedure IsFSSolutionInstalled(): Boolean + begin + if TryTouchFSSolutionEntities() then + exit(true); + + ClearLastError(); + exit(false); + end; + + [TryFunction] + [NonDebuggable] + local procedure InitializeFSConnection(var CRMHelper: DotNet CrmHelper; ConnectionString: SecretText) + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + if ConnectionString.IsEmpty() then begin + FSConnectionSetup.Get(); + CRMHelper := CRMHelper.CrmHelper(FSConnectionSetup.GetConnectionStringWithCredentials().Unwrap()); + end else + CRMHelper := CRMHelper.CrmHelper(ConnectionString.Unwrap()); + if not CRMIntegrationManagement.TestCRMConnection(CRMHelper) then + CRMIntegrationManagement.ProcessConnectionFailures(); + end; + + [TryFunction] + local procedure ImportDefaultFSSolution(var CRMHelper: DotNet CrmHelper) + begin + CRMHelper.ImportDefaultFieldServiceSolution() + end; + + local procedure GetFieldServiceIntegrationRoleID(): Text + begin + exit('c11b4fa8-956b-439d-8b3c-021e8736a78b'); + end; + + local procedure TextToGuid(TextVar: Text): Guid + var + GuidVar: Guid; + begin + if not Evaluate(GuidVar, TextVar) then; + exit(GuidVar); + end; + + [EventSubscriber(ObjectType::Table, Database::"Service Connection", 'OnRegisterServiceConnection', '', false, false)] + local procedure RegisterFSConnectionOnRegisterServiceConnection(var ServiceConnection: Record "Service Connection") + var + FSConnectionSetup: Record "FS Connection Setup"; + RecRef: RecordRef; + begin + if not FSConnectionSetup.Get() then begin + if not FSConnectionSetup.WritePermission() then begin + Session.LogMessage('0000MYK', NoPermissionsTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit; + end; + FSConnectionSetup.Init(); + FSConnectionSetup.Insert(); + end; + + RecRef.GetTable(FSConnectionSetup); + ServiceConnection.Status := ServiceConnection.Status::Enabled; + if not FSConnectionSetup."Is Enabled" then + ServiceConnection.Status := ServiceConnection.Status::Disabled + else + if FSConnectionSetup.TestConnection() then + ServiceConnection.Status := ServiceConnection.Status::Connected + else + ServiceConnection.Status := ServiceConnection.Status::Error; + ServiceConnection.InsertServiceConnectionExtended( + ServiceConnection, RecRef.RecordId, FSConnectionSetup.TableCaption(), FSConnectionSetup."Server Address", Page::"FS Connection Setup", Page::"FS Connection Setup Wizard"); + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al new file mode 100644 index 0000000000..85d78bb769 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSLookupFSTables.Codeunit.al @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------------------------ +// 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; + +codeunit 6612 "FS Lookup FS Tables" +{ + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Lookup CRM Tables", 'OnLookupCRMTables', '', false, false)] + local procedure HandleOnLookupCRMTables(CRMTableID: Integer; NAVTableId: Integer; SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text; var Handled: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + if Handled then + exit; + + if not FSConnectionSetup.IsEnabled() then + exit; + + case CRMTableID of + Database::"FS Bookable Resource": + if LookupFSBookableResource(SavedCRMId, CRMId, IntTableFilter) then + Handled := true; + Database::"FS Customer Asset": + if LookupFSCustomerAsset(SavedCRMId, CRMId, IntTableFilter) then + Handled := true; + end; + end; + + local procedure LookupFSCustomerAsset(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean + var + FSCustomerAsset: Record "FS Customer Asset"; + OriginalFSCustomerAsset: Record "FS Customer Asset"; + FSCustomerAssetList: Page "FS Customer Asset List"; + begin + if not IsNullGuid(CRMId) then begin + if FSCustomerAsset.Get(CRMId) then + FSCustomerAssetList.SetRecord(FSCustomerAsset); + if not IsNullGuid(SavedCRMId) then + if OriginalFSCustomerAsset.Get(SavedCRMId) then + FSCustomerAssetList.SetCurrentlyCoupledFSCustomerAsset(OriginalFSCustomerAsset); + end; + FSCustomerAsset.SetView(IntTableFilter); + FSCustomerAssetList.SetTableView(FSCustomerAsset); + FSCustomerAssetList.LookupMode(true); + Commit(); + if FSCustomerAssetList.RunModal() = Action::LookupOK then begin + FSCustomerAssetList.GetRecord(FSCustomerAsset); + CRMId := FSCustomerAsset.CustomerAssetId; + exit(true); + end; + exit(false); + end; + + local procedure LookupFSBookableResource(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean + var + FSBookableResource: Record "FS Bookable Resource"; + OriginalFSBookableResource: Record "FS Bookable Resource"; + FSBookableResourceList: Page "FS Bookable Resource List"; + begin + if not IsNullGuid(CRMId) then begin + if FSBookableResource.Get(CRMId) then + FSBookableResourceList.SetRecord(FSBookableResource); + if not IsNullGuid(SavedCRMId) then + if OriginalFSBookableResource.Get(SavedCRMId) then + FSBookableResourceList.SetCurrentlyCoupledFSBookableResource(OriginalFSBookableResource); + end; + FSBookableResource.SetView(IntTableFilter); + FSBookableResourceList.SetTableView(FSBookableResource); + FSBookableResourceList.LookupMode(true); + Commit(); + if FSBookableResourceList.RunModal() = Action::LookupOK then begin + FSBookableResourceList.GetRecord(FSBookableResource); + CRMId := FSBookableResource.BookableResourceId; + 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 new file mode 100644 index 0000000000..e8a3142e05 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Codeunits/FSSetupDefaults.Codeunit.al @@ -0,0 +1,774 @@ +// ------------------------------------------------------------------------------------------------ +// 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.D365Sales; +using Microsoft.Integration.SyncEngine; +using Microsoft.Utilities; +using System.Threading; +using Microsoft.Projects.Project.Job; +using Microsoft.Service.Item; +using Microsoft.Projects.Resources.Resource; +using Microsoft.Projects.Project.Journal; +using System.Environment.Configuration; +using Microsoft.Inventory.Location; + +codeunit 6611 "FS Setup Defaults" +{ + var + CRMProductName: Codeunit "CRM Product Name"; + JobQueueCategoryLbl: Label 'BCI INTEG', Locked = true; + OptionJobQueueCategoryLbl: Label 'BCI OPTION', Locked = true; + CategoryTok: Label 'AL Field Service Integration', Locked = true; + JobQueueEntryNameTok: Label ' %1 - %2 synchronization job.', Comment = '%1 = The Integration Table Name to synchronized (ex. CUSTOMER), %2 = CRM product name'; + IntegrationTablePrefixTok: Label 'Dynamics CRM', Comment = 'Product name', Locked = true; + + internal procedure ResetConfiguration(var FSConnectionSetup: Record "FS Connection Setup") + var + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeResetConfiguration(FSConnectionSetup, IsHandled); + if IsHandled then + exit; + + CDSIntegrationMgt.RegisterConnection(); + CDSIntegrationMgt.ActivateConnection(); + + ResetProjectTaskMapping(FSConnectionSetup, 'PROJECTTASK', true); + ResetProjectJournalLineWOProductMapping(FSConnectionSetup, 'PJLINE-WORDERPRODUCT', true); + ResetProjectJournalLineWOServiceMapping(FSConnectionSetup, 'PJLINE-WORDERSERVICE', true); + ResetServiceItemCustomerAssetMapping(FSConnectionSetup, 'SVCITEM-CUSTASSET', true); + ResetResourceBookableResourceMapping(FSConnectionSetup, 'RESOURCE-BOOKABLERSC', true); + ResetLocationMapping(FSConnectionSetup, 'LOCATION', true); + SetCustomIntegrationsTableMappings(FSConnectionSetup); + end; + + internal procedure ResetProjectTaskMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + JobTask: Record "Job Task"; + FSProjectTask: Record "FS Project Task"; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeResetProjectTaskMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + JobTask.Reset(); + JobTask.SetRange("Job Task Type", JobTask."Job Task Type"::Posting); + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Job Task", Database::"FS Project Task", + FSProjectTask.FieldNo(ProjectTaskId), FSProjectTask.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::"Job Task", JobTask.TableCaption(), JobTask.GetView())); + if not ShouldResetServiceItemMapping() then + IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|RESOURCE-BOOKABLERSC' + else + IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|RESOURCE-BOOKABLERSC|SVCITEM-CUSTASSET'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobTask.FieldNo("Job No."), + FSProjectTask.FieldNo(ProjectNumber), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobTask.FieldNo("Job Task No."), + FSProjectTask.FieldNo(ProjectTaskNumber), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobTask.FieldNo(Description), + FSProjectTask.FieldNo(Description), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 5); + end; + + internal procedure ResetProjectJournalLineWOProductMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + FSWorkOrderProduct: Record "FS Work Order Product"; + JobJournalLine: Record "Job Journal Line"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + IsHandled: Boolean; + EmptyGuid: Guid; + begin + IsHandled := false; + OnBeforeResetProjectJournalLineWOProductMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + FSWorkOrderProduct.Reset(); + FSWorkOrderProduct.SetRange(StateCode, FSWorkOrderProduct.StateCode::Active); + FSWorkOrderProduct.SetFilter(ProjectTask, '<>' + Format(EmptyGuid)); + case FSConnectionSetup."Line Synch. Rule" of + "FS Work Order Line Synch. Rule"::LineUsed: + FSWorkOrderProduct.SetRange(LineStatus, FSWorkOrderProduct.LineStatus::Used); + "FS Work Order Line Synch. Rule"::WorkOrderCompleted: + FSWorkOrderProduct.SetFilter(WorkOrderStatus, Format(FSWorkOrderProduct.WorkOrderStatus::Completed) + '|' + Format(FSWorkOrderProduct.WorkOrderStatus::Posted)); + end; + FSWorkOrderProduct.SetFilter(ProjectTask, '<>' + Format(EmptyGuid)); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWorkOrderProduct.SetRange(CompanyId, CDSCompany.CompanyId); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Job Journal Line", Database::"FS Work Order Product", + FSWorkOrderProduct.FieldNo(WorkOrderProductId), FSWorkOrderProduct.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Work Order Product", FSWorkOrderProduct.TableCaption(), FSWorkOrderProduct.GetView())); + IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|ITEM-PRODUCT'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo(Description), + FSWorkOrderProduct.FieldNo(Name), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("External Document No."), + FSWorkOrderProduct.FieldNo(WorkOrderName), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo(Quantity), + FSWorkOrderProduct.FieldNo(Quantity), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("Qty. to Transfer to Invoice"), + FSWorkOrderProduct.FieldNo(QtyToBill), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("Currency Code"), + FSWorkOrderProduct.FieldNo(TransactionCurrencyId), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("Location Code"), + FSWorkOrderProduct.FieldNo(WarehouseId), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + OnAfterResetProjectJournalLineWOProductMapping(IntegrationTableMappingName); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 5); + end; + + internal procedure ResetProjectJournalLineWOServiceMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + FSWorkOrderService: Record "FS Work Order Service"; + JobJournalLine: Record "Job Journal Line"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + IsHandled: Boolean; + EmptyGuid: Guid; + begin + IsHandled := false; + OnBeforeResetProjectJournalLineWOServiceMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + FSWorkOrderService.Reset(); + FSWorkOrderService.SetRange(StateCode, FSWorkOrderService.StateCode::Active); + FSWorkOrderService.SetFilter(ProjectTask, '<>' + Format(EmptyGuid)); + case FSConnectionSetup."Line Synch. Rule" of + "FS Work Order Line Synch. Rule"::LineUsed: + FSWorkOrderService.SetRange(LineStatus, FSWorkOrderService.LineStatus::Used); + "FS Work Order Line Synch. Rule"::WorkOrderCompleted: + FSWorkOrderService.SetFilter(WorkOrderStatus, Format(FSWorkOrderService.WorkOrderStatus::Completed) + '|' + Format(FSWorkOrderService.WorkOrderStatus::Posted)); + end; + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSWorkOrderService.SetRange(CompanyId, CDSCompany.CompanyId); + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Job Journal Line", Database::"FS Work Order Service", + FSWorkOrderService.FieldNo(WorkOrderServiceId), FSWorkOrderService.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Work Order Service", FSWorkOrderService.TableCaption(), FSWorkOrderService.GetView())); + + IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|ITEM-PRODUCT|RESOURCE-BOOKABLERSC'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo(Description), + FSWorkOrderService.FieldNo(Name), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("External Document No."), + FSWorkOrderService.FieldNo(WorkOrderName), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo(Quantity), + FSWorkOrderService.FieldNo(Duration), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("Qty. to Transfer to Invoice"), + FSWorkOrderService.FieldNo(DurationToBill), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + JobJournalLine.FieldNo("Currency Code"), + FSWorkOrderService.FieldNo(TransactionCurrencyId), + IntegrationFieldMapping.Direction::FromIntegrationTable, + '', true, false); + + OnAfterResetProjectJournalLineWOServiceMapping(IntegrationTableMappingName); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 5); + end; + + internal procedure ResetResourceBookableResourceMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + FSBookableResource: Record "FS Bookable Resource"; + Resource: Record Resource; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + IsHandled: Boolean; + EmptyGuid: Guid; + begin + IsHandled := false; + OnBeforeResetResourceBookableResourceMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + Resource.SetRange(Blocked, false); + Resource.SetRange("Use Time Sheet", false); + Resource.SetRange("Base Unit of Measure", FSConnectionSetup."Hour Unit of Measure"); + + FSBookableResource.Reset(); + FSBookableResource.SetRange(StateCode, FSBookableResource.StateCode::Active); + FSBookableResource.SetFilter(ResourceType, Format(FSBookableResource.ResourceType::Generic) + '|' + Format(FSBookableResource.ResourceType::Account) + '|' + Format(FSBookableResource.ResourceType::Equipment)); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSBookableResource.SetFilter(CompanyId, CDSCompany.CompanyId + '|' + Format(EmptyGuid)); + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::Resource, Database::"FS Bookable Resource", + FSBookableResource.FieldNo(BookableResourceId), FSBookableResource.FieldNo(ModifiedOn), + '', '', true); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::Resource, Resource.TableCaption(), Resource.GetView())); + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Bookable Resource", FSBookableResource.TableCaption(), FSBookableResource.GetView())); + IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|ITEM-PRODUCT'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + Resource.FieldNo(Name), + FSBookableResource.FieldNo(Name), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + Resource.FieldNo("Vendor No."), + FSBookableResource.FieldNo(AccountId), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + Resource.FieldNo("Unit Cost"), + FSBookableResource.FieldNo(HourlyRate), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + OnAfterResetResourceBookableResourceMapping(IntegrationTableMappingName); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 5); + end; + + internal procedure ResetServiceItemCustomerAssetMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + FSCustomerAsset: Record "FS Customer Asset"; + ServiceItem: Record "Service Item"; + CDSCompany: Record "CDS Company"; + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + EmptyGuid: Guid; + IsHandled: Boolean; + begin + if not ShouldResetServiceItemMapping() then begin + Session.LogMessage('0000MMQ', 'The current company is not eligible to synchronize service items.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit; + end; + + IsHandled := false; + OnBeforeResetServiceItemCustomerAssetMapping(IntegrationTableMappingName, ShouldRecreateJobQueueEntry, IsHandled); + if IsHandled then + exit; + + FSCustomerAsset.Reset(); + FSCustomerAsset.SetRange(StateCode, FSCustomerAsset.StateCode::Active); + if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then + FSCustomerAsset.SetFilter(CompanyId, CDSCompany.CompanyId + '|' + EmptyGuid); + + ServiceItem.Reset(); + ServiceItem.SetRange(Blocked, ServiceItem.Blocked::" "); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::"Service Item", Database::"FS Customer Asset", + FSCustomerAsset.FieldNo(CustomerAssetId), FSCustomerAsset.FieldNo(ModifiedOn), + '', '', true); + + IntegrationTableMapping.SetIntegrationTableFilter( + GetTableFilterFromView(Database::"FS Customer Asset", FSCustomerAsset.TableCaption(), FSCustomerAsset.GetView())); + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::"Service Item", ServiceItem.TableCaption(), ServiceItem.GetView())); + IntegrationTableMapping."Dependency Filter" := 'CUSTOMER|ITEM-PRODUCT'; + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceItem.FieldNo(Description), + FSCustomerAsset.FieldNo(Name), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceItem.FieldNo("Customer No."), + FSCustomerAsset.FieldNo(Account), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + ServiceItem.FieldNo("Item No."), + FSCustomerAsset.FieldNo(Product), + IntegrationFieldMapping.Direction::Bidirectional, + '', true, false); + + OnAfterResetServiceItemCustomerAssetMapping(IntegrationTableMappingName); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 5); + end; + + internal procedure ResetLocationMapping(var FSConnectionSetup: Record "FS Connection Setup"; IntegrationTableMappingName: Code[20]; ShouldRecreateJobQueueEntry: Boolean) + var + IntegrationTableMapping: Record "Integration Table Mapping"; + IntegrationFieldMapping: Record "Integration Field Mapping"; + Location: Record Location; + FSWarehouse: Record "FS Warehouse"; + begin + Location.SetRange("Use As In-Transit", false); + Location.SetFilter("Job Consump. Whse. Handling", '''' + Format(Location."Job Consump. Whse. Handling"::"No Warehouse Handling") + '''|''' + + Format(Location."Job Consump. Whse. Handling"::"Warehouse Pick (optional)") + '''|''' + + Format(Location."Job Consump. Whse. Handling"::"Inventory Pick") + ''''); + Location.SetFilter("Asm. Consump. Whse. Handling", '''' + Format(Location."Asm. Consump. Whse. Handling"::"No Warehouse Handling") + '''|''' + + Format(Location."Asm. Consump. Whse. Handling"::"Warehouse Pick (optional)") + '''|''' + + Format(Location."Asm. Consump. Whse. Handling"::"Inventory Movement") + ''''); + + InsertIntegrationTableMapping( + IntegrationTableMapping, IntegrationTableMappingName, + Database::Location, Database::"FS Warehouse", + FSWarehouse.FieldNo(WarehouseId), FSWarehouse.FieldNo(ModifiedOn), + '', '', false); + + IntegrationTableMapping.SetTableFilter( + GetTableFilterFromView(Database::Location, Location.TableCaption(), Location.GetView())); + IntegrationTableMapping.Modify(); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + Location.FieldNo(Code), + FSWarehouse.FieldNo(Name), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + + InsertIntegrationFieldMapping( + IntegrationTableMappingName, + Location.FieldNo(Name), + FSWarehouse.FieldNo(Description), + IntegrationFieldMapping.Direction::ToIntegrationTable, + '', true, false); + + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, 1, ShouldRecreateJobQueueEntry, 5); + end; + + local procedure ShouldResetServiceItemMapping(): Boolean + var + ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade"; + begin + exit(ApplicationAreaMgmtFacade.IsPremiumExperienceEnabled()); + end; + + local procedure InsertIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; MappingName: Code[20]; TableNo: Integer; IntegrationTableNo: Integer; IntegrationTableUIDFieldNo: Integer; IntegrationTableModifiedFieldNo: Integer; TableConfigTemplateCode: Code[10]; IntegrationTableConfigTemplateCode: Code[10]; SynchOnlyCoupledRecords: Boolean) + var + CDSIntegrationMgt: Codeunit "CDS Integration Mgt."; + UncoupleCodeunitId: Integer; + Direction: Integer; + begin + Direction := GetDefaultDirection(TableNo); + if Direction in [IntegrationTableMapping.Direction::ToIntegrationTable, IntegrationTableMapping.Direction::Bidirectional] then + if CDSIntegrationMgt.HasCompanyIdField(IntegrationTableNo) then + UncoupleCodeunitId := Codeunit::"CDS Int. Table Uncouple"; + IntegrationTableMapping.CreateRecord(MappingName, TableNo, IntegrationTableNo, IntegrationTableUIDFieldNo, + IntegrationTableModifiedFieldNo, TableConfigTemplateCode, IntegrationTableConfigTemplateCode, + SynchOnlyCoupledRecords, Direction, IntegrationTablePrefixTok, + Codeunit::"CRM Integration Table Synch.", UncoupleCodeunitId); + end; + + local procedure InsertIntegrationFieldMapping(IntegrationTableMappingName: Code[20]; TableFieldNo: Integer; IntegrationTableFieldNo: Integer; SynchDirection: Option; ConstValue: Text; ValidateField: Boolean; ValidateIntegrationTableField: Boolean) + var + IntegrationFieldMapping: Record "Integration Field Mapping"; + begin + IntegrationFieldMapping.CreateRecord(IntegrationTableMappingName, TableFieldNo, IntegrationTableFieldNo, SynchDirection, + ConstValue, ValidateField, ValidateIntegrationTableField); + end; + + internal procedure CreateJobQueueEntry(IntegrationTableMapping: Record "Integration Table Mapping"; ServiceName: Text): Boolean + begin + exit(CreateJobQueueEntry(IntegrationTableMapping, Codeunit::"Integration Synch. Job Runner", StrSubstNo(JobQueueEntryNameTok, IntegrationTableMapping.GetTempDescription(), ServiceName))); + end; + + local procedure CreateJobQueueEntry(var IntegrationTableMapping: Record "Integration Table Mapping"; JobCodeunitId: Integer; JobDescription: Text): Boolean + var + JobQueueEntry: Record "Job Queue Entry"; + StartTime: DateTime; + begin + StartTime := CurrentDateTime() + 1000; + JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit); + JobQueueEntry.SetRange("Object ID to Run", JobCodeunitId); + JobQueueEntry.SetRange("Record ID to Process", IntegrationTableMapping.RecordId()); + JobQueueEntry.SetRange("Job Queue Category Code", JobQueueCategoryLbl); + JobQueueEntry.SetRange(Status, JobQueueEntry.Status::Ready); + JobQueueEntry.SetFilter("Earliest Start Date/Time", '<=%1', StartTime); + if not JobQueueEntry.IsEmpty() then begin + JobQueueEntry.DeleteTasks(); + Commit(); + end; + + JobQueueEntry.Init(); + Clear(JobQueueEntry.ID); // "Job Queue - Enqueue" is to define new ID + JobQueueEntry."Earliest Start Date/Time" := StartTime; + JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit; + JobQueueEntry."Object ID to Run" := JobCodeunitId; + JobQueueEntry."Record ID to Process" := IntegrationTableMapping.RecordId(); + JobQueueEntry."Run in User Session" := false; + JobQueueEntry."Notify On Success" := false; + JobQueueEntry."Maximum No. of Attempts to Run" := 2; + JobQueueEntry."Job Queue Category Code" := JobQueueCategoryLbl; + JobQueueEntry.Status := JobQueueEntry.Status::Ready; + JobQueueEntry."Rerun Delay (sec.)" := 30; + JobQueueEntry.Description := CopyStr(JobDescription, 1, MaxStrLen(JobQueueEntry.Description)); + OnCreateJobQueueEntryOnBeforeJobQueueEnqueue(JobQueueEntry, IntegrationTableMapping, JobCodeunitId, JobDescription); + exit(Codeunit.Run(Codeunit::"Job Queue - Enqueue", JobQueueEntry)) + end; + + local procedure RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping: Record "Integration Table Mapping"; IntervalInMinutes: Integer; ShouldRecreateJobQueueEntry: Boolean; InactivityTimeoutPeriod: Integer) + begin + RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping, IntervalInMinutes, ShouldRecreateJobQueueEntry, InactivityTimeoutPeriod, CRMProductName.CDSServiceName(), false); + end; + + internal procedure RecreateJobQueueEntryFromIntTableMapping(IntegrationTableMapping: Record "Integration Table Mapping"; IntervalInMinutes: Integer; ShouldRecreateJobQueueEntry: Boolean; InactivityTimeoutPeriod: Integer; ServiceName: Text; IsOption: Boolean) + var + JobQueueEntry: Record "Job Queue Entry"; + begin + JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit); + JobQueueEntry.SetRange("Object ID to Run", Codeunit::"Integration Synch. Job Runner"); + JobQueueEntry.SetRange("Record ID to Process", IntegrationTableMapping.RecordId()); + JobQueueEntry.DeleteTasks(); + + JobQueueEntry.InitRecurringJob(IntervalInMinutes); + JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit; + JobQueueEntry."Object ID to Run" := Codeunit::"Integration Synch. Job Runner"; + JobQueueEntry."Record ID to Process" := IntegrationTableMapping.RecordId(); + JobQueueEntry."Run in User Session" := false; + JobQueueEntry.Description := + CopyStr(StrSubstNo(JobQueueEntryNameTok, IntegrationTableMapping.Name, ServiceName), 1, MaxStrLen(JobQueueEntry.Description)); + JobQueueEntry."Maximum No. of Attempts to Run" := 10; + JobQueueEntry.Status := JobQueueEntry.Status::Ready; + JobQueueEntry."Rerun Delay (sec.)" := 30; + JobQueueEntry."Inactivity Timeout Period" := InactivityTimeoutPeriod; + if IsOption then + JobQueueEntry."Job Queue Category Code" := OptionJobQueueCategoryLbl; + if ShouldRecreateJobQueueEntry then + Codeunit.Run(Codeunit::"Job Queue - Enqueue", JobQueueEntry) + else + JobQueueEntry.Insert(true); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnGetCDSTableNo', '', false, false)] + local procedure ReturnProxyTableNoOnGetCDSTableNo(BCTableNo: Integer; var CDSTableNo: Integer; var Handled: Boolean) + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + if Handled then + exit; + + if not FSConnectionSetup.IsEnabled() then + exit; + + case BCTableNo of + Database::Resource: + CDSTableNo := Database::"FS Bookable Resource"; + Database::"Service Item": + CDSTableNo := Database::"FS Customer Asset"; + Database::"Job Task": + CDSTableNo := Database::"FS Project Task"; + Database::Location: + CDSTableNo := Database::"FS Warehouse"; + end; + + if CDSTableNo <> 0 then + Handled := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnAddEntityTableMapping', '', false, false)] + local procedure AddProxyTablesOnAddEntityTableMapping(var TempNameValueBuffer: Record "Name/Value Buffer" temporary) + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMSetupDefaults: Codeunit "CRM Setup Defaults"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + CRMSetupDefaults.AddEntityTableMapping('bookableresource', Database::Resource, TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('bookableresource', Database::"FS Bookable Resource", TempNameValueBuffer); + + CRMSetupDefaults.AddEntityTableMapping('msdyn_customerasset', Database::"Service Item", TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('msdyn_customerasset', Database::"FS Customer Asset", TempNameValueBuffer); + + CRMSetupDefaults.AddEntityTableMapping('bcbi_projecttask', Database::"Job Task", TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('bcbi_projecttask', Database::"FS Project Task", TempNameValueBuffer); + + CRMSetupDefaults.AddEntityTableMapping('msdyn_workorderproduct', Database::"Job Journal Line", TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('msdyn_workorderproduct', Database::"FS Work Order Product", TempNameValueBuffer); + + CRMSetupDefaults.AddEntityTableMapping('msdyn_workorderservice', Database::"Job Journal Line", TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('msdyn_workorderservice', Database::"FS Work Order Service", TempNameValueBuffer); + + CRMSetupDefaults.AddEntityTableMapping('msdyn_warehouse', Database::Location, TempNameValueBuffer); + CRMSetupDefaults.AddEntityTableMapping('msdyn_warehouse', Database::"FS Warehouse", TempNameValueBuffer); + + TempNameValueBuffer.SetRange(Name, 'product'); + TempNameValueBuffer.SetRange(Value, Format(Database::Resource)); + if TempNameValueBuffer.FindFirst() then + TempNameValueBuffer.Delete(); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnBeforeGetNameFieldNo', '', false, false)] + local procedure ReturnNameFieldNoOnBeforeGetNameFieldNo(TableId: Integer; var FieldNo: Integer) + var + FSConnectionSetup: Record "FS Connection Setup"; + ServiceItem: Record "Service Item"; + FSCustomerAsset: Record "FS Customer Asset"; + FSBookableResource: Record "FS Bookable Resource"; + FSWorkOrderProduct: Record "FS Work Order Product"; + FSWorkOrderService: Record "FS Work Order Service"; + FSProjectTask: Record "FS Project Task"; + JobTask: Record "Job Task"; + JobJournalLine: Record "Job Journal Line"; + Location: Record Location; + FSWarehouse: Record "FS Warehouse"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + case TableId of + 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 Product": + FieldNo := FSWorkOrderProduct.FieldNo(Name); + Database::"FS Work Order Service": + FieldNo := FSWorkOrderService.FieldNo(Name); + Database::"FS Project Task": + FieldNo := FSProjectTask.FieldNo(ProjectNumber); + Database::"Job Task": + FieldNo := JobTask.FieldNo("Job Task No."); + Database::"Job Journal Line": + FieldNo := JobJournalLine.FieldNo(Description); + Database::"FS Warehouse": + FieldNo := FSWarehouse.FieldNo(Name); + Database::Location: + FieldNo := Location.FieldNo(Code); + end; + end; + + procedure GetDefaultDirection(NAVTableID: Integer): Integer + var + IntegrationTableMapping: Record "Integration Table Mapping"; + begin + case NAVTableID of + Database::"Service Item", + Database::"Work Type", + Database::"Resource": + exit(IntegrationTableMapping.Direction::Bidirectional); + Database::"Job Task", + Database::Location: + exit(IntegrationTableMapping.Direction::ToIntegrationTable); + Database::"Job Journal Line": + exit(IntegrationTableMapping.Direction::FromIntegrationTable); + end; + end; + + internal procedure GetTableFilterFromView(TableID: Integer; Caption: Text; View: Text): Text + var + FilterBuilder: FilterPageBuilder; + begin + FilterBuilder.AddTable(Caption, TableID); + FilterBuilder.SetView(Caption, View); + exit(FilterBuilder.GetView(Caption, false)); + end; + + internal procedure SetCustomIntegrationsTableMappings(FSConnectionSetup: Record "FS Connection Setup") + begin + OnAfterResetConfiguration(FSConnectionSetup); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Integration Management", 'OnBeforeHandleCustomIntegrationTableMapping', '', false, false)] + local procedure OnBeforeHandleCustomIntegrationTableMapping(var IsHandled: Boolean; IntegrationTableMappingName: Code[20]) + var + FSConnectionSetup: Record "FS Connection Setup"; + IntegrationTableMapping: Record "Integration Table Mapping"; + begin + if not FSConnectionSetup.IsEnabled() then + exit; + + if not IntegrationTableMapping.Get(IntegrationTableMappingName) then + exit; + + case IntegrationTableMapping."Table ID" of + Database::Resource: + if IntegrationTableMapping."Integration Table ID" = Database::"FS Bookable Resource" then + ResetResourceBookableResourceMapping(FSConnectionSetup, IntegrationTableMapping.Name, true); + Database::"Job Task": + if IntegrationTableMapping."Integration Table ID" = Database::"FS Project Task" then + ResetProjectTaskMapping(FSConnectionSetup, IntegrationTableMapping.Name, true); + Database::"Service Item": + if IntegrationTableMapping."Integration Table ID" = Database::"FS Customer Asset" then + ResetServiceItemCustomerAssetMapping(FSConnectionSetup, IntegrationTableMapping.Name, true); + Database::"Job Journal Line": + begin + if IntegrationTableMapping."Integration Table ID" = Database::"FS Work Order Product" then + ResetProjectJournalLineWOProductMapping(FSConnectionSetup, IntegrationTableMapping.Name, true); + if IntegrationTableMapping."Integration Table ID" = Database::"FS Work Order Service" then + ResetProjectJournalLineWOServiceMapping(FSConnectionSetup, IntegrationTableMapping.Name, true); + end; + Database::Location: + if IntegrationTableMapping."Integration Table ID" = Database::"FS Warehouse" then + ResetLocationMapping(FSConnectionSetup, IntegrationTableMapping.Name, true); + end; + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterResetConfiguration(FSConnectionSetup: Record "FS Connection Setup") + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterResetProjectJournalLineWOProductMapping(IntegrationTableMappingName: Code[20]) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterResetProjectJournalLineWOServiceMapping(IntegrationTableMappingName: Code[20]) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterResetServiceItemCustomerAssetMapping(IntegrationTableMappingName: Code[20]) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnAfterResetResourceBookableResourceMapping(var IntegrationTableMappingName: Code[20]) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetConfiguration(var FSConnectionSetup: Record "FS Connection Setup"; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetProjectJournalLineWOProductMapping(var IntegrationTableMappingName: Code[20]; var ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetProjectJournalLineWOServiceMapping(var IntegrationTableMappingName: Code[20]; var ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetServiceItemCustomerAssetMapping(var IntegrationTableMappingName: Code[20]; var ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetProjectTaskMapping(var IntegrationTableMappingName: Code[20]; var ShouldRecreateJobQueueEntry: Boolean; var IsHandled: Boolean) + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnBeforeResetResourceBookableResourceMapping(var IntegrationTableMappingName: Code[20]; var 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 + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Enums/FSWorkOrderLinePostRule.Enum.al b/Apps/W1/FieldServiceIntegration/app/src/Enums/FSWorkOrderLinePostRule.Enum.al new file mode 100644 index 0000000000..1cb0f201c7 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Enums/FSWorkOrderLinePostRule.Enum.al @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6610 "FS Work Order Line Post Rule" +{ + AssignmentCompatibility = true; + Extensible = true; + + value(0; Never) + { + Caption = 'I will post project journal lines manually'; + } + value(1; LineUsed) + { + Caption = 'when work order product/service is used'; + } + value(2; WorkOrderCompleted) + { + Caption = 'when work order is completed'; + } +} \ No newline at end of file diff --git a/Apps/W1/ReportLayouts/app/Permissions/ReportLayoutsObjects.permissionset.al b/Apps/W1/FieldServiceIntegration/app/src/Enums/FSWorkOrderLineSynchRule.Enum.al similarity index 50% rename from Apps/W1/ReportLayouts/app/Permissions/ReportLayoutsObjects.permissionset.al rename to Apps/W1/FieldServiceIntegration/app/src/Enums/FSWorkOrderLineSynchRule.Enum.al index e9a4593313..130c53ff08 100644 --- a/Apps/W1/ReportLayouts/app/Permissions/ReportLayoutsObjects.permissionset.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Enums/FSWorkOrderLineSynchRule.Enum.al @@ -2,18 +2,19 @@ // 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; -namespace System.Security.AccessControl; - -using Microsoft.Shared.Report; -permissionset 9660 "Report Layouts - Objects" +enum 6611 "FS Work Order Line Synch. Rule" { - Assignable = false; - Access = Public; + AssignmentCompatibility = true; + Extensible = true; - Permissions = - Page "Report Layouts" = X, - Page "Report Layout Edit Dialog" = X, - Page "Report Layout New Dialog" = X, - codeunit "Report Layouts Impl." = X; + value(0; LineUsed) + { + Caption = 'when work order product/service is used'; + } + value(1; WorkOrderCompleted) + { + Caption = 'when work order is completed'; + } } \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobJournal.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobJournal.PageExt.al new file mode 100644 index 0000000000..b8ba6ef73a --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobJournal.PageExt.al @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Project.Journal; + +pageextension 6618 "FS Job Journal" extends "Job Journal" +{ + layout + { + addafter("Remaining Qty.") + { + field(FSQuantityToTransferToInvoice; Rec."Qty. to Transfer to Invoice") + { + ApplicationArea = Jobs; + Visible = FSRelatedFieldsVisible; + ToolTip = 'Specifies the number of units of the project journal''s No. field, that is, either the resource, item, or G/L account number, that applies. If you later change the value in the No. field, the quantity does not change on the journal line.'; + } + } + } + + var + FSRelatedFieldsVisible: Boolean; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + FSRelatedFieldsVisible := FSConnectionSetup.IsEnabled(); + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobProjectManagerRC.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobProjectManagerRC.PageExt.al new file mode 100644 index 0000000000..29bb00e0fa --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobProjectManagerRC.PageExt.al @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.RoleCenters; +using Microsoft.Integration.Dataverse; + +pageextension 6615 "FS Job Project Manager RC" extends "Job Project Manager RC" +{ + actions + { + addlast(sections) + { + group(GroupFS) + { + Caption = 'Dynamics 365 Field Service'; + + action("Bookable Resources - Field Service") + { + ApplicationArea = Suite; + Caption = 'Bookable Resources - Dynamics 365 Field Service'; + RunObject = Page "FS Bookable Resource List"; + } + action("Customer Assets -Field Service") + { + ApplicationArea = Suite; + Caption = 'Customer Assets - Dynamics 365 Field Service'; + RunObject = Page "FS Customer Asset List"; + } + action("Records Skipped For Synch.") + { + ApplicationArea = Suite; + Caption = 'Coupled Data Synchronization Errors'; + RunObject = Page "CRM Skipped Records"; + AccessByPermission = TableData "CRM Integration Record" = R; + } + } + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskCard.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskCard.PageExt.al new file mode 100644 index 0000000000..1952251e9f --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskCard.PageExt.al @@ -0,0 +1,167 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Project.Job; +using Microsoft.Integration.Dataverse; + +pageextension 6610 "FS Job Task Card" extends "Job Task Card" +{ + actions + { + addafter("&Job Task") + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Enabled = FSActionGroupEnabled; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Project Task in Field Service'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service entity.'; + + 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 + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.UpdateOneNow(Rec.RecordId); + 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 product.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.DefineCoupling(Rec.RecordId); + 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 entity.'; + + trigger OnAction() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + CRMCouplingManagement.RemoveCoupling(Rec.RecordId); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the job task 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(SynchronizeNowFS_Promoted; SynchronizeNowFS) + { + } + actionref(GoToProductFS_Promoted; GoToProductFS) + { + } + actionref(FSShowLog_Promoted; FSShowLog) + { + } + } + } + } + + + var + FSActionGroupEnabled: Boolean; + FSIntegrationEnabled: Boolean; + CRMIsCoupledToRecord: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnAfterGetCurrRecord() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + if CRMIntegrationEnabled then + CRMIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId); + end; + + trigger OnOpenPage() + var + Job: Record Job; + FSConnectionSetup: Record "FS Connection Setup"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + ApplyUsageLink: Boolean; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + + if Job.Get(Rec."Job No.") then + ApplyUsageLink := Job."Apply Usage Link"; + + FSActionGroupEnabled := FSIntegrationEnabled and (Rec."Job Task Type" = Rec."Job Task Type"::Posting) and ApplyUsageLink; + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskLines.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskLines.PageExt.al new file mode 100644 index 0000000000..fd98640764 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskLines.PageExt.al @@ -0,0 +1,179 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Project.Job; +using Microsoft.Integration.Dataverse; + +pageextension 6611 "FS Job Task Lines" extends "Job Task Lines" +{ + layout + { + addlast(Control1) + { + field("Coupled to FS"; Rec."Coupled to FS") + { + ApplicationArea = Jobs; + Visible = FSIntegrationEnabled; + ToolTip = 'Specifies if the project task is coupled to an entity in Field Service.'; + } + } + } + + actions + { + addafter("&Job Task") + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Enabled = FSActionGroupEnabled; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Project Task in Field Service'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service entity.'; + + 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 + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.UpdateOneNow(Rec.RecordId); + 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 product.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.DefineCoupling(Rec.RecordId); + 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 entity.'; + + trigger OnAction() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + CRMCouplingManagement.RemoveCoupling(Rec.RecordId); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the job task line 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(SynchronizeNowFS_Promoted; SynchronizeNowFS) + { + } + actionref(GoToProductFS_Promoted; GoToProductFS) + { + } + actionref(FSShowLog_Promoted; FSShowLog) + { + } + } + } + } + + + var + FSActionGroupEnabled: Boolean; + FSIntegrationEnabled: Boolean; + CRMIsCoupledToRecord: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnAfterGetCurrRecord() + var + Job: Record Job; + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + if FSIntegrationEnabled then + CRMIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId); + + if not Job.Get(Rec."Job No.") then + FSActionGroupEnabled := false + else + FSActionGroupEnabled := FSIntegrationEnabled and (Rec."Job Task Type" = Rec."Job Task Type"::Posting) and Job."Apply Usage Link"; + 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; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskList.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskList.PageExt.al new file mode 100644 index 0000000000..826a9ce239 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSJobTaskList.PageExt.al @@ -0,0 +1,179 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Project.Job; +using Microsoft.Integration.Dataverse; + +pageextension 6617 "FS Job Task List" extends "Job Task List" +{ + layout + { + addlast(Control1) + { + field("Coupled to FS"; Rec."Coupled to FS") + { + ApplicationArea = Jobs; + Visible = FSIntegrationEnabled; + ToolTip = 'Specifies if the project task is coupled to an entity in Field Service.'; + } + } + } + + actions + { + addafter("&Job Task") + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Enabled = FSActionGroupEnabled; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Project Task in Field Service'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service entity.'; + + 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 + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.UpdateOneNow(Rec.RecordId); + 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 product.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.DefineCoupling(Rec.RecordId); + 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 entity.'; + + trigger OnAction() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + CRMCouplingManagement.RemoveCoupling(Rec.RecordId); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the job task 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(SynchronizeNowFS_Promoted; SynchronizeNowFS) + { + } + actionref(GoToProductFS_Promoted; GoToProductFS) + { + } + actionref(FSShowLog_Promoted; FSShowLog) + { + } + } + } + } + + + var + FSActionGroupEnabled: Boolean; + FSIntegrationEnabled: Boolean; + CRMIsCoupledToRecord: Boolean; + CRMIntegrationEnabled: Boolean; + + trigger OnAfterGetCurrRecord() + var + Job: Record Job; + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + if FSIntegrationEnabled then + CRMIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId); + + if not Job.Get(Rec."Job No.") then + FSActionGroupEnabled := false + else + FSActionGroupEnabled := FSIntegrationEnabled and (Rec."Job Task Type" = Rec."Job Task Type"::Posting) and Job."Apply Usage Link"; + 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; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSProjectManagerActivities.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSProjectManagerActivities.PageExt.al new file mode 100644 index 0000000000..feac69a9fd --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSProjectManagerActivities.PageExt.al @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.RoleCenters; +using Microsoft.Integration.SyncEngine; +using Microsoft.Integration.Dataverse; + +pageextension 6616 "FS Project Manager Activities" extends "Project Manager Activities" +{ + layout + { + addlast(content) + { + cuegroup("FS Data Integration") + { + Caption = 'Data Integration'; + Visible = ShowFSIntegrationCues; + + field("FS Integration Errors"; Rec."FS Int. Errors") + { + ApplicationArea = Basic, Suite; + Caption = 'Integration Errors'; + DrillDownPageID = "Integration Synch. Error List"; + ToolTip = 'Specifies the number of errors related to data integration with Dynamics 365 Field Service.'; + Visible = ShowFSIntegrationCues; + } + field("FS Coupled Data Synch Errors"; Rec."Coupled Data Sync Errors") + { + ApplicationArea = RelationshipMgmt; + Caption = 'Coupled Data Synchronization Errors'; + DrillDownPageID = "CRM Skipped Records"; + ToolTip = 'Specifies the number of errors that occurred in the latest synchronization of coupled data between Business Central and Dynamics 365 Field Service.'; + Visible = ShowFSIntegrationCues; + } + } + } + } + + var + ShowFSIntegrationCues: Boolean; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + ShowFSIntegrationCues := FSConnectionSetup.IsEnabled(); + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceCard.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceCard.PageExt.al new file mode 100644 index 0000000000..6183b5345f --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceCard.PageExt.al @@ -0,0 +1,213 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Resources.Resource; +using Microsoft.Integration.SyncEngine; + +pageextension 6612 "FS Resource Card" extends "Resource Card" +{ + actions + { + modify(ActionGroupCRM) + { + Visible = CRMIntegrationEnabled and (not FSIntegrationEnabled); + } + addafter(ActionGroupCRM) + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Visible = FSIntegrationEnabled; + Enabled = (BlockedFilterApplied and (not Rec.Blocked)) or not BlockedFilterApplied; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Bookable Resource'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service bookable resource.'; + + 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 Sales.'; + + trigger OnAction() + var + Resource: Record Resource; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + ResourceRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(Resource); + Resource.Next(); + + if Resource.Count = 1 then + CRMIntegrationManagement.UpdateOneNow(Resource.RecordId) + else begin + ResourceRecordRef.GetTable(Resource); + CRMIntegrationManagement.UpdateMultipleNow(ResourceRecordRef); + 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 Sales 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 Sales product.'; + + 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 resources to products in Dynamics 365 Sales based on matching criteria.'; + + trigger OnAction() + var + Resource: Record Resource; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(Resource); + RecRef.GetTable(Resource); + 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 Sales product.'; + + trigger OnAction() + var + Resource: Record Resource; + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(Resource); + RecRef.GetTable(Resource); + CRMCouplingManagement.RemoveCoupling(RecRef); + 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; + } + } + } + addafter(Category_Synchronize) + { + 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) + { + } + actionref("Unit Group_FS_Promoted"; "Unit Group") + { + } + } + } + } + + var + FSIntegrationEnabled: Boolean; + CRMIsCoupledToRecord: Boolean; + CRMIntegrationEnabled: Boolean; + BlockedFilterApplied: 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"; + IntegrationTableMapping: Record "Integration Table Mapping"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then begin + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + if FSIntegrationEnabled then begin + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + IntegrationTableMapping.SetRange("Table ID", Database::Resource); + IntegrationTableMapping.SetRange("Integration Table ID", Database::"FS Bookable Resource"); + if IntegrationTableMapping.FindFirst() then + BlockedFilterApplied := IntegrationTableMapping.GetTableFilter().Contains('Field38=1(0)'); + end; + end; + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceList.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceList.PageExt.al new file mode 100644 index 0000000000..b63f802a2b --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSResourceList.PageExt.al @@ -0,0 +1,210 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Resources.Resource; +using Microsoft.Integration.SyncEngine; + +pageextension 6613 "FS Resource List" extends "Resource List" +{ + actions + { + modify(ActionGroupCRM) + { + Visible = CRMIntegrationEnabled and (not FSIntegrationEnabled); + } + addafter(ActionGroupCRM) + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Visible = FSIntegrationEnabled; + Enabled = (BlockedFilterApplied and (not Rec.Blocked)) or not BlockedFilterApplied; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Bookable Resource'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service bookable resource.'; + + 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 Sales.'; + + trigger OnAction() + var + Resource: Record Resource; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + ResourceRecordRef: RecordRef; + begin + CurrPage.SetSelectionFilter(Resource); + Resource.Next(); + + if Resource.Count = 1 then + CRMIntegrationManagement.UpdateOneNow(Resource.RecordId) + else begin + ResourceRecordRef.GetTable(Resource); + CRMIntegrationManagement.UpdateMultipleNow(ResourceRecordRef); + 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 Sales 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 Sales product.'; + + 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 resources to products in Dynamics 365 Sales based on matching criteria.'; + + trigger OnAction() + var + Resource: Record Resource; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(Resource); + RecRef.GetTable(Resource); + 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 Sales product.'; + + trigger OnAction() + var + Resource: Record Resource; + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(Resource); + RecRef.GetTable(Resource); + CRMCouplingManagement.RemoveCoupling(RecRef); + 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; + } + } + } + addafter(Category_Synchronize) + { + 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; + BlockedFilterApplied: 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"; + IntegrationTableMapping: Record "Integration Table Mapping"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then begin + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + if FSIntegrationEnabled then begin + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + IntegrationTableMapping.SetRange("Table ID", Database::Resource); + IntegrationTableMapping.SetRange("Integration Table ID", Database::"FS Bookable Resource"); + if IntegrationTableMapping.FindFirst() then + BlockedFilterApplied := IntegrationTableMapping.GetTableFilter().Contains('Field38=1(0)'); + end; + end; + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceConnections.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceConnections.PageExt.al new file mode 100644 index 0000000000..7dfbafccbd --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceConnections.PageExt.al @@ -0,0 +1,17 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Utilities; + +pageextension 6622 "FS Service Connections" extends "Service Connections" +{ + trigger OnOpenPage() + var + FSAssistedSetupSubscriber: Codeunit "FS Assisted Setup Subscriber"; + begin + FSAssistedSetupSubscriber.RegisterAssistedSetup(); + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemCard.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemCard.PageExt.al new file mode 100644 index 0000000000..8d0c5e5f3d --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemCard.PageExt.al @@ -0,0 +1,186 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Item; +using Microsoft.Integration.Dataverse; +using Microsoft.Integration.SyncEngine; + +pageextension 6619 "FS Service Item Card" extends "Service Item Card" +{ + actions + { + addafter(History) + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Visible = FSIntegrationEnabled; + Enabled = (BlockedFilterApplied and (Rec.Blocked = Rec.Blocked::" ")) or not BlockedFilterApplied; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Customer Asset'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service customer asset.'; + + 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 + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.UpdateOneNow(Rec.RecordId); + 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 product.'; + + 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 resources to products in Field Service based on matching criteria.'; + + trigger OnAction() + var + ServiceItem: Record "Service Item"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceItem); + RecRef.GetTable(ServiceItem); + 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 entity.'; + + trigger OnAction() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + CRMCouplingManagement.RemoveCoupling(Rec.RecordId); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the service item 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; + BlockedFilterApplied: Boolean; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + IntegrationTableMapping: Record "Integration Table Mapping"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then begin + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + if FSIntegrationEnabled then begin + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + IntegrationTableMapping.SetRange("Table ID", Database::"Service Item"); + IntegrationTableMapping.SetRange("Integration Table ID", Database::"FS Customer Asset"); + if IntegrationTableMapping.FindFirst() then + BlockedFilterApplied := IntegrationTableMapping.GetTableFilter().Contains('Field40=1(0)'); + end; + end; + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemList.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemList.PageExt.al new file mode 100644 index 0000000000..6c57650a99 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceItemList.PageExt.al @@ -0,0 +1,200 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Item; +using Microsoft.Integration.Dataverse; +using Microsoft.Integration.SyncEngine; + +pageextension 6620 "FS Service Item List" extends "Service Item List" +{ + layout + { + addlast(Control1) + { + field("Coupled to FS"; Rec."Coupled to FS") + { + ApplicationArea = All; + Visible = FSIntegrationEnabled; + ToolTip = 'Specifies if the project task is coupled to an entity in Field Service.'; + } + } + } + + + actions + { + addafter(History) + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Visible = FSIntegrationEnabled; + Enabled = (BlockedFilterApplied and (Rec.Blocked = Rec.Blocked::" ")) or not BlockedFilterApplied; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Customer Asset'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service customer asset.'; + + 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 + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.UpdateOneNow(Rec.RecordId); + 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 product.'; + + 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 resources to products in Field Service based on matching criteria.'; + + trigger OnAction() + var + ServiceItem: Record "Service Item"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + RecRef: RecordRef; + begin + CurrPage.SetSelectionFilter(ServiceItem); + RecRef.GetTable(ServiceItem); + 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 entity.'; + + trigger OnAction() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + CRMCouplingManagement.RemoveCoupling(Rec.RecordId); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the service item 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; + BlockedFilterApplied: Boolean; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + IntegrationTableMapping: Record "Integration Table Mapping"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationEnabled := CRMIntegrationManagement.IsCRMIntegrationEnabled(); + if CRMIntegrationEnabled then begin + FSIntegrationEnabled := FSConnectionSetup.IsEnabled(); + if FSIntegrationEnabled then begin + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + IntegrationTableMapping.SetRange("Table ID", Database::"Service Item"); + IntegrationTableMapping.SetRange("Integration Table ID", Database::"FS Customer Asset"); + if IntegrationTableMapping.FindFirst() then + BlockedFilterApplied := IntegrationTableMapping.GetTableFilter().Contains('Field40=1(0)'); + end; + end; + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceManagerRC.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceManagerRC.PageExt.al new file mode 100644 index 0000000000..0fde612097 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Page Extensions/FSServiceManagerRC.PageExt.al @@ -0,0 +1,42 @@ +// ------------------------------------------------------------------------------------------------ +// 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.RoleCenters; + +pageextension 6621 "FS Service Manager RC" extends "Service Manager Role Center" +{ + actions + { + addlast(sections) + { + group(GroupFS) + { + Caption = 'Dynamics 365 Field Service'; + + action("Bookable Resources - Field Service") + { + ApplicationArea = Suite; + Caption = 'Bookable Resources - Dynamics 365 Field Service'; + RunObject = Page "FS Bookable Resource List"; + } + action("Customer Assets - Field Service") + { + ApplicationArea = Suite; + Caption = 'Customer Assets - Dynamics 365 Field Service'; + RunObject = Page "FS Customer Asset List"; + } + action("Records Skipped For Synch.") + { + ApplicationArea = Suite; + Caption = 'Coupled Data Synchronization Errors'; + RunObject = Page "CRM Skipped Records"; + AccessByPermission = TableData "CRM Integration Record" = R; + } + } + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSBookableResourceList.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSBookableResourceList.Page.al new file mode 100644 index 0000000000..829ac65f20 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSBookableResourceList.Page.al @@ -0,0 +1,166 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Resources.Resource; + +page 6610 "FS Bookable Resource List" +{ + ApplicationArea = Suite; + Caption = 'Bookable Resources - Dynamics 365 Field Service'; + Editable = false; + PageType = List; + SourceTable = "FS Bookable Resource"; + SourceTableView = sorting(Name); + UsageCategory = Lists; + + layout + { + area(content) + { + repeater(Control2) + { + ShowCaption = false; + field(Name; Rec.Name) + { + ApplicationArea = Suite; + Caption = 'Name'; + StyleExpr = FirstColumnStyle; + ToolTip = 'Specifies the bookable resource name.'; + } + field(HourlyRate; Rec.HourlyRate) + { + ApplicationArea = Suite; + Caption = 'Hourly Rate'; + ToolTip = 'Specifies the bookable resource hourly rate.'; + } + field(ResourceType; Rec.ResourceType) + { + ApplicationArea = Suite; + Caption = 'Resource Type'; + ToolTip = 'Specifies the bookable resource type.'; + } + field(Coupled; Coupled) + { + ApplicationArea = Suite; + Caption = 'Coupled'; + ToolTip = 'Specifies if the Dynamics 365 Field Service record is coupled to Business Central.'; + } + } + } + } + + actions + { + area(processing) + { + action(CreateFromFS) + { + ApplicationArea = Suite; + Caption = 'Create in Business Central'; + Image = NewResource; + ToolTip = 'Generate the entity from the Field Service bookable resource.'; + + trigger OnAction() + var + FSBookableResource: Record "FS Bookable Resource"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CurrPage.SetSelectionFilter(FSBookableResource); + CRMIntegrationManagement.CreateNewRecordsFromSelectedCRMRecords(FSBookableResource); + 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 OnAfterGetRecord() + var + CRMIntegrationRecord: Record "CRM Integration Record"; + RecordID: RecordID; + begin + if CRMIntegrationRecord.FindRecordIDFromID(Rec.BookableResourceId, Database::Resource, RecordID) then + if CurrentlyCoupledFSBookableResource.BookableResourceId = Rec.BookableResourceId 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; + + trigger OnInit() + begin + Codeunit.Run(Codeunit::"CRM Integration Management"); + end; + + trigger OnOpenPage() + var + LookupCRMTables: Codeunit "Lookup CRM Tables"; + begin + Rec.FilterGroup(4); + Rec.SetView(LookupCRMTables.GetIntegrationTableMappingView(Database::"FS Bookable Resource")); + Rec.FilterGroup(0); + end; + + var + CurrentlyCoupledFSBookableResource: Record "FS Bookable Resource"; + Coupled: Text; + FirstColumnStyle: Text; + + procedure SetCurrentlyCoupledFSBookableResource(FSBookableResource: Record "FS Bookable Resource") + begin + CurrentlyCoupledFSBookableResource := FSBookableResource; + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al new file mode 100644 index 0000000000..ce744c8f99 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetup.Page.al @@ -0,0 +1,505 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Environment.Configuration; +using System.Telemetry; +using System.Threading; +using Microsoft.Integration.D365Sales; +using Microsoft.Projects.Project.Journal; + +page 6612 "FS Connection Setup" +{ + AccessByPermission = TableData "FS Connection Setup" = IM; + ApplicationArea = Suite; + Caption = 'Dynamics 365 Field Service Integration Setup'; + DeleteAllowed = false; + InsertAllowed = false; + LinksAllowed = false; + ShowFilter = false; + SourceTable = "FS Connection Setup"; + UsageCategory = Administration; + AdditionalSearchTerms = 'Dynamics 365 Field Service Connection Setup'; + + layout + { + area(content) + { + group(NAVToFS) + { + Caption = 'Connection from Dynamics 365 Business Central to Dynamics 365 Field Service'; + field("Server Address"; Rec."Server Address") + { + ApplicationArea = Suite; + Editable = IsEditable; + ToolTip = 'Specifies the URL of the environment that hosts the Dynamics 365 Field Service solution that you want to connect to.'; + + trigger OnValidate() + begin + ConnectionString := Rec.GetConnectionStringAsStoredInSetup(); + end; + } + field("Is Enabled"; Rec."Is Enabled") + { + ApplicationArea = Suite; + Caption = 'Enabled', Comment = 'Name of tickbox which shows whether the connection is enabled or disabled'; + ToolTip = 'Specifies if the connection to Dynamics 365 Field Service is enabled. When you check this checkbox, you will be prompted to sign-in to Dataverse with an administrator user account. The account will be used one time to give consent to, install and configure applications and components that the integration requires.'; + + trigger OnValidate() + var + FeatureTelemetry: Codeunit "Feature Telemetry"; + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + begin + CurrPage.Update(true); + if Rec."Is Enabled" then begin + FeatureTelemetry.LogUptake('0000MB9', 'Dynamics 365 Field Service', Enum::"Feature Uptake Status"::"Set up"); + Session.LogMessage('0000MBC', CRMConnEnabledOnPageTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + + if (Rec."Server Address" <> '') and (Rec."Server Address" <> TestServerAddressTok) then + if CDSIntegrationImpl.MultipleCompaniesConnected() then + CDSIntegrationImpl.SendMultipleCompaniesNotification(); + end; + end; + } + field(ScheduledSynchJobsActive; ScheduledSynchJobsRunning) + { + ApplicationArea = Suite; + Caption = 'Active scheduled synchronization jobs'; + Editable = false; + StyleExpr = ScheduledSynchJobsRunningStyleExpr; + ToolTip = 'Specifies how many of the default integration synchronization job queue entries ready to automatically synchronize data between Business Central and Dynamics 365 Field Service.'; + + trigger OnDrillDown() + var + ScheduledSynchJobsRunningMsg: Text; + begin + if TotalJobs = 0 then + ScheduledSynchJobsRunningMsg := JobQueueIsNotRunningMsg + else + if ActiveJobs = TotalJobs then + ScheduledSynchJobsRunningMsg := AllScheduledJobsAreRunningMsg + else + ScheduledSynchJobsRunningMsg := StrSubstNo(PartialScheduledJobsAreRunningMsg, ActiveJobs, TotalJobs); + Message(ScheduledSynchJobsRunningMsg); + end; + } + } + group(FSSettings) + { + Caption = 'Additional Settings'; + Visible = true; + field("Is FS Solution Installed"; Rec."Is FS Solution Installed") + { + ApplicationArea = Suite; + Caption = 'Dynamics 365 Business Central Integration Solution Imported to Field Service'; + Editable = false; + Visible = false; + StyleExpr = CRMSolutionInstalledStyleExpr; + ToolTip = 'Specifies if the Integration Solution is installed and configured in Dynamics 365 Field Service. You cannot change this setting.'; + + trigger OnDrillDown() + begin + if Rec."Is FS Solution Installed" then + Message(FavorableCRMSolutionInstalledMsg, PRODUCTNAME.Short(), CRMProductName.FSServiceName()) + else + Message(UnfavorableCRMSolutionInstalledMsg, PRODUCTNAME.Short()); + end; + } + field("Job Journal Template"; Rec."Job Journal Template") + { + 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.'; + } + 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.'; + } + field("Hour Unit of Measure"; Rec."Hour Unit of Measure") + { + ApplicationArea = Suite; + 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(SynchSettings) + { + Caption = 'Synchronization Settings'; + Visible = Rec."Is Enabled"; + field("Line Synch. Rule"; Rec."Line Synch. Rule") + { + ApplicationArea = Suite; + ToolTip = 'Specifies when to synchronize work order products and work order services.'; + + trigger OnValidate() + var + IntegrationTableMapping: Record "Integration Table Mapping"; + JobJouralLine: Record "Job Journal Line"; + FSSetupDefaults: Codeunit "FS Setup Defaults"; + begin + if not Rec."Is Enabled" then + exit; + + if not Confirm(StrSubstNo(ResetOneIntegrationTableMappingConfirmQst, JobJouralLine.TableCaption())) then + Error(''); + + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + IntegrationTableMapping.SetRange("Table ID", Database::"Job Journal Line"); + IntegrationTableMapping.SetFilter("Integration Table ID", Format(Database::"FS Work Order Product") + '|' + Format(Database::"FS Work Order Service")); + if IntegrationTableMapping.FindSet() then + repeat + if IntegrationTableMapping."Integration Table ID" = Database::"FS Work Order Service" then + FSSetupDefaults.ResetProjectJournalLineWOServiceMapping(Rec, IntegrationTableMapping.Name, true); + if IntegrationTableMapping."Integration Table ID" = Database::"FS Work Order Product" then + FSSetupDefaults.ResetProjectJournalLineWOProductMapping(Rec, IntegrationTableMapping.Name, true); + until IntegrationTableMapping.Next() = 0; + end; + } + 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.'; + } + } + } + } + + actions + { + area(processing) + { + action("Assisted Setup") + { + ApplicationArea = Suite; + Caption = 'Assisted Setup'; + Image = Setup; + ToolTip = 'Runs Dynamics 365 Field Service Connection Setup Wizard.'; + + trigger OnAction() + var + GuidedExperience: Codeunit "Guided Experience"; + CRMIntegrationMgt: Codeunit "CRM Integration Management"; + GuidedExperienceType: Enum "Guided Experience Type"; + begin + CRMIntegrationMgt.RegisterAssistedSetup(); + Commit(); // Make sure all data is committed before we run the wizard + GuidedExperience.Run(GuidedExperienceType::"Assisted Setup", ObjectType::Page, Page::"FS Connection Setup Wizard"); + CurrPage.Update(false); + end; + } + action("Test Connection") + { + ApplicationArea = Suite; + Caption = 'Test Connection', Comment = 'Test is a verb.'; + Image = ValidateEmailLoggingSetup; + ToolTip = 'Tests the connection to Dynamics 365 Field Service using the specified settings.'; + + trigger OnAction() + begin + Rec.PerformTestConnection(); + end; + } + action(IntegrationTableMappings) + { + ApplicationArea = Suite; + Caption = 'Integration Table Mappings'; + Enabled = Rec."Is Enabled"; + Image = MapAccounts; + ToolTip = 'Opens the integration table mapping list.'; + + trigger OnAction() + begin + Page.Run(Page::"Integration Table Mapping List"); + end; + } + action("Redeploy Solution") + { + ApplicationArea = Suite; + Caption = 'Redeploy Integration Solution'; + Image = Setup; + Enabled = IsCdsIntegrationEnabled and (not Rec."Is Enabled"); + ToolTip = 'Redeploy and reconfigure the Dynamics 365 Field Service integration solution.'; + + trigger OnAction() + begin + Commit(); + Rec.DeployFSSolution(true); + end; + } + action(ResetConfiguration) + { + ApplicationArea = Suite; + Caption = 'Use Default Synchronization Setup'; + Enabled = Rec."Is Enabled"; + Image = ResetStatus; + ToolTip = 'Resets the integration table mappings and synchronization jobs to the default values for a connection with Dynamics 365 Field Service. All current mappings are deleted.'; + + trigger OnAction() + var + FSSetupDefaults: Codeunit "FS Setup Defaults"; + begin + Rec.EnsureCDSConnectionIsEnabled(); + Rec.EnsureCRMConnectionIsEnabled(); + if Confirm(ResetIntegrationTableMappingConfirmQst, false, CRMProductName.FSServiceName()) then begin + FSSetupDefaults.ResetConfiguration(Rec); + Message(SetupSuccessfulMsg, CRMProductName.FSServiceName()); + end; + Rec.RefreshDataFromFS(); + end; + } + action(StartInitialSynchAction) + { + ApplicationArea = Suite; + Caption = 'Run Full Synchronization'; + Enabled = Rec."Is Enabled"; + Image = RefreshLines; + ToolTip = 'Start all the default integration jobs for synchronizing Business Central record types and Dynamics 365 Field Service entities, as defined on the Integration Table Mappings page.'; + + trigger OnAction() + begin + Page.Run(Page::"CRM Full Synch. Review"); + end; + } + } + area(navigation) + { + action("Synch. Job Queue Entries") + { + ApplicationArea = Suite; + Caption = 'Synch. Job Queue Entries'; + Image = JobListSetup; + ToolTip = 'View the job queue entries that manage the scheduled synchronization between Dynamics 365 Field Service and Business Central.'; + + trigger OnAction() + var + JobQueueEntry: Record "Job Queue Entry"; + begin + JobQueueEntry.FilterGroup := 2; + JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit); + JobQueueEntry.SetFilter("Object ID to Run", Rec.GetJobQueueEntriesObjectIDToRunFilter()); + JobQueueEntry.FilterGroup := 0; + + Page.Run(Page::"Job Queue Entries", JobQueueEntry); + end; + } + action(SkippedSynchRecords) + { + ApplicationArea = Suite; + Caption = 'Skipped Synch. Records'; + Enabled = Rec."Is Enabled"; + Image = NegativeLines; + RunObject = Page "CRM Skipped Records"; + RunPageMode = View; + ToolTip = 'View the list of records that will be skipped for synchronization.'; + } + } + area(Promoted) + { + group(Category_Process) + { + Caption = 'Connection', Comment = 'Generated from the PromotedActionCategories property index 1.'; + + actionref("Assisted Setup_Promoted"; "Assisted Setup") + { + } + actionref("Test Connection_Promoted"; "Test Connection") + { + } + } + group(Category_Report) + { + Caption = 'Mapping', Comment = 'Generated from the PromotedActionCategories property index 2.'; + + actionref(IntegrationTableMappings_Promoted; IntegrationTableMappings) + { + } + actionref("Redeploy Solution_Promoted"; "Redeploy Solution") + { + } + } + group(Category_Category4) + { + Caption = 'Synchronization', Comment = 'Generated from the PromotedActionCategories property index 3.'; + + actionref(StartInitialSynchAction_Promoted; StartInitialSynchAction) + { + } + actionref("Synch. Job Queue Entries_Promoted"; "Synch. Job Queue Entries") + { + } + actionref(SkippedSynchRecords_Promoted; SkippedSynchRecords) + { + } + } + } + } + + trigger OnAfterGetRecord() + begin + RefreshData(); + end; + + trigger OnInit() + var + ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + ApplicationAreaMgmtFacade.CheckAppAreaOnlyBasic(); + CRMIntegrationManagement.RegisterAssistedSetup(); + SetVisibilityFlags(); + end; + + trigger OnOpenPage() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + FeatureTelemetry: Codeunit "Feature Telemetry"; + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + MultipleCompaniesDetected: Boolean; + begin + FeatureTelemetry.LogUptake('0000MBA', 'Dataverse', Enum::"Feature Uptake Status"::Discovered); + FeatureTelemetry.LogUptake('0000MBB', 'Dynamics 365 Field Service', Enum::"Feature Uptake Status"::Discovered); + Rec.EnsureCDSConnectionIsEnabled(); + Rec.EnsureCRMConnectionIsEnabled(); + + if not Rec.Get() then begin + Rec.Init(); + InitializeDefaultProxyVersion(); + Rec.Insert(); + Rec.LoadConnectionStringElementsFromCDSConnectionSetup(); + end else begin + if not Rec."Is Enabled" then + Rec.LoadConnectionStringElementsFromCDSConnectionSetup(); + ConnectionString := Rec.GetConnectionStringAsStoredInSetup(); + Rec.UnregisterConnection(); + if (not IsValidProxyVersion()) then begin + if not IsValidProxyVersion() then + InitializeDefaultProxyVersion(); + Rec.Modify(); + end; + if Rec."Is Enabled" then begin + // just try notifying, because the setup may be broken, and we are in OnOpenPage + if TryDetectMultipleCompanies(MultipleCompaniesDetected) then + if MultipleCompaniesDetected then + CDSIntegrationImpl.SendMultipleCompaniesNotification() + + end else + if Rec."Disable Reason" <> '' then + CRMIntegrationManagement.SendConnectionDisabledNotification(Rec."Disable Reason"); + end; + end; + + trigger OnQueryClosePage(CloseAction: Action): Boolean + begin + if not Rec."Is Enabled" then + if not Confirm(StrSubstNo(EnableServiceQst, CurrPage.Caption), true) then + exit(false); + end; + + [TryFunction] + local procedure TryDetectMultipleCompanies(var MultipleCompaniesDetected: Boolean) + var + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + begin + Rec.RegisterConnection(); + if (Rec."Server Address" <> '') and (Rec."Server Address" <> TestServerAddressTok) then + MultipleCompaniesDetected := CDSIntegrationImpl.MultipleCompaniesConnected(); + end; + + var + CRMProductName: Codeunit "CRM Product Name"; + ResetIntegrationTableMappingConfirmQst: Label 'This will restore the default integration table mappings and synchronization jobs for %1. All custom mappings and jobs will be deleted. The default mappings and jobs will be used the next time data is synchronized. Do you want to continue?', Comment = '%1 = CRM product name'; + ResetOneIntegrationTableMappingConfirmQst: Label 'This will restore the default integration table mappings and synchronization jobs for %1. Do you want to continue?', Comment = '%1 = CRM product name'; + UnfavorableCRMSolutionInstalledMsg: Label 'The %1 Integration Solution was not detected.', Comment = '%1 - product name'; + FavorableCRMSolutionInstalledMsg: Label 'The %1 Integration Solution is installed in %2.', Comment = '%1 - product name, %2 = CRM product name'; + ReadyScheduledSynchJobsTok: Label '%1 of %2', Comment = '%1 = Count of scheduled job queue entries in ready or in process state, %2 count of all scheduled jobs'; + ScheduledSynchJobsRunning: Text; + EnableServiceQst: Label 'The %1 is not enabled. Are you sure you want to exit?', Comment = '%1 = This Page Caption (Microsoft Dynamics 365 Connection Setup)'; + PartialScheduledJobsAreRunningMsg: Label 'An active job queue is available but only %1 of the %2 scheduled synchronization jobs are ready or in process.', Comment = '%1 = Count of scheduled job queue entries in ready or in process state, %2 count of all scheduled jobs'; + JobQueueIsNotRunningMsg: Label 'There is no job queue started. Scheduled synchronization jobs require an active job queue to process jobs.\\Contact your administrator to get a job queue configured and started.'; + AllScheduledJobsAreRunningMsg: Label 'An job queue is started and all scheduled synchronization jobs are ready or already processing.'; + SetupSuccessfulMsg: Label 'The default setup for %1 synchronization has completed successfully.', Comment = '%1 = CRM product name'; + CategoryTok: Label 'AL Dataverse Integration', Locked = true; + CRMConnEnabledOnPageTxt: Label 'Field Service Connection has been enabled from FS Connection Setup Page', Locked = true; + TestServerAddressTok: Label '@@test@@', Locked = true; + ScheduledSynchJobsRunningStyleExpr: Text; + CRMSolutionInstalledStyleExpr: Text; + CRMVersionStyleExpr: Text; + ConnectionString: Text; + ActiveJobs: Integer; + TotalJobs: Integer; + IsEditable: Boolean; + IsCdsIntegrationEnabled: Boolean; + CRMVersionStatus: Boolean; + + local procedure RefreshData() + begin + Rec.RefreshDataFromFS(false); + RefreshSynchJobsData(); + UpdateEnableFlags(); + SetStyleExpr(); + end; + + local procedure RefreshSynchJobsData() + begin + Rec.CountCRMJobQueueEntries(ActiveJobs, TotalJobs); + ScheduledSynchJobsRunning := StrSubstNo(ReadyScheduledSynchJobsTok, ActiveJobs, TotalJobs); + ScheduledSynchJobsRunningStyleExpr := GetRunningJobsStyleExpr(); + end; + + local procedure SetStyleExpr() + begin + CRMSolutionInstalledStyleExpr := GetStyleExpr(Rec."Is FS Solution Installed"); + CRMVersionStyleExpr := GetStyleExpr(CRMVersionStatus); + end; + + local procedure GetRunningJobsStyleExpr() StyleExpr: Text + begin + if ActiveJobs < TotalJobs then + StyleExpr := 'Ambiguous' + else + StyleExpr := GetStyleExpr((ActiveJobs = TotalJobs) and (TotalJobs <> 0)) + end; + + local procedure GetStyleExpr(Favorable: Boolean) StyleExpr: Text + begin + if Favorable then + StyleExpr := 'Favorable' + else + StyleExpr := 'Unfavorable' + end; + + local procedure UpdateEnableFlags() + var + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + begin + IsEditable := not Rec."Is Enabled" and not CDSIntegrationImpl.IsIntegrationEnabled(); + end; + + local procedure SetVisibilityFlags() + var + CDSConnectionSetup: Record "CDS Connection Setup"; + begin + if CDSConnectionSetup.Get() then + IsCdsIntegrationEnabled := CDSConnectionSetup."Is Enabled"; + end; + + local procedure IsValidProxyVersion(): Boolean + begin + exit(Rec."Proxy Version" <> 0); + end; + + local procedure InitializeDefaultProxyVersion() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + Rec.Validate("Proxy Version", CRMIntegrationManagement.GetLastProxyVersionItem()); + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al new file mode 100644 index 0000000000..1472bb1947 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSConnectionSetupWizard.Page.al @@ -0,0 +1,517 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Environment; +using System.Environment.Configuration; +using System.Telemetry; +using System.Utilities; +using Microsoft.Integration.D365Sales; + +page 6613 "FS Connection Setup Wizard" +{ + Caption = 'Dynamics 365 Field Service Integration Setup'; + PageType = NavigatePage; + SourceTable = "FS Connection Setup"; + SourceTableTemporary = true; + + layout + { + area(content) + { + group(BannerStandard) + { + Caption = ''; + Editable = false; + Visible = TopBannerVisible and not CredentialsStepVisible; +#pragma warning disable AA0100 + field("MediaResourcesStandard.""Media Reference"""; MediaResourcesStandard."Media Reference") +#pragma warning restore AA0100 + { + ApplicationArea = Suite; + Editable = false; + ShowCaption = false; + } + } + group(BannerDone) + { + Caption = ''; + Editable = false; + Visible = TopBannerVisible and CredentialsStepVisible; +#pragma warning disable AA0100 + field("MediaResourcesDone.""Media Reference"""; MediaResourcesDone."Media Reference") +#pragma warning restore AA0100 + { + ApplicationArea = Suite; + Editable = false; + ShowCaption = false; + } + } + group(Step1) + { + Visible = FirstStepVisible; + group("Welcome to Dynamics 365 Connection Setup") + { + Caption = 'Welcome to Dynamics 365 Field Service Connection Setup'; + group(Control23) + { + InstructionalText = 'You can set up a Dynamics 365 Field Service connection to enable seamless coupling of data.'; + ShowCaption = false; + } + group(Control21) + { + InstructionalText = 'Start by specifying the URL to your Dynamics 365 Field Service solution, such as https://mycrm.crm4.dynamics.com'; + ShowCaption = false; + } + field(ServerAddress; Rec."Server Address") + { + ApplicationArea = Suite; + Editable = ConnectionStringFieldsEditable; + ToolTip = 'Specifies the URL of the environment that hosts the Dynamics 365 Field Service solution that you want to connect to.'; + + trigger OnValidate() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.CheckModifyCRMConnectionURL(Rec."Server Address"); + end; + } + group(Control9) + { + InstructionalText = 'Once coupled, you can work with and synchronize data types that are common to both services, such as work order products, work order services, customer assets and bookable resources, and keep the data up-to-date in both locations.'; + ShowCaption = false; + } + } + } + group(Step2) + { + Caption = ''; + Visible = CredentialsStepVisible; + group("Step2.1") + { + Caption = ''; + InstructionalText = 'Specify the user that will be used for synchronization between the two services.'; + Visible = IsUserNamePasswordVisible; + field(Email; Rec."User Name") + { + ApplicationArea = Suite; + Caption = 'Email'; + ExtendedDatatype = EMail; + Editable = ConnectionStringFieldsEditable; + ToolTip = 'Specifies the user name of a Dynamics 365 Field Service account.'; + } + field(Password; Password) + { + ApplicationArea = Suite; + Caption = 'Password'; + ExtendedDatatype = Masked; + Editable = ConnectionStringFieldsEditable; + ToolTip = 'Specifies the password of a Dynamics 365 Field Service user account.'; + + trigger OnValidate() + begin + PasswordSet := true; + end; + } + } + group(Control22) + { + InstructionalText = 'This account must be a valid user in Dynamics 365 Field Service that does not have the System Administrator role.'; + ShowCaption = false; + Visible = IsUserNamePasswordVisible; + } + group(Control20) + { + InstructionalText = 'To enable the connection, fill out the settings below and choose Finish. You may be asked to specify an administrative user account in Dynamics 365 Field Service.'; + ShowCaption = false; + + field("Job Journal Template"; Rec."Job Journal Template") + { + 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.'; + } + 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.'; + } + field("Hour Unit of Measure"; Rec."Hour Unit of Measure") + { + ApplicationArea = Suite; + ShowMandatory = true; + ToolTip = 'Specifies the unit of measure that corresponds to the ''hour'' unit that is used on Dynamics 365 Field Service bookable resources.'; + } + field("Line Synch. Rule"; Rec."Line Synch. Rule") + { + ApplicationArea = Suite; + ToolTip = 'Specifies when to synchronize work order products and work order services.'; + } + 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.'; + } + } + group("Advanced Settings") + { + Caption = 'Advanced Settings'; + Visible = false; + field(ImportFSSolution; ImportSolution) + { + ApplicationArea = Suite; + Caption = 'Import Dynamics 365 Field Service Integration Solution'; + Enabled = ImportFSSolutionEnabled; + ToolTip = 'Specifies that the solution required for integration with Dynamics 365 Field Service will be imported.'; + } + field(EnableFSConnection; EnableFSConnection) + { + ApplicationArea = Suite; + Caption = 'Enable Dynamics 365 Field Service Connection'; + Enabled = EnableFSConnectionEnabled; + ToolTip = 'Specifies if the connection to Dynamics 365 Field Service will be enabled.'; + } + field(SDKVersion; Rec."Proxy Version") + { + ApplicationArea = Suite; + AssistEdit = true; + Caption = 'Dynamics 365 SDK Version'; + Editable = false; + ToolTip = 'Specifies the Microsoft Dynamics 365 (CRM) software development kit version that is used to connect to Dynamics 365 Field Service.'; + + trigger OnAssistEdit() + var + TempStack: Record TempStack temporary; + begin + if Page.RunModal(Page::"SDK Version List", TempStack) = Action::LookupOK then begin + Rec."Proxy Version" := TempStack.StackOrder; + CurrPage.Update(true); + end; + end; + } + } + } + } + } + + actions + { + area(processing) + { + action(ActionBack) + { + ApplicationArea = Basic, Suite; + Caption = 'Back'; + Enabled = BackActionEnabled; + Image = PreviousRecord; + InFooterBar = true; + + trigger OnAction() + begin + NextStep(true); + end; + } + action(ActionNext) + { + ApplicationArea = Basic, Suite; + Caption = 'Next'; + Enabled = NextActionEnabled; + Image = NextRecord; + InFooterBar = true; + + trigger OnAction() + begin + if (Step = Step::Start) and (Rec."Server Address" = '') then + Error(CRMURLShouldNotBeEmptyErr, CRMProductName.FSServiceName()); + NextStep(false); + end; + } + action(ActionAdvanced) + { + ApplicationArea = Basic, Suite; + Caption = 'Advanced'; + Image = Setup; + InFooterBar = true; + Visible = false; + + trigger OnAction() + begin + ShowAdvancedSettings := true; + AdvancedActionEnabled := false; + SimpleActionEnabled := true; + end; + } + action(ActionSimple) + { + ApplicationArea = Basic, Suite; + Caption = 'Simple'; + Image = Setup; + InFooterBar = true; + Visible = SimpleActionEnabled; + + trigger OnAction() + begin + ShowAdvancedSettings := false; + AdvancedActionEnabled := true; + SimpleActionEnabled := false; + end; + } + action(ActionFinish) + { + ApplicationArea = Basic, Suite; + Caption = 'Finish'; + Enabled = FinishActionEnabled; + Image = Approve; + InFooterBar = true; + + trigger OnAction() + var + FeatureTelemetry: Codeunit "Feature Telemetry"; + GuidedExperience: Codeunit "Guided Experience"; + begin + if Rec."Authentication Type" = Rec."Authentication Type"::Office365 then + if Rec."User Name" = '' then + Error(CRMSynchUserCredentialsNeededErr, CRMProductName.FSServiceName()); + + if not FinalizeSetup() then + exit; + Page.Run(Page::"FS Connection Setup"); + GuidedExperience.CompleteAssistedSetup(ObjectType::Page, Page::"FS Connection Setup Wizard"); + Commit(); + FeatureTelemetry.LogUptake('0000MBD', 'Dynamics 365 Field Service', Enum::"Feature Uptake Status"::"Set up"); + CurrPage.Close(); + end; + } + } + } + + trigger OnInit() + begin + LoadTopBanners(); + SetVisibilityFlags(); + end; + + trigger OnOpenPage() + var + FSConnectionSetup: Record "FS Connection Setup"; + CRMConnectionSetup: Record "CRM Connection Setup"; + FeatureTelemetry: Codeunit "Feature Telemetry"; + begin + FSConnectionSetup.EnsureCDSConnectionIsEnabled(); + FSConnectionSetup.EnsureCRMConnectionIsEnabled(); + CRMConnectionSetup.Get(); + FSConnectionSetup.LoadConnectionStringElementsFromCDSConnectionSetup(); + FeatureTelemetry.LogUptake('0000MBE', 'Dataverse', Enum::"Feature Uptake Status"::Discovered); + FeatureTelemetry.LogUptake('0000MBF', 'Dynamics 365 Field Service', Enum::"Feature Uptake Status"::Discovered); + + Rec.Init(); + if FSConnectionSetup.Get() then begin + Rec."Proxy Version" := FSConnectionSetup."Proxy Version"; + Rec."Authentication Type" := FSConnectionSetup."Authentication Type"; + Rec."Server Address" := FSConnectionSetup."Server Address"; + Rec."User Name" := FSConnectionSetup."User Name"; + Rec."User Password Key" := FSConnectionSetup."User Password Key"; + Rec."Job Journal Template" := FSConnectionSetup."Job Journal Template"; + Rec."Job Journal Batch" := FSConnectionSetup."Job Journal Batch"; + Rec."Hour Unit of Measure" := FSConnectionSetup."Hour Unit of Measure"; + Rec."Line Synch. Rule" := FSConnectionSetup."Line Synch. Rule"; + Rec."Line Post Rule" := FSConnectionSetup."Line Post Rule"; + if not FSConnectionSetup.GetPassword().IsEmpty() then + Password := '**********'; + ConnectionStringFieldsEditable := false; + end else begin + InitializeDefaultAuthenticationType(); + InitializeDefaultProxyVersion(); + end; + Rec.Insert(); + Step := Step::Start; + EnableControls(); + end; + + trigger OnQueryClosePage(CloseAction: Action): Boolean + var + GuidedExperience: Codeunit "Guided Experience"; + begin + if CloseAction = Action::OK then + if GuidedExperience.AssistedSetupExistsAndIsNotComplete(ObjectType::Page, Page::"FS Connection Setup Wizard") then + if not Confirm(ConnectionNotSetUpQst, false, CRMProductName.FSServiceName()) then + Error(''); + end; + + var + MediaRepositoryStandard: Record "Media Repository"; + MediaRepositoryDone: Record "Media Repository"; + MediaResourcesStandard: Record "Media Resources"; + MediaResourcesDone: Record "Media Resources"; + CRMProductName: Codeunit "CRM Product Name"; + ClientTypeManagement: Codeunit "Client Type Management"; + Step: Option Start,Credentials,Finish; + TopBannerVisible: Boolean; + ConnectionStringFieldsEditable: Boolean; + BackActionEnabled: Boolean; + NextActionEnabled: Boolean; + FinishActionEnabled: Boolean; + FirstStepVisible: Boolean; + CredentialsStepVisible: Boolean; + EnableFSConnection: Boolean; + ImportSolution: Boolean; + EnableFSConnectionEnabled: Boolean; + ImportFSSolutionEnabled: Boolean; + ShowAdvancedSettings: Boolean; + AdvancedActionEnabled: Boolean; + SimpleActionEnabled: Boolean; + IsUserNamePasswordVisible: Boolean; + PasswordSet: Boolean; + [NonDebuggable] + Password: Text; + 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'; + Office365AuthTxt: Label 'AuthType=Office365', Locked = true; + + local procedure LoadTopBanners() + begin + if MediaRepositoryStandard.Get('AssistedSetup-NoText-120px.png', Format(ClientTypeManagement.GetCurrentClientType())) and + MediaRepositoryDone.Get('AssistedSetupDone-NoText-120px.png', Format(ClientTypeManagement.GetCurrentClientType())) + then + if MediaResourcesStandard.Get(MediaRepositoryStandard."Media Resources Ref") and + MediaResourcesDone.Get(MediaRepositoryDone."Media Resources Ref") + then + TopBannerVisible := MediaResourcesDone."Media Reference".HasValue; + end; + + local procedure SetVisibilityFlags() + var + CDSConnectionSetup: Record "CDS Connection Setup"; + begin + IsUserNamePasswordVisible := true; + + if CDSConnectionSetup.Get() then + if CDSConnectionSetup."Authentication Type" = CDSConnectionSetup."Authentication Type"::Office365 then + if not CDSConnectionSetup."Connection String".Contains(Office365AuthTxt) then + IsUserNamePasswordVisible := false; + end; + + local procedure NextStep(Backward: Boolean) + begin + if Backward then + Step := Step - 1 + else + Step := Step + 1; + + EnableControls(); + end; + + local procedure ResetControls() + begin + BackActionEnabled := false; + NextActionEnabled := false; + FinishActionEnabled := false; + AdvancedActionEnabled := false; + + FirstStepVisible := false; + CredentialsStepVisible := false; + + ImportFSSolutionEnabled := true; + end; + + local procedure EnableControls() + begin + ResetControls(); + + case Step of + Step::Start: + ShowStartStep(); + Step::Credentials: + ShowFinishStep(); + end; + end; + + local procedure ShowStartStep() + begin + BackActionEnabled := false; + NextActionEnabled := true; + FinishActionEnabled := false; + FirstStepVisible := true; + AdvancedActionEnabled := false; + SimpleActionEnabled := false; + end; + + local procedure ShowFinishStep() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + BackActionEnabled := true; + NextActionEnabled := false; + AdvancedActionEnabled := not ShowAdvancedSettings; + SimpleActionEnabled := not AdvancedActionEnabled; + CredentialsStepVisible := true; + FinishActionEnabled := true; + + EnableFSConnectionEnabled := Rec."Server Address" <> ''; + Rec."Authentication Type" := Rec."Authentication Type"::Office365; + if FSConnectionSetup.Get() then begin + EnableFSConnection := true; + EnableFSConnectionEnabled := not FSConnectionSetup."Is Enabled"; + ImportSolution := true; + if FSConnectionSetup."Is FS Solution Installed" then + ImportFSSolutionEnabled := false; + end else begin + if ImportFSSolutionEnabled then + ImportSolution := true; + if EnableFSConnectionEnabled then + EnableFSConnection := true; + end; + end; + + local procedure FinalizeSetup(): Boolean + var + FSConnectionSetup: Record "FS Connection Setup"; + FSIntegrationMgt: Codeunit "FS Integration Mgt."; + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + AdminEmail: Text; + AdminPassword: SecretText; + AccessToken: SecretText; + AdminADDomain: Text; + ImportSolutionFailed: Boolean; + begin + if ImportSolution and ImportFSSolutionEnabled then begin + case Rec."Authentication Type" of + Rec."Authentication Type"::Office365: + CDSIntegrationImpl.GetAccessToken(Rec."Server Address", true, AccessToken); + Rec."Authentication Type"::AD: + if not Rec.PromptForCredentials(AdminEmail, AdminPassword, AdminADDomain) then + exit(false); + else + if not Rec.PromptForCredentials(AdminEmail, AdminPassword) then + exit(false); + end; + FSIntegrationMgt.ImportFSSolution(Rec."Server Address", Rec."User Name", AdminEmail, AdminPassword, AccessToken, AdminADDomain, Rec."Proxy Version", true, ImportSolutionFailed); + end; + if PasswordSet then + FSConnectionSetup.UpdateFromWizard(Rec, Password) + else + FSConnectionSetup.UpdateFromWizard(Rec, FSConnectionSetup.GetPassword()); + + if EnableFSConnection then + FSConnectionSetup.EnableFSConnectionFromWizard(); + exit(true); + end; + + local procedure InitializeDefaultAuthenticationType() + begin + Rec.Validate("Authentication Type", Rec."Authentication Type"::Office365); + end; + + local procedure InitializeDefaultProxyVersion() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + Rec.Validate("Proxy Version", CRMIntegrationManagement.GetLastProxyVersionItem()); + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSCustomerAssetList.Page.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSCustomerAssetList.Page.al new file mode 100644 index 0000000000..f24dc2b353 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSCustomerAssetList.Page.al @@ -0,0 +1,184 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Item; +using Microsoft.Integration.D365Sales; +using System.Environment.Configuration; + +page 6611 "FS Customer Asset List" +{ + ApplicationArea = Suite; + Caption = 'Customer Assets - Dynamics 365 Field Service'; + Editable = false; + PageType = List; + SourceTable = "FS Customer Asset"; + SourceTableView = sorting(Name); + UsageCategory = Lists; + + layout + { + area(content) + { + repeater(Control2) + { + ShowCaption = false; + field(Name; Rec.Name) + { + ApplicationArea = Suite; + Caption = 'Name'; + StyleExpr = FirstColumnStyle; + ToolTip = 'Specifies the customer asset name.'; + } + field(AssetTag; Rec.AssetTag) + { + ApplicationArea = Suite; + Caption = 'Asset Tag'; + ToolTip = 'Specifies the customer asset tag.'; + } + field(CustomerAssetCategoryName; Rec.CustomerAssetCategoryName) + { + ApplicationArea = Suite; + Caption = 'Category'; + ToolTip = 'Specifies the customer asset category name. '; + } + field(CustomerName; CustomerName) + { + ApplicationArea = Suite; + Caption = 'Category'; + ToolTip = 'Specifies the name of the customer. '; + } + field(Coupled; Coupled) + { + ApplicationArea = Suite; + Caption = 'Coupled'; + ToolTip = 'Specifies if the Dynamics 365 Field Service record is coupled to Business Central.'; + } + } + } + } + + actions + { + area(processing) + { + action(CreateFromFS) + { + ApplicationArea = Suite; + Caption = 'Create in Business Central'; + Image = NewItemNonStock; + ToolTip = 'Generate the entity from the Field Service customer asset.'; + Visible = ShowCreateInBC; + + trigger OnAction() + var + FSCustomerAsset: Record "FS Customer Asset"; + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CurrPage.SetSelectionFilter(FSCustomerAsset); + CRMIntegrationManagement.CreateNewRecordsFromSelectedCRMRecords(FSCustomerAsset); + 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 OnAfterGetRecord() + var + CRMIntegrationRecord: Record "CRM Integration Record"; + CRMAccount: Record "CRM Account"; + RecordID: RecordID; + begin + if CRMIntegrationRecord.FindRecordIDFromID(Rec.CustomerAssetId, Database::"Service Item", RecordID) then + if CurrentlyCoupledFSCustomerAsset.CustomerAssetId = Rec.CustomerAssetId 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; + if CRMAccount.Get(Rec.Account) then + CustomerName := CRMAccount.Name + else + CustomerName := ''; + end; + + 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 Customer Asset")); + Rec.FilterGroup(0); + ShowCreateInBC := ApplicationAreaMgmtFacade.IsPremiumExperienceEnabled(); + end; + + var + CurrentlyCoupledFSCustomerAsset: Record "FS Customer Asset"; + Coupled: Text; + FirstColumnStyle: Text; + CustomerName: Text; + ShowCreateInBC: Boolean; + + procedure SetCurrentlyCoupledFSCustomerAsset(FSCustomerAsset: Record "FS Customer Asset") + begin + CurrentlyCoupledFSCustomerAsset := FSCustomerAsset; + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Pages/FSLocationList.PageExt.al b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSLocationList.PageExt.al new file mode 100644 index 0000000000..2a0edda4f8 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Pages/FSLocationList.PageExt.al @@ -0,0 +1,172 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Inventory.Location; +using Microsoft.Integration.Dataverse; + +pageextension 6623 "FS Location List" extends "Location List" +{ + layout + { + addlast(Control1) + { + field("Coupled to FS"; Rec."Coupled to FS") + { + ApplicationArea = Jobs; + Visible = FSIntegrationEnabled; + ToolTip = 'Specifies if the project task is coupled to an entity in Field Service.'; + } + } + } + + actions + { + addafter("&Location") + { + group(ActionFS) + { + Caption = 'Dynamics 365 Field Service'; + Enabled = FSIntegrationEnabled; + + action(GoToProductFS) + { + ApplicationArea = Suite; + Caption = 'Location in Field Service'; + Image = CoupledItem; + ToolTip = 'Open the coupled Dynamics 365 Field Service entity.'; + + 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 + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.UpdateOneNow(Rec.RecordId); + 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 product.'; + + trigger OnAction() + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + begin + CRMIntegrationManagement.DefineCoupling(Rec.RecordId); + 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 entity.'; + + trigger OnAction() + var + CRMCouplingManagement: Codeunit "CRM Coupling Management"; + begin + CRMCouplingManagement.RemoveCoupling(Rec.RecordId); + end; + } + } + action(FSShowLog) + { + ApplicationArea = Suite; + Caption = 'Synchronization Log'; + Image = Log; + ToolTip = 'View integration synchronization jobs for the location 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(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; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365AUTOMATION.PermissionSetExt.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365AUTOMATION.PermissionSetExt.al new file mode 100644 index 0000000000..50c5775fa2 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365AUTOMATION.PermissionSetExt.al @@ -0,0 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Security.AccessControl; + +permissionsetextension 6610 "FS D365 AUTOMATION" extends "D365 AUTOMATION" +{ + IncludedPermissionSets = "FS - Edit"; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMMGT.PermissionSetExt.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMMGT.PermissionSetExt.al new file mode 100644 index 0000000000..46dfc933c1 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMMGT.PermissionSetExt.al @@ -0,0 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Security.AccessControl; + +permissionsetextension 6611 "FS D365 DYN CRM MGT" extends "D365 DYN CRM MGT" +{ + IncludedPermissionSets = "FS - Edit"; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMREAD.PermissionSetExt.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMREAD.PermissionSetExt.al new file mode 100644 index 0000000000..631d437e20 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365DYNCRMREAD.PermissionSetExt.al @@ -0,0 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Security.AccessControl; + +permissionsetextension 6612 "FS D365 DYN CRM READ" extends "D365 DYN CRM READ" +{ + IncludedPermissionSets = "FS - Read"; +} diff --git a/Apps/W1/ReportLayouts/app/Entitlements/ReportLayouts.entitlement.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365READ.PermissionSetExt.al similarity index 71% rename from Apps/W1/ReportLayouts/app/Entitlements/ReportLayouts.entitlement.al rename to Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365READ.PermissionSetExt.al index 0db8675068..b6665799c3 100644 --- a/Apps/W1/ReportLayouts/app/Entitlements/ReportLayouts.entitlement.al +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365READ.PermissionSetExt.al @@ -2,12 +2,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ - -namespace Microsoft.Shared.Report; +namespace Microsoft.Integration.DynamicsFieldService; using System.Security.AccessControl; -entitlement "ReportLayouts" + +permissionsetextension 6613 "FS D365 READ" extends "D365 READ" { - Type = Implicit; - ObjectEntitlements = "Report Layouts - Objects"; -} \ No newline at end of file + IncludedPermissionSets = "FS - Read"; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365TEAMMEMBER.PermissionSetExt.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365TEAMMEMBER.PermissionSetExt.al new file mode 100644 index 0000000000..fa5cd132bb --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSD365TEAMMEMBER.PermissionSetExt.al @@ -0,0 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Security.AccessControl; + +permissionsetextension 6614 "FS D365 TEAM MEMBER" extends "D365 TEAM MEMBER" +{ + IncludedPermissionSets = "FS - Edit"; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al new file mode 100644 index 0000000000..70b9c6083f --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSEdit.PermissionSet.al @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------ +// 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; + +permissionset 6610 "FS - Edit" +{ + Access = Internal; + Assignable = false; + Caption = 'Field Service - Edit'; + + IncludedPermissionSets = "FS - Read"; + + Permissions = tabledata "FS Connection Setup" = IMD, + tabledata "FS Bookable Resource" = IMD, + tabledata "FS Bookable Resource Booking" = IMD, + tabledata "FS BookableResourceBookingHdr" = IMD, + tabledata "FS Customer Asset" = IMD, + tabledata "FS Customer Asset Category" = IMD, + tabledata "FS Project Task" = IMD, + tabledata "FS Resource Pay Type" = IMD, + tabledata "FS Warehouse" = IMD, + tabledata "FS Work Order" = IMD, + tabledata "FS Work Order Incident" = IMD, + tabledata "FS Work Order Product" = IMD, + tabledata "FS Work Order Service" = IMD, + tabledata "FS Work Order Substatus" = IMD, + tabledata "FS Work Order Type" = IMD; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSINTELLIGENTCLOUD.PermissionSetExt.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSINTELLIGENTCLOUD.PermissionSetExt.al new file mode 100644 index 0000000000..c15d7a96b6 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSINTELLIGENTCLOUD.PermissionSetExt.al @@ -0,0 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// 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 System.Security.AccessControl; + +permissionsetextension 6615 "FS INTELLIGENT CLOUD" extends "INTELLIGENT CLOUD" +{ + IncludedPermissionSets = "FS - Read"; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al new file mode 100644 index 0000000000..588a11a6be --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSObjects.PermissionSet.al @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------------------------------ +// 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; + +permissionset 6612 "FS - Objects" +{ + Access = Public; + Assignable = false; + Caption = 'Field Service - Objects'; + + Permissions = 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 Lookup FS Tables" = X, + codeunit "FS Setup Defaults" = X, + page "FS Bookable Resource List" = X, + page "FS Connection Setup" = X, + page "FS Connection Setup Wizard" = X, + page "FS Customer Asset List" = X, + table "FS Bookable Resource" = X, + table "FS Bookable Resource Booking" = X, + table "FS BookableResourceBookingHdr" = X, + table "FS Connection Setup" = X, + table "FS Customer Asset" = X, + table "FS Customer Asset Category" = X, + table "FS Project Task" = X, + table "FS Resource Pay Type" = X, + table "FS Warehouse" = X, + table "FS Work Order" = X, + table "FS Work Order Incident" = X, + table "FS Work Order Product" = X, + table "FS Work Order Service" = X, + table "FS Work Order Substatus" = X, + table "FS Work Order Type" = X; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al new file mode 100644 index 0000000000..1f4dda8f14 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Permissions/FSRead.PermissionSet.al @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------ +// 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; + +permissionset 6611 "FS - Read" +{ + Access = Internal; + Assignable = false; + Caption = 'Field Service - Read'; + + IncludedPermissionSets = "FS - Objects"; + + Permissions = tabledata "FS Connection Setup" = R, + tabledata "FS Bookable Resource" = R, + tabledata "FS Bookable Resource Booking" = R, + tabledata "FS BookableResourceBookingHdr" = R, + tabledata "FS Customer Asset" = R, + tabledata "FS Customer Asset Category" = R, + tabledata "FS Project Task" = R, + tabledata "FS Resource Pay Type" = R, + tabledata "FS Warehouse" = R, + tabledata "FS Work Order" = R, + tabledata "FS Work Order Incident" = R, + tabledata "FS Work Order Product" = R, + tabledata "FS Work Order Service" = R, + tabledata "FS Work Order Substatus" = R, + tabledata "FS Work Order Type" = R; +} diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJob.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJob.TableExt.al new file mode 100644 index 0000000000..d2027dc68f --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJob.TableExt.al @@ -0,0 +1,69 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.Project.Job; +using Microsoft.Integration.SyncEngine; + +tableextension 6610 "FS Job" extends Job +{ + fields + { + modify(Blocked) + { + trigger OnAfterValidate() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + if Rec.Blocked <> Rec.Blocked::" " then + if FSConnectionSetup.IsEnabled() then + MoveFilterOnProjectTaskMapping(); + end; + } + modify("Apply Usage Link") + { + trigger OnAfterValidate() + var + FSConnectionSetup: Record "FS Connection Setup"; + begin + if FSConnectionSetup.IsEnabled() then + MoveFilterOnProjectTaskMapping(); + end; + } + } + + local procedure MoveFilterOnProjectTaskMapping() + var + IntegrationTableMapping: Record "Integration Table Mapping"; + JobTask: Record "Job Task"; + begin + if Rec.Blocked <> Rec.Blocked::" " then + exit; + + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + IntegrationTableMapping.SetRange("Table ID", Database::"Job Task"); + IntegrationTableMapping.SetRange("Integration Table ID", Database::"FS Project Task"); + if not IntegrationTableMapping.FindFirst() then + exit; + + JobTask.SetRange("Job No.", Rec."No."); + JobTask.SetCurrentKey(SystemCreatedAt); + JobTask.SetAscending(SystemCreatedAt, true); + if not JobTask.FindFirst() then + exit; + + if JobTask.SystemCreatedAt = 0DT then begin + IntegrationTableMapping."Synch. Int. Tbl. Mod. On Fltr." := 0DT; + IntegrationTableMapping.Modify(); + exit; + end; + + if IntegrationTableMapping."Synch. Int. Tbl. Mod. On Fltr." > JobTask.SystemCreatedAt then begin + IntegrationTableMapping."Synch. Int. Tbl. Mod. On Fltr." := JobTask.SystemCreatedAt; + IntegrationTableMapping.Modify(); + end; + end; +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobCue.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobCue.TableExt.al new file mode 100644 index 0000000000..abd3f3865e --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobCue.TableExt.al @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Projects.RoleCenters; +using Microsoft.Integration.SyncEngine; + +tableextension 6613 "FS Job Cue" extends "Job Cue" +{ + fields + { + field(12000; "Coupled Data Sync Errors"; Integer) + { + CalcFormula = count("CRM Integration Record" where(Skipped = const(true))); + Caption = 'Coupled Data Synch Errors'; + FieldClass = FlowField; + } + field(12001; "FS Int. Errors"; Integer) + { + CalcFormula = count("Integration Synch. Job Errors"); + Caption = 'Field Service Integration Errors'; + FieldClass = FlowField; + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobTask.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobTask.TableExt.al new file mode 100644 index 0000000000..1e77f88101 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSJobTask.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.Integration.Dataverse; +using Microsoft.Projects.Project.Job; + +tableextension 6611 "FS Job Task" extends "Job Task" +{ + 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::"Job Task"))); + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSLocation.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSLocation.TableExt.al new file mode 100644 index 0000000000..9443bf8f91 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSLocation.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.Inventory.Location; +using Microsoft.Integration.Dataverse; + +tableextension 6614 "FS Location" extends Location +{ + 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::Location))); + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItem.TableExt.al b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItem.TableExt.al new file mode 100644 index 0000000000..52ebe79b1c --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Table Extensions/FSServiceItem.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.Integration.Dataverse; +using Microsoft.Service.Item; + +tableextension 6612 "FS Service Item" extends "Service Item" +{ + 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"))); + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResource.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResource.Table.al new file mode 100644 index 0000000000..013ade00d3 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResource.Table.al @@ -0,0 +1,501 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6610 "FS Bookable Resource" +{ + ExternalName = 'bookableresource'; + TableType = CRM; + Description = 'Resource that has capacity which can be allocated to work.'; + DataClassification = SystemMetadata; + + fields + { + field(1; BookableResourceId; GUID) + { + ExternalName = 'bookableresourceid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Unique identifier of the resource.'; + Caption = 'Bookable Resource'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who created the record.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who modified the record.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(15; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(20; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(21; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(22; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(24; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(25; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(26; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(27; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(28; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(29; Name; Text[100]) + { + ExternalName = 'name'; + ExternalType = 'String'; + Description = 'Type the name of the resource.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(30; ProcessId; GUID) + { + ExternalName = 'processid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Contains the id of the process associated with the entity.'; + Caption = 'Process Id'; + DataClassification = SystemMetadata; + } + field(31; StageId; GUID) + { + ExternalName = 'stageid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Contains the id of the stage where the entity is located.'; + Caption = '(Deprecated) Stage Id'; + DataClassification = SystemMetadata; + } + field(32; TraversedPath; Text[1250]) + { + ExternalName = 'traversedpath'; + ExternalType = 'String'; + Description = 'A comma separated list of string values representing the unique identifiers of stages in a Business Process Flow Instance in the order that they occur.'; + Caption = '(Deprecated) Traversed Path'; + DataClassification = SystemMetadata; + } + field(33; AccountId; GUID) + { + ExternalName = 'accountid'; + ExternalType = 'Lookup'; + Description = 'Select the account that represents this resource.'; + Caption = 'Account'; + TableRelation = "CRM Account".AccountId; + DataClassification = SystemMetadata; + } + field(35; ContactId; GUID) + { + ExternalName = 'contactid'; + ExternalType = 'Lookup'; + Description = 'Select the contact that represents this resource.'; + Caption = 'Contact'; + TableRelation = "CRM Contact".ContactId; + DataClassification = SystemMetadata; + } + field(36; ResourceType; Option) + { + ExternalName = 'resourcetype'; + ExternalType = 'Picklist'; + ExternalAccess = Insert; + Description = 'Select whether the resource is a user, equipment, contact, account, generic resource or a group of resources.'; + Caption = 'Resource Type'; + InitValue = User; + OptionMembers = Generic,Contact,User,Equipment,Account,Crew,Facility,Pool; + OptionOrdinalValues = 1, 2, 3, 4, 5, 6, 7, 8; + DataClassification = SystemMetadata; + } + field(38; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Bookable Resource'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(40; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Bookable Resource'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(42; TimeZone; Integer) + { + ExternalName = 'timezone'; + ExternalType = 'Integer'; + Description = 'Specifies the timezone for the resource''s working hours.'; + Caption = 'Time Zone'; + DataClassification = SystemMetadata; + } + field(43; UserId; GUID) + { + ExternalName = 'userid'; + ExternalType = 'Lookup'; + ExternalAccess = Insert; + Description = 'Select the user who represents this resource.'; + Caption = 'User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(44; ExchangeRate; Decimal) + { + ExternalName = 'exchangerate'; + ExternalType = 'Decimal'; + ExternalAccess = Read; + Description = 'Exchange rate for the currency associated with the bookableresource with respect to the base currency.'; + Caption = 'ExchangeRate'; + DataClassification = SystemMetadata; + } + field(45; TransactionCurrencyId; GUID) + { + ExternalName = 'transactioncurrencyid'; + ExternalType = 'Lookup'; + Description = 'Exchange rate for the currency associated with the BookableResource with respect to the base currency.'; + Caption = 'Currency'; + TableRelation = "CRM Transactioncurrency".TransactionCurrencyId; + DataClassification = SystemMetadata; + } + field(56; DeriveCapacity; Boolean) + { + ExternalName = 'msdyn_derivecapacity'; + ExternalType = 'Boolean'; + Description = ''; + Caption = 'Derive Capacity From Group Members'; + DataClassification = SystemMetadata; + } + field(58; DisplayOnScheduleAssistant; Boolean) + { + ExternalName = 'msdyn_displayonscheduleassistant'; + ExternalType = 'Boolean'; + Description = 'Specify if this resource should be enabled for availablity search.'; + Caption = 'Enable for Availability Search'; + DataClassification = SystemMetadata; + } + field(60; DisplayOnScheduleBoard; Boolean) + { + ExternalName = 'msdyn_displayonscheduleboard'; + ExternalType = 'Boolean'; + Description = 'Specify if this resource should be displayed on the schedule board.'; + Caption = 'Display On Schedule Board'; + DataClassification = SystemMetadata; + } + field(62; EndLocation; Option) + { + ExternalName = 'msdyn_endlocation'; + ExternalType = 'Picklist'; + Description = 'Shows the default ending location type when booking daily schedules for this resource.'; + Caption = 'End Location'; + InitValue = LocationAgnostic; + OptionMembers = LocationAgnostic,ResourceAddress,OrganizationalUnitAddress; + OptionOrdinalValues = 690970002, 690970000, 690970001; + DataClassification = SystemMetadata; + } + field(64; GenericType; Option) + { + ExternalName = 'msdyn_generictype'; + ExternalType = 'Picklist'; + Description = ''; + Caption = 'Generic Type (Deprecated)'; + InitValue = " "; + OptionMembers = " ",ServiceCenter; + OptionOrdinalValues = -1, 690970000; + DataClassification = SystemMetadata; + } + field(67; PrimaryEMail; Text[100]) + { + ExternalName = 'msdyn_primaryemail'; + ExternalType = 'String'; + Description = ''; + Caption = 'Primary Email'; + DataClassification = SystemMetadata; + } + field(68; StartLocation; Option) + { + ExternalName = 'msdyn_startlocation'; + ExternalType = 'Picklist'; + Description = 'Shows the default starting location type when booking daily schedules for this resource.'; + Caption = 'Start Location'; + InitValue = LocationAgnostic; + OptionMembers = LocationAgnostic,ResourceAddress,OrganizationalUnitAddress; + OptionOrdinalValues = 690970002, 690970000, 690970001; + DataClassification = SystemMetadata; + } + field(70; TargetUtilization; Integer) + { + ExternalName = 'msdyn_targetutilization'; + ExternalType = 'Integer'; + Description = 'Shows the target utilization for the resource.'; + Caption = 'Target Utilization'; + DataClassification = SystemMetadata; + } + field(72; EnableAppointments; Option) + { + ExternalName = 'msdyn_enableappointments'; + ExternalType = 'Picklist'; + Description = 'Enable appointments to display on the new schedule board and be considered in availability search for resources.'; + Caption = 'Include Appointments'; + InitValue = Yes; + OptionMembers = No,Yes; + OptionOrdinalValues = 192350000, 192350001; + DataClassification = SystemMetadata; + } + field(74; EnableOutlookSchedules; Option) + { + ExternalName = 'msdyn_enableoutlookschedules'; + ExternalType = 'Picklist'; + Description = 'This only applies when directly calling the API. It does not apply when the Book button is clicked on the Schedule Board or on any schedulable entity.'; + Caption = 'Include Outlook Free/Busy in Search Resource Availability API'; + InitValue = Yes; + OptionMembers = No,Yes; + OptionOrdinalValues = 192350000, 192350001; + DataClassification = SystemMetadata; + } + field(76; BookingsToDrip; Integer) + { + ExternalName = 'msdyn_bookingstodrip'; + ExternalType = 'Integer'; + Description = 'The number of bookings to drip on the Mobile . This field is disabled/enabled based on Enable Drip Scheduling field'; + Caption = 'Bookings To Drip'; + DataClassification = SystemMetadata; + } + field(77; EnabledForFieldServiceMobile; Boolean) + { + ExternalName = 'msdyn_enabledforfieldservicemobile'; + ExternalType = 'Boolean'; + Description = 'Set this field to Yes if this resource requires access to the legacy Field Service Mobile application.'; + Caption = 'Enable for Field Service Mobile (legacy Xamarin app)'; + DataClassification = SystemMetadata; + } + field(79; EnableDripScheduling; Boolean) + { + ExternalName = 'msdyn_enabledripscheduling'; + ExternalType = 'Boolean'; + Description = 'Enables drip scheduling on the mobile app.'; + Caption = 'Enable Drip Scheduling'; + DataClassification = SystemMetadata; + } + field(81; HourlyRate; Decimal) + { + ExternalName = 'msdyn_hourlyrate'; + ExternalType = 'Money'; + Description = ''; + Caption = 'Hourly Rate'; + DataClassification = SystemMetadata; + } + field(82; HourlyRate_Base; Decimal) + { + ExternalName = 'msdyn_hourlyrate_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Value of the Hourly Rate in base currency.'; + Caption = 'Hourly Rate (Base)'; + DataClassification = SystemMetadata; + } + field(83; TimeOffApprovalRequired; Boolean) + { + ExternalName = 'msdyn_timeoffapprovalrequired'; + ExternalType = 'Boolean'; + Description = 'Specifies if approval required for Time Off Requests.'; + Caption = 'Time Off Approval Required'; + DataClassification = SystemMetadata; + } + field(87; CrewStrategy; Option) + { + ExternalName = 'msdyn_crewstrategy'; + ExternalType = 'Picklist'; + Description = 'Crew Strategy'; + Caption = 'Crew Strategy'; + InitValue = " "; + OptionMembers = " ",CrewLeaderManagement,"CrewMemberSelf-Management","CascadeAndAcceptCascadeCompletely(NotRecommended)"; + OptionOrdinalValues = -1, 192350001, 192350002, 192350000; + DataClassification = SystemMetadata; + } + field(89; InternalFlags; BLOB) + { + ExternalName = 'msdyn_internalflags'; + ExternalType = 'Memo'; + Description = 'For internal use only.'; + Caption = 'Internal Flags'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(90; Latitude; Decimal) + { + ExternalName = 'msdyn_latitude'; + ExternalType = 'Double'; + Description = 'The location latitude.'; + Caption = 'Latitude'; + DataClassification = SystemMetadata; + } + field(91; Longitude; Decimal) + { + ExternalName = 'msdyn_longitude'; + ExternalType = 'Double'; + Description = 'The location longitude.'; + Caption = 'Longitude'; + DataClassification = SystemMetadata; + } + field(92; LocationTimestamp; Datetime) + { + ExternalName = 'msdyn_locationtimestamp'; + ExternalType = 'DateTime'; + Description = 'The location timestamp.'; + Caption = 'Location Timestamp'; + DataClassification = SystemMetadata; + } + field(93; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; BookableResourceId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al new file mode 100644 index 0000000000..f566b60040 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBooking.Table.al @@ -0,0 +1,636 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6611 "FS Bookable Resource Booking" +{ + ExternalName = 'bookableresourcebooking'; + TableType = CRM; + Description = 'Represents the line details of a resource booking.'; + DataClassification = SystemMetadata; + + fields + { + field(1; BookableResourceBookingId; GUID) + { + ExternalName = 'bookableresourcebookingid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Unique identifier of the resource booking.'; + Caption = 'Bookable Resource Booking'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who created the record.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who modified the record.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(15; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(20; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(21; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(22; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(24; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(25; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(26; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(27; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(28; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(29; Name; Text[100]) + { + ExternalName = 'name'; + ExternalType = 'String'; + Description = 'Type a name for the booking.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(30; ProcessId; GUID) + { + ExternalName = 'processid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Contains the id of the process associated with the entity.'; + Caption = 'Process Id'; + DataClassification = SystemMetadata; + } + field(31; StageId; GUID) + { + ExternalName = 'stageid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Contains the id of the stage where the entity is located.'; + Caption = '(Deprecated) Stage Id'; + DataClassification = SystemMetadata; + } + field(32; TraversedPath; Text[1250]) + { + ExternalName = 'traversedpath'; + ExternalType = 'String'; + Description = 'A comma separated list of string values representing the unique identifiers of stages in a Business Process Flow Instance in the order that they occur.'; + Caption = '(Deprecated) Traversed Path'; + DataClassification = SystemMetadata; + } + field(34; BookingType; Option) + { + ExternalName = 'bookingtype'; + ExternalType = 'Picklist'; + Description = 'Select whether the booking is solid or liquid. Solid bookings are firm and cannot be changed whereas liquid bookings can be changed.'; + Caption = 'Booking Type'; + InitValue = Solid; + OptionMembers = Liquid,Solid; + OptionOrdinalValues = 2, 1; + DataClassification = SystemMetadata; + } + field(36; Duration; Integer) + { + ExternalName = 'duration'; + ExternalType = 'Integer'; + Description = 'Enter the duration of the booking.'; + Caption = 'Duration'; + DataClassification = SystemMetadata; + } + field(37; EndTime; Datetime) + { + ExternalName = 'endtime'; + ExternalType = 'DateTime'; + Description = 'Enter the end date and time of the booking.'; + Caption = 'End Time'; + DataClassification = SystemMetadata; + } + field(38; Header; GUID) + { + ExternalName = 'header'; + ExternalType = 'Lookup'; + Description = 'Shows the reference to the booking header record that represents the summary of bookings.'; + Caption = 'Header'; + TableRelation = "FS BookableResourceBookingHdr".BookableResourceBookingHeaderId; + DataClassification = SystemMetadata; + } + field(39; Resource; GUID) + { + ExternalName = 'resource'; + ExternalType = 'Lookup'; + Description = 'Shows the resource that is booked.'; + Caption = 'Resource'; + TableRelation = "FS Bookable Resource".BookableResourceId; + DataClassification = SystemMetadata; + } + field(40; StartTime; Datetime) + { + ExternalName = 'starttime'; + ExternalType = 'DateTime'; + Description = 'Enter the start date and time of the booking.'; + Caption = 'Start Time'; + DataClassification = SystemMetadata; + } + field(41; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Bookable Resource Booking'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(43; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Bookable Resource Booking'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(45; ExchangeRate; Decimal) + { + ExternalName = 'exchangerate'; + ExternalType = 'Decimal'; + ExternalAccess = Read; + Description = 'Exchange rate for the currency associated with the bookableresourcebooking with respect to the base currency.'; + Caption = 'ExchangeRate'; + DataClassification = SystemMetadata; + } + field(46; TransactionCurrencyId; GUID) + { + ExternalName = 'transactioncurrencyid'; + ExternalType = 'Lookup'; + Description = 'Exchange rate for the currency associated with the BookableResourceBooking with respect to the base currency.'; + Caption = 'Currency'; + TableRelation = "CRM Transactioncurrency".TransactionCurrencyId; + DataClassification = SystemMetadata; + } + field(47; ResourceName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource".Name where(BookableResourceId = field(Resource))); + ExternalName = 'resourcename'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(48; HeaderName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS BookableResourceBookingHdr".Name where(BookableResourceBookingHeaderId = field(Header))); + ExternalName = 'headername'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(51; ActualArrivalTime; Datetime) + { + ExternalName = 'msdyn_actualarrivaltime'; + ExternalType = 'DateTime'; + Description = 'Shows the time that work started.'; + Caption = 'Actual Arrival Time'; + DataClassification = SystemMetadata; + } + field(52; ActualTravelDuration; Integer) + { + ExternalName = 'msdyn_actualtravelduration'; + ExternalType = 'Integer'; + Description = 'Shows the total travel duration. Calculated based on the difference between the Bookable Resource Booking''s start time and actual arrival time.'; + Caption = 'Actual Travel Duration'; + DataClassification = SystemMetadata; + } + field(53; AllowOverlapping; Boolean) + { + ExternalName = 'msdyn_allowoverlapping'; + ExternalType = 'Boolean'; + Description = 'Allow the time of this booking to be displayed on the schedule assistant as available.'; + Caption = 'Allow Overlapping'; + DataClassification = SystemMetadata; + } + field(56; BookingMethod; Option) + { + ExternalName = 'msdyn_bookingmethod'; + ExternalType = 'Picklist'; + Description = 'Shows the method used to create this booking.'; + Caption = 'Booking Method'; + InitValue = Manual; + OptionMembers = ResourceSchedulingOptimization,"System-AgreementSchedule",ScheduleBoard,Mobile,Manual,ScheduleAssistant; + OptionOrdinalValues = 192350000, 690970005, 690970001, 690970002, 690970003, 690970004; + DataClassification = SystemMetadata; + } + field(59; CascadeCrewChanges; Boolean) + { + ExternalName = 'msdyn_cascadecrewchanges'; + ExternalType = 'Boolean'; + Description = 'Defines whether changing any of the following fields (Start Time, End Time, Status) should cascade the changes to other bookings on this requirement that have the same start and end time.'; + Caption = 'Cascade Crew Changes'; + DataClassification = SystemMetadata; + } + field(61; AcceptCascadeCrewChanges; Boolean) + { + ExternalName = 'msdyn_acceptcascadecrewchanges'; + ExternalType = 'Boolean'; + Description = 'Defines whether this booking accepts changes propagated as cascading changes'; + Caption = 'Accept Cascade Crew Changes'; + DataClassification = SystemMetadata; + } + field(63; effort; Decimal) + { + ExternalName = 'msdyn_effort'; + ExternalType = 'Decimal'; + Description = 'Capacity that needs to take from resource capacity'; + Caption = 'Capacity'; + DataClassification = SystemMetadata; + } + field(64; EstimatedArrivalTime; Datetime) + { + ExternalName = 'msdyn_estimatedarrivaltime'; + ExternalType = 'DateTime'; + Description = 'Estimated Arrival Time'; + Caption = 'Estimated Arrival Time'; + DataClassification = SystemMetadata; + } + field(65; EstimatedTravelDuration; Integer) + { + ExternalName = 'msdyn_estimatedtravelduration'; + ExternalType = 'Integer'; + Description = 'Estimated Travel Duration'; + Caption = 'Estimated Travel Duration'; + DataClassification = SystemMetadata; + } + field(66; URSInternalFlags; BLOB) + { + ExternalName = 'msdyn_ursinternalflags'; + ExternalType = 'Memo'; + Description = 'For internal use only.'; + Caption = 'Internal Flags'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(67; Latitude; Decimal) + { + ExternalName = 'msdyn_latitude'; + ExternalType = 'Double'; + Description = ''; + Caption = 'Latitude'; + DataClassification = SystemMetadata; + } + field(68; Longitude; Decimal) + { + ExternalName = 'msdyn_longitude'; + ExternalType = 'Double'; + Description = ''; + Caption = 'Longitude'; + DataClassification = SystemMetadata; + } + field(69; MilesTraveled; Decimal) + { + ExternalName = 'msdyn_milestraveled'; + ExternalType = 'Double'; + Description = 'In this field you can enter the total miles the resource drove to the job site'; + Caption = 'Miles Traveled'; + DataClassification = SystemMetadata; + } + field(71; ResourceGroup; GUID) + { + ExternalName = 'msdyn_resourcegroup'; + ExternalType = 'Lookup'; + Description = 'Unique identifier for Resource associated with Resource Booking'; + Caption = 'Resource Group'; + TableRelation = "FS Bookable Resource".BookableResourceId; + DataClassification = SystemMetadata; + } + field(74; WorkLocation; Option) + { + ExternalName = 'msdyn_worklocation'; + ExternalType = 'Picklist'; + Description = ''; + Caption = 'Work Location'; + InitValue = " "; + OptionMembers = " ",Onsite,Facility,LocationAgnostic; + OptionOrdinalValues = -1, 690970000, 690970001, 690970002; + DataClassification = SystemMetadata; + } + field(78; ResourceGroupName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource".Name where(BookableResourceId = field(ResourceGroup))); + ExternalName = 'msdyn_resourcegroupname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(82; BaseTravelDuration; Integer) + { + ExternalName = 'msdyn_basetravelduration'; + ExternalType = 'Integer'; + Description = 'The Base travel duration indicates the travel time without traffic'; + Caption = 'Base Travel Duration'; + DataClassification = SystemMetadata; + } + field(83; requirementgroupset; Text[40]) + { + ExternalName = 'msdyn_requirementgroupset'; + ExternalType = 'String'; + Description = 'Requirement Group Set'; + Caption = 'Requirement Group Set'; + DataClassification = SystemMetadata; + } + field(84; TravelTimeCalculationType; Option) + { + ExternalName = 'msdyn_traveltimecalculationtype'; + ExternalType = 'Picklist'; + Description = 'Travel Time Calculation'; + Caption = 'Travel Time Calculation'; + InitValue = BingMapsWithoutHistoricalTraffic; + OptionMembers = BingMapsWithoutHistoricalTraffic,BingMapsWithHistoricalTraffic,CustomMapProvider,Approximate; + OptionOrdinalValues = 192350000, 192350001, 192350002, 192350003; + DataClassification = SystemMetadata; + } + field(87; InternalFlags; BLOB) + { + ExternalName = 'msdyn_internalflags'; + ExternalType = 'Memo'; + Description = 'For internal use only.'; + Caption = 'Internal Flags'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(88; OfflineTimestamp; Datetime) + { + ExternalName = 'msdyn_offlinetimestamp'; + ExternalType = 'DateTime'; + Description = 'Internal Use. This field is used to capture the time when the Booking was updated on mobile offline.'; + Caption = 'Offline Timestamp'; + DataClassification = SystemMetadata; + } + field(89; PreventTimestampCreation; Boolean) + { + ExternalName = 'msdyn_preventtimestampcreation'; + ExternalType = 'Boolean'; + Description = 'Prevents time stamp creation if the time stamp was already created on a mobile device.'; + Caption = 'Prevent Timestamp Creation'; + DataClassification = SystemMetadata; + } + field(91; Signature; BLOB) + { + ExternalName = 'msdyn_signature'; + ExternalType = 'Memo'; + Description = 'This field is used for capturing signature on Mobile (using the Pen Control)'; + Caption = 'Signature'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(92; SlotText; BLOB) + { + ExternalName = 'msdyn_slottext'; + ExternalType = 'Memo'; + Description = 'Shows the automatically generated text of the time slot on the schedule board.'; + Caption = 'Slot Text'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(93; TotalBillableDuration; Integer) + { + ExternalName = 'msdyn_totalbillableduration'; + ExternalType = 'Integer'; + Description = 'Shows the total billable duration. If you leave this field blank the system automatically determines the billable duration by calculating the resource journal details.'; + Caption = 'Total Billable Duration'; + DataClassification = SystemMetadata; + } + field(94; TotalBreakDuration; Integer) + { + ExternalName = 'msdyn_totalbreakduration'; + ExternalType = 'Integer'; + Description = 'Shows the total break duration. If you leave this field blank the system automatically determines the break duration by calculating the resource journal details.'; + Caption = 'Total Break Duration'; + DataClassification = SystemMetadata; + } + field(95; TotalCost; Decimal) + { + ExternalName = 'msdyn_totalcost'; + ExternalType = 'Money'; + Description = 'Shows the total cost for this booking.'; + Caption = 'Total Cost'; + DataClassification = SystemMetadata; + } + field(96; totalcost_Base; Decimal) + { + ExternalName = 'msdyn_totalcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Value of the Total Cost in base currency.'; + Caption = 'Total Cost (Base)'; + DataClassification = SystemMetadata; + } + field(97; TotalDurationInProgress; Integer) + { + ExternalName = 'msdyn_totaldurationinprogress'; + ExternalType = 'Integer'; + Description = 'Shows the total duration that this booking was in progress.'; + Caption = 'Total Duration In Progress'; + DataClassification = SystemMetadata; + } + field(98; TravelTimeRescheduling; Boolean) + { + ExternalName = 'msdyn_traveltimerescheduling'; + ExternalType = 'Boolean'; + Description = ''; + Caption = 'Travel Time Rescheduling (Deprecated)'; + DataClassification = SystemMetadata; + } + field(100; WorkOrder; GUID) + { + ExternalName = 'msdyn_workorder'; + ExternalType = 'Lookup'; + Description = 'Unique identifier for Work Order associated with Resource Booking.'; + Caption = 'Work Order'; + TableRelation = "FS Work Order".WorkOrderId; + DataClassification = SystemMetadata; + } + field(102; WorkOrderName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order".Name where(WorkOrderId = field(WorkOrder))); + ExternalName = 'msdyn_workordername'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(103; Crew; GUID) + { + ExternalName = 'msdyn_crew'; + ExternalType = 'Lookup'; + Description = 'This field is populated by the Field Service solution to define to which crew a booking is connected.'; + Caption = 'Crew'; + TableRelation = "FS Bookable Resource".BookableResourceId; + DataClassification = SystemMetadata; + } + field(104; CrewMemberType; Option) + { + ExternalName = 'msdyn_crewmembertype'; + ExternalType = 'Picklist'; + Description = 'Crew Member Type'; + Caption = 'Crew Member Type'; + InitValue = " "; + OptionMembers = " ",Leader,Member,None; + OptionOrdinalValues = -1, 192350000, 192350001, 192350002; + DataClassification = SystemMetadata; + } + field(106; QuickNoteAction; Option) + { + ExternalName = 'msdyn_quickNoteAction'; + ExternalType = 'Picklist'; + Description = 'Internal For Quick note pcf control actions'; + Caption = 'Quick note actions'; + InitValue = None; + OptionMembers = None,Text,Photo,Video,Audio,File; + OptionOrdinalValues = 100000000, 100000001, 100000002, 100000003, 100000004, 100000005; + DataClassification = SystemMetadata; + } + field(108; CrewName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource".Name where(BookableResourceId = field(Crew))); + ExternalName = 'msdyn_crewname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + } + keys + { + key(PK; BookableResourceBookingId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBookingHdr.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBookingHdr.Table.al new file mode 100644 index 0000000000..8a12e8c01c --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSBookableResourceBookingHdr.Table.al @@ -0,0 +1,308 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6612 "FS BookableResourceBookingHdr" +{ + ExternalName = 'bookableresourcebookingheader'; + TableType = CRM; + Description = 'Reservation entity representing the summary of the associated resource bookings.'; + DataClassification = SystemMetadata; + + fields + { + field(1; BookableResourceBookingHeaderId; GUID) + { + ExternalName = 'bookableresourcebookingheaderid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Unique identifier of the resource booking header.'; + Caption = 'Bookable Resource Booking Header'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who created the record.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who modified the record.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(15; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(20; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(21; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(22; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(24; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(25; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(26; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(27; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(28; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(29; Name; Text[100]) + { + ExternalName = 'name'; + ExternalType = 'String'; + Description = 'The name of the booking summary.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(30; ProcessId; GUID) + { + ExternalName = 'processid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Contains the id of the process associated with the entity.'; + Caption = 'Process Id'; + DataClassification = SystemMetadata; + } + field(31; StageId; GUID) + { + ExternalName = 'stageid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Contains the id of the stage where the entity is located.'; + Caption = '(Deprecated) Stage Id'; + DataClassification = SystemMetadata; + } + field(32; TraversedPath; Text[1250]) + { + ExternalName = 'traversedpath'; + ExternalType = 'String'; + Description = 'A comma separated list of string values representing the unique identifiers of stages in a Business Process Flow Instance in the order that they occur.'; + Caption = '(Deprecated) Traversed Path'; + DataClassification = SystemMetadata; + } + field(33; Duration; Integer) + { + ExternalName = 'duration'; + ExternalType = 'Integer'; + Description = 'Shows the aggregate duration of the linked bookings.'; + Caption = 'Duration'; + DataClassification = SystemMetadata; + } + field(34; EndTime; Datetime) + { + ExternalName = 'endtime'; + ExternalType = 'DateTime'; + Description = 'Shows the end date and time of the booking summary.'; + Caption = 'End Time'; + DataClassification = SystemMetadata; + } + field(35; StartTime; Datetime) + { + ExternalName = 'starttime'; + ExternalType = 'DateTime'; + Description = 'Shows the start date and time of the booking summary.'; + Caption = 'Start Time'; + DataClassification = SystemMetadata; + } + field(36; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Bookable Resource Booking Header'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(38; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Bookable Resource Booking Header'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(40; ExchangeRate; Decimal) + { + ExternalName = 'exchangerate'; + ExternalType = 'Decimal'; + ExternalAccess = Read; + Description = 'Exchange rate for the currency associated with the bookableresourcebookingheader with respect to the base currency.'; + Caption = 'ExchangeRate'; + DataClassification = SystemMetadata; + } + field(41; TransactionCurrencyId; GUID) + { + ExternalName = 'transactioncurrencyid'; + ExternalType = 'Lookup'; + Description = 'Exchange rate for the currency associated with the BookableResourceBookingHeader with respect to the base currency.'; + Caption = 'Currency'; + TableRelation = "CRM Transactioncurrency".TransactionCurrencyId; + DataClassification = SystemMetadata; + } + field(43; BookableResourceId; GUID) + { + ExternalName = 'msdyn_bookableresourceid'; + ExternalType = 'Lookup'; + Description = 'Bookable Resource'; + Caption = 'Bookable Resource'; + TableRelation = "FS Bookable Resource".BookableResourceId; + DataClassification = SystemMetadata; + } + field(45; BookingType; Option) + { + ExternalName = 'msdyn_bookingtype'; + ExternalType = 'Picklist'; + Description = 'Select whether the booking is solid or liquid. Solid bookings are firm and cannot be changed whereas liquid bookings can be changed.'; + Caption = 'Booking Type'; + InitValue = Solid; + OptionMembers = Solid,Liquid; + OptionOrdinalValues = 1, 2; + DataClassification = SystemMetadata; + } + field(48; BookableResourceIdName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource".Name where(BookableResourceId = field(BookableResourceId))); + ExternalName = 'msdyn_bookableresourceidname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + } + keys + { + key(PK; BookableResourceBookingHeaderId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al new file mode 100644 index 0000000000..1aa8d0defe --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSConnectionSetup.Table.al @@ -0,0 +1,1054 @@ +// ------------------------------------------------------------------------------------------------ +// 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.CRM.Outlook; +using Microsoft.Foundation.UOM; +using Microsoft.Integration.Dataverse; +using Microsoft.Integration.D365Sales; +using Microsoft.Integration.SyncEngine; +using Microsoft.Service.Item; +using Microsoft.Projects.Project.Job; +using Microsoft.Projects.Project.Journal; +using System.Environment; +using System.Environment.Configuration; +using System.Security.Encryption; +using System.Threading; +using Microsoft.Projects.Resources.Resource; + +table 6623 "FS Connection Setup" +{ + Caption = 'Dynamics 365 Field Service Integration Setup'; + Permissions = tabledata "FS Connection Setup" = r; + InherentEntitlements = rX; + InherentPermissions = rX; + DataClassification = CustomerContent; + ReplicateData = true; + + fields + { + field(1; "Primary Key"; Code[20]) + { + DataClassification = SystemMetadata; + Caption = 'Primary Key'; + } + field(2; "Server Address"; Text[250]) + { + DataClassification = OrganizationIdentifiableInformation; + Caption = 'Field Service URL'; + + trigger OnValidate() + var + EnvironmentInfo: Codeunit "Environment Information"; + begin + CRMIntegrationManagement.CheckModifyCRMConnectionURL("Server Address"); + + if "Server Address" <> '' then + if EnvironmentInfo.IsSaaS() or (StrPos("Server Address", '.dynamics.com') > 0) then + "Authentication Type" := "Authentication Type"::Office365 + else + "Authentication Type" := "Authentication Type"::AD; + UpdateConnectionString(); + end; + } + field(3; "User Name"; Text[250]) + { + Caption = 'User Name'; + DataClassification = EndUserIdentifiableInformation; + + trigger OnValidate() + begin + "User Name" := DelChr("User Name", '<>'); + CheckUserName(); + UpdateDomainName(); + UpdateConnectionString(); + end; + } + field(4; "User Password Key"; Guid) + { + Caption = 'User Password Key'; + DataClassification = EndUserPseudonymousIdentifiers; + + trigger OnValidate() + begin + if not IsTemporary() then + if "User Password Key" <> xRec."User Password Key" then + xRec.DeletePassword(); + end; + } + field(59; "Restore Connection"; Boolean) + { + DataClassification = SystemMetadata; + Caption = 'Restore Connection'; + } + field(60; "Is Enabled"; Boolean) + { + DataClassification = SystemMetadata; + Caption = 'Is Enabled'; + + trigger OnValidate() + var + CRMConnectionSetup: Record "CRM Connection Setup"; + FSSetupDefaults: Codeunit "FS Setup Defaults"; + CRMConnectionSetupPage: Page "CRM Connection Setup"; + begin + if "Is Enabled" then begin + TestField("Job Journal Template"); + TestField("Job Journal Batch"); + if not CRMConnectionSetup.IsEnabled() then + Error(CRMConnSetupMustBeEnabledErr, CRMConnectionSetupPage.Caption()); + if "Hour Unit of Measure" = '' then + Error(HourUnitOfMeasureMustBePickedErr); + if not RemoveExistingCouplingOfResources() then + Error(''); + end; + UpdateConnectionDetails(); + RefreshDataFromFS(); + if "Is Enabled" then begin + TestIntegrationUserRequirements(); + FSSetupDefaults.ResetConfiguration(Rec); + Session.LogMessage('0000MAT', CRMConnEnabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + end else + Session.LogMessage('0000MAU', CRMConnDisabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + end; + } + field(63; "FS Version"; Text[30]) + { + DataClassification = SystemMetadata; + Caption = 'Field Service Version'; + } + field(67; "Is FS Solution Installed"; Boolean) + { + DataClassification = SystemMetadata; + Caption = 'Is CRM Solution Installed'; + } + field(76; "Proxy Version"; Integer) + { + Caption = 'Proxy Version'; + DataClassification = SystemMetadata; + + trigger OnValidate() + begin + UpdateProxyVersionInConnectionString(); + end; + } + field(118; CurrencyDecimalPrecision; Integer) + { + DataClassification = SystemMetadata; + Caption = 'Currency Decimal Precision'; + Description = 'Number of decimal places that can be used for currency.'; + } + field(124; BaseCurrencyId; Guid) + { + DataClassification = SystemMetadata; + Caption = 'Currency'; + Description = 'Unique identifier of the base currency of the organization.'; + TableRelation = "CRM Transactioncurrency".TransactionCurrencyId; + } + field(133; BaseCurrencyPrecision; Integer) + { + DataClassification = SystemMetadata; + Caption = 'Base Currency Precision'; + Description = 'Number of decimal places that can be used for the base currency.'; + MaxValue = 4; + MinValue = 0; + } + field(134; BaseCurrencySymbol; Text[5]) + { + DataClassification = SystemMetadata; + Caption = 'Base Currency Symbol'; + Description = 'Symbol used for the base currency.'; + } + field(135; "Authentication Type"; Option) + { + DataClassification = SystemMetadata; + Caption = 'Authentication Type'; + OptionCaption = 'OAuth 2.0,AD,IFD,OAuth'; + OptionMembers = Office365,AD,IFD,OAuth; + + trigger OnValidate() + begin + case "Authentication Type" of + "Authentication Type"::Office365: + Domain := ''; + "Authentication Type"::AD: + UpdateDomainName(); + end; + UpdateConnectionString(); + end; + } + field(136; "Connection String"; Text[250]) + { + DataClassification = OrganizationIdentifiableInformation; + Caption = 'Connection String'; + } + field(137; Domain; Text[250]) + { + Caption = 'Domain'; + DataClassification = OrganizationIdentifiableInformation; + Editable = false; + } + field(138; "Server Connection String"; BLOB) + { + DataClassification = OrganizationIdentifiableInformation; + Caption = 'Server Connection String'; + } + field(139; "Disable Reason"; Text[250]) + { + DataClassification = SystemMetadata; + Caption = 'Disable Reason'; + } + field(200; "Job Journal Template"; Code[10]) + { + DataClassification = SystemMetadata; + Caption = 'Project Journal Template'; + TableRelation = "Job Journal Template"; + } + field(201; "Job Journal Batch"; Code[10]) + { + DataClassification = SystemMetadata; + Caption = 'Project Journal Batch'; + TableRelation = "Job Journal Batch".Name where("Journal Template Name" = field("Job Journal Template")); + } + field(202; "Hour Unit of Measure"; Code[10]) + { + DataClassification = SystemMetadata; + Caption = 'Hour Unit of Measure'; + TableRelation = "Unit of Measure"; + } + field(203; "Line Synch. Rule"; Enum "FS Work Order Line Synch. Rule") + { + DataClassification = SystemMetadata; + Caption = 'Synchronize work order products/services'; + } + field(204; "Line Post Rule"; Enum "FS Work Order Line Post Rule") + { + DataClassification = SystemMetadata; + Caption = 'Automatically post project journal lines'; + } + } + + keys + { + key(Key1; "Primary Key") + { + Clustered = true; + } + } + + fieldgroups + { + } + + trigger OnModify() + begin + if IsTemporary() then + exit; + if "User Password Key" <> xRec."User Password Key" then + xRec.DeletePassword(); + end; + + trigger OnDelete() + begin + if IsTemporary() then + exit; + DeletePassword(); + end; + + var + CRMIntegrationManagement: Codeunit "CRM Integration Management"; + FSIntegrationMgt: Codeunit "FS Integration Mgt."; + CDSIntegrationImpl: Codeunit "CDS Integration Impl."; + CRMProductName: Codeunit "CRM Product Name"; + IsolatedStorageManagement: Codeunit "Isolated Storage Management"; + TempUserPassword: SecretText; + ConnectionErr: Label 'The connection setup cannot be validated. Verify the settings and try again.\Detailed error description: %1.', Comment = '%1 Error message from the provider (.NET exception message)'; + ConnectionStringFormatTok: Label 'Url=%1; UserName=%2; Password=%3; ProxyVersion=%4; %5', Locked = true; + ConnectionSuccessMsg: Label 'The connection test was successful. The settings are valid.'; + DetailsMissingErr: Label 'A %1 URL and user name are required to enable a connection.', Comment = '%1 = CRM product name'; + MissingUsernameTok: Label '{USER}', Locked = true; + MissingPasswordTok: Label '{PASSWORD}', Locked = true; + AccessTokenTok: Label 'AccessToken', Locked = true; + ClientSecretConnectionStringFormatTxt: Label '%1; Url=%2; ClientId=%3; ClientSecret=%4; ProxyVersion=%5', Locked = true; + ClientSecretAuthTxt: Label 'AuthType=ClientSecret', Locked = true; + ClientSecretTok: Label '{CLIENTSECRET}', Locked = true; + CertificateConnectionStringFormatTxt: Label '%1; Url=%2; ClientId=%3; Certificate=%4; ProxyVersion=%5', Locked = true; + CertificateAuthTxt: Label 'AuthType=Certificate', Locked = true; + CertificateTok: Label '{CERTIFICATE}', Locked = true; + ClientIdTok: Label '{CLIENTID}', Locked = true; + UserNameMustIncludeDomainErr: Label 'The user name must include the domain when the authentication type is set to Active Directory.'; + UserNameMustBeEmailErr: Label 'The user name must be a valid email address when the authentication type is set to Office 365.'; + ConnectionStringPwdPlaceHolderMissingErr: Label 'The connection string must include the password placeholder {PASSWORD}.'; + ConnectionStringPwdOrClientSecretPlaceHolderMissingErr: Label 'The connection string must include either the password placeholder {PASSWORD}, the client secret placeholder {CLIENTSECRET} or the certificate placeholder {CERTIFICATE}.', Comment = '{PASSWORD}, {CERTIFICATE} and {CLIENTSECRET} are locked strings - do not translate them.'; + SystemAdminRoleTemplateIdTxt: Label '{627090FF-40A3-4053-8790-584EDC5BE201}', Locked = true; + SystemAdminErr: Label 'User %1 has the %2 role on server %3.\\You must choose a user that does not have the %2 role.', Comment = '%1 user name, %2 - security role name, %3 - server address'; + BCRolesErr: Label 'User %1 does not have the required roles on server %4.\\You must choose a user that has the roles %2 and %3.', Comment = '%1 user name, %2 - security role name, %3 - security role name, %4 - server address'; + UserNotLicensedErr: Label 'User %1 is not licensed on server %2.', Comment = '%1 user name, %2 - server address'; + UserNotActiveErr: Label 'User %1 is disabled on server %2.', Comment = '%1 user name, %2 - server address'; + UserHasNoRolesErr: Label 'User %1 has no user roles assigned on server %2.', Comment = '%1 user name, %2 - server address'; + BCIntegrationUserFSRoleIdTxt: Label '{c11b4fa8-956b-439d-8b3c-021e8736a78b}', Locked = true; + CDSConnectionMustBeEnabledErr: Label 'You must enable the connection to Dataverse before you can set up the connection to %1.\\Choose ''Set up Dataverse connection'' in %2 page.', Comment = '%1 = CRM product name, %2 = Assisted Setup page caption.'; + CRMConnectionMustBeEnabledErr: Label 'You must enable the connection to Dynamics 365 Field Service before you can set up the connection to %1.\\Choose ''Set up a connection to Dynamics 365 Field Service'' in %2 page.', Comment = '%1 = CRM product name, %2 = Assisted Setup page caption.'; + ShowDataverseConnectionSetupLbl: Label 'Show Dataverse Connection Setup'; + ShowCRMConnectionSetupLbl: Label 'Show Microsoft Dynamics 365 Connection Setup'; + DeploySucceedMsg: Label 'The solution, user roles, and entities have been deployed.'; + DeployFailedMsg: Label 'The deployment of the solution succeeded, but the deployment of user roles failed.'; + DeploySolutionFailedMsg: Label 'The deployment of the solution failed.'; + CategoryTok: Label 'AL Field Service Integration', Locked = true; + CRMConnDisabledTxt: Label 'Field Service connection has been disabled.', Locked = true; + CRMConnEnabledTxt: Label 'Field Service connection has been enabled.', Locked = true; + DefaultingToDataverseServiceClientTxt: Label 'Defaulting to DataverseServiceClient', Locked = true; + CRMConnSetupMustBeEnabledErr: label 'You must enable the connection in page %1', Comment = '%1 - page caption'; + HourUnitOfMeasureMustBePickedErr: label 'Field Service uses a fixed unit of measure for bookable resources - hour. You must pick a corresponding resource unit of measure.'; + UncoupleResourcesQst: label 'The current coupling of Resource records to Product entity will be removed. New mapping will be set up between Resource table and Bookable Resource entity. All resources will be uncoupled, but not deleted. Do you want to continue?'; + + local procedure RemoveExistingCouplingOfResources(): Boolean + var + IntegrationTableMapping: Record "Integration Table Mapping"; + CRMIntegrationRecord: Record "CRM Integration Record"; + begin + IntegrationTableMapping.SetRange("Table ID", Database::Resource); + IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Product"); + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + + CRMIntegrationRecord.SetRange("Table ID", Database::Resource); + + if IntegrationTableMapping.IsEmpty() and CRMIntegrationRecord.IsEmpty() then + exit(true); + + if not Confirm(UncoupleResourcesQst) then + exit(false); + + if not IntegrationTableMapping.IsEmpty() then + CRMIntegrationRecord.DeleteAll(); + + IntegrationTableMapping.DeleteAll(true); + exit(true); + end; + + internal procedure EnsureCDSConnectionIsEnabled(); + var + CDSConnectionSetup: Record "CDS Connection Setup"; + begin + if Get() then + if "Is Enabled" then + exit; + + if CDSConnectionSetup.Get() then + if CDSConnectionSetup."Is Enabled" then + exit; + + CDSConnectionNotEnabledError(); + end; + + internal procedure EnsureCRMConnectionIsEnabled(); + var + CRMConnectionSetup: Record "CRM Connection Setup"; + begin + if Get() then + if "Is Enabled" then + exit; + + if CRMConnectionSetup.Get() then + if CRMConnectionSetup."Is Enabled" then + exit; + + CRMConnectionNotEnabledError(); + end; + + internal procedure LoadConnectionStringElementsFromCDSConnectionSetup(); + var + CDSConnectionSetup: Record "CDS Connection Setup"; + begin + if Get() then + if "Is Enabled" then + exit; + + if CDSConnectionSetup.Get() then + if CDSConnectionSetup."Is Enabled" then begin + "Server Address" := CDSConnectionSetup."Server Address"; + "User Name" := CDSConnectionSetup."User Name"; + "User Password Key" := CDSConnectionSetup."User Password Key"; + "Authentication Type" := CDSConnectionSetup."Authentication Type"; + "Proxy Version" := CDSConnectionSetup."Proxy Version"; + if not Modify() then + Insert(); + SetConnectionString(CDSConnectionSetup."Connection String"); + exit; + end; + + CDSConnectionNotEnabledError(); + end; + + internal procedure DeployFSSolution(ForceRedeploy: Boolean); + var + DummyCRMConnectionSetup: Record "CRM Connection Setup"; + AdminEmail: Text; + AdminPassword: SecretText; + AccessToken: SecretText; + AdminADDomain: Text; + ImportSolutionFailed: Boolean; + begin + if not ForceRedeploy and FSIntegrationMgt.IsFSSolutionInstalled() then + exit; + + DummyCRMConnectionSetup.EnsureCDSConnectionIsEnabled(); + case "Authentication Type" of + "Authentication Type"::Office365: + CDSIntegrationImpl.GetAccessToken("Server Address", true, AccessToken); + "Authentication Type"::AD: + if not PromptForCredentials(AdminEmail, AdminPassword, AdminADDomain) then + exit; + else + if not PromptForCredentials(AdminEmail, AdminPassword) then + exit; + end; + + if FSIntegrationMgt.ImportFSSolution("Server Address", "User Name", AdminEmail, AdminPassword, AccessToken, AdminADDomain, GetProxyVersion(), ForceRedeploy, ImportSolutionFailed) then + Message(DeploySucceedMsg) + else + if ImportSolutionFailed then + Message(DeploySolutionFailedMsg) + else + Message(DeployFailedMsg); + end; + + internal procedure CountCRMJobQueueEntries(var ActiveJobs: Integer; var TotalJobs: Integer) + var + JobQueueEntry: Record "Job Queue Entry"; + begin + if not "Is Enabled" then begin + TotalJobs := 0; + ActiveJobs := 0; + end else begin + if "Is FS Solution Installed" then + JobQueueEntry.SetFilter("Object ID to Run", GetJobQueueEntriesObjectIDToRunFilter()) + else + JobQueueEntry.SetRange("Object ID to Run", Codeunit::"Integration Synch. Job Runner"); + TotalJobs := JobQueueEntry.Count(); + + JobQueueEntry.SetFilter(Status, '%1|%2', JobQueueEntry.Status::Ready, JobQueueEntry.Status::"In Process"); + ActiveJobs := JobQueueEntry.Count(); + end; + end; + + internal procedure SetPassword(PasswordText: SecretText) + begin + if IsTemporary() then begin + TempUserPassword := PasswordText; + exit; + end; + if IsNullGuid("User Password Key") then + "User Password Key" := CreateGuid(); + + IsolatedStorageManagement.Set("User Password Key", PasswordText, DATASCOPE::Company); + end; + + internal procedure DeletePassword() + begin + if IsTemporary() then begin + Clear(TempUserPassword); + exit; + end; + + if IsNullGuid("User Password Key") then + exit; + + IsolatedStorageManagement.Delete(Format("User Password Key"), DATASCOPE::Company); + end; + + internal procedure RegisterConnection() + begin + if not HasTableConnection(TableConnectionType::CRM, "Primary Key") then + RegisterConnectionWithName("Primary Key"); + end; + + [NonDebuggable] + internal procedure RegisterConnectionWithName(ConnectionName: Text) + begin + RegisterTableConnection(TableConnectionType::CRM, ConnectionName, GetConnectionStringWithCredentials().Unwrap()); + SetDefaultTableConnection(TableConnectionType::CRM, GetDefaultFSConnection(ConnectionName)); + end; + + internal procedure UnregisterConnection(): Boolean + begin + exit(UnregisterConnectionWithName("Primary Key")); + end; + + [TryFunction] + internal procedure UnregisterConnectionWithName(ConnectionName: Text) + begin + UnregisterTableConnection(TableConnectionType::CRM, ConnectionName); + end; + + [NonDebuggable] + internal procedure GetConnectionStringWithCredentials() ConnectionString: SecretText + var + ConnectionStringWithPlaceholders: Text; + PasswordPlaceHolderPos: Integer; + begin + ConnectionStringWithPlaceholders := GetConnectionStringAsStoredInSetup(); + + // if the setup record is temporary and connection string contains access token, this is a temp setup record constructed for the admin log-on + // in this case just use the connection string + if IsTemporary() and ConnectionStringWithPlaceholders.Contains(AccessTokenTok) then + exit(ConnectionStringWithPlaceholders); + + if ConnectionStringWithPlaceholders = '' then + ConnectionStringWithPlaceholders := UpdateConnectionString(); + + // if auth type is Office365 and connection string contains {ClientSecret} token + // then we will connect via OAuth client credentials grant flow, and construct the connection string accordingly, with the actual client secret + if "Authentication Type" = "Authentication Type"::Office365 then begin + if ConnectionStringWithPlaceholders.Contains(ClientSecretTok) then begin + ConnectionStringWithPlaceholders := StrSubstNo(ClientSecretConnectionStringFormatTxt, ClientSecretAuthTxt, "Server Address", CDSIntegrationImpl.GetCDSConnectionClientId(), '%1', GetProxyVersion()); + ConnectionString := SecretStrSubstNo(ConnectionStringWithPlaceholders, CDSIntegrationImpl.GetCDSConnectionClientSecret()); + exit(ConnectionString); + end; + + if ConnectionStringWithPlaceholders.Contains(CertificateTok) then begin + ConnectionString := StrSubstNo(CertificateConnectionStringFormatTxt, CertificateAuthTxt, "Server Address", CDSIntegrationImpl.GetCDSConnectionFirstPartyAppId(), CDSIntegrationImpl.GetCDSConnectionFirstPartyAppCertificate(), GetProxyVersion()); + exit(ConnectionString); + end; + end; + + PasswordPlaceHolderPos := StrPos(ConnectionStringWithPlaceholders, MissingPasswordTok); + ConnectionStringWithPlaceholders := + CopyStr(ConnectionStringWithPlaceholders, 1, PasswordPlaceHolderPos - 1) + '%1' + + CopyStr(ConnectionStringWithPlaceholders, PasswordPlaceHolderPos + StrLen(MissingPasswordTok)); + ConnectionString := SecretStrSubstNo(ConnectionStringWithPlaceholders, GetPassword()); + end; + + [NonDebuggable] + internal procedure GetPassword(): SecretText + var + Value: SecretText; + begin + if IsTemporary() then + exit(TempUserPassword); + if not IsNullGuid("User Password Key") then + IsolatedStorageManagement.Get("User Password Key", DATASCOPE::Company, Value); + exit(Value); + end; + + local procedure GetUserName() UserName: Text + begin + if "User Name" = '' then + UserName := MissingUsernameTok + else + UserName := CopyStr("User Name", StrPos("User Name", '\') + 1); + end; + + internal procedure GetJobQueueEntriesObjectIDToRunFilter(): Text + begin + exit( + StrSubstNo( + '%1|%2|%3|%4|%5|%6', + Codeunit::"Integration Synch. Job Runner", + Codeunit::"CRM Statistics Job", + Codeunit::"Auto Create Sales Orders", + Codeunit::"Auto Process Sales Quotes", + Codeunit::"Int. Uncouple Job Runner", + Codeunit::"Int. Coupling Job Runner")); + end; + + internal procedure PerformTestConnection() + begin + VerifyTestConnection(); + Message(ConnectionSuccessMsg); + end; + + internal procedure VerifyTestConnection(): Boolean + begin + if ("Server Address" = '') or ("User Name" = '') then + Error(DetailsMissingErr, CRMProductName.FSServiceName()); + + CRMIntegrationManagement.ClearState(); + + if not TestConnection() then + Error(ConnectionErr, CRMIntegrationManagement.GetLastErrorMessage()); + + TestIntegrationUserRequirements(); + + exit(true); + end; + + internal procedure TestConnection() Success: Boolean + var + TestConnectionName: Text; + begin + TestConnectionName := Format(CreateGuid()); + UnregisterConnectionWithName(TestConnectionName); + RegisterConnectionWithName(TestConnectionName); + SetDefaultTableConnection( + TableConnectionType::CRM, GetDefaultFSConnection(TestConnectionName), true); + Success := TryReadSystemUsers(); + + UnregisterConnectionWithName(TestConnectionName); + end; + + internal procedure TestIntegrationUserRequirements() + var + CRMRole: Record "CRM Role"; + TempCRMRole: Record "CRM Role" temporary; + CRMSystemuserroles: Record "CRM Systemuserroles"; + CRMSystemuser: Record "CRM Systemuser"; + BCIntAdminCRMRoleName: Text; + BCIntUserCRMRoleName: Text; + SystemAdminCRMRoleName: Text; + TestConnectionName: Text; + BCIntegrationAdminRoleDeployed: Boolean; + BCIntegrationRolesDeployed: Boolean; + ChosenUserIsSystemAdmin: Boolean; + ChosenUserHasBCFSSecurityRole: Boolean; + begin + TestConnectionName := Format(CreateGuid()); + UnregisterConnectionWithName(TestConnectionName); + RegisterConnectionWithName(TestConnectionName); + SetDefaultTableConnection( + TableConnectionType::CRM, GetDefaultFSConnection(TestConnectionName), true); + + if CRMRole.FindSet() then + repeat + TempCRMRole.TransferFields(CRMRole); + TempCRMRole.Insert(); + if LowerCase(Format(TempCRMRole.RoleId)) = BCIntegrationUserFSRoleIdTxt then begin + BCIntegrationAdminRoleDeployed := true; + BCIntAdminCRMRoleName := TempCRMRole.Name; + end; + until CRMRole.Next() = 0; + + BCIntegrationRolesDeployed := BCIntegrationAdminRoleDeployed; + + CRMSystemuser.SetFilter(InternalEMailAddress, StrSubstNo('@%1', "User Name")); + if CRMSystemuser.FindFirst() then begin + if CRMSystemuser.IsDisabled then + Error(UserNotActiveErr, "User Name", "Server Address"); + if "Authentication Type" <> "Authentication Type"::Office365 then + if not CRMSystemuser.IsLicensed then + Error(UserNotLicensedErr, "User Name", "Server Address"); + + CRMSystemuserroles.SetRange(SystemUserId, CRMSystemuser.SystemUserId); + if CRMSystemuserroles.FindSet() then + repeat + if TempCRMRole.Get(CRMSystemuserroles.RoleId) then begin + if UpperCase(Format(TempCRMRole.RoleTemplateId)) = SystemAdminRoleTemplateIdTxt then begin + ChosenUserIsSystemAdmin := true; + SystemAdminCRMRoleName := TempCRMRole.Name + end; + if LowerCase(Format(TempCRMRole.RoleId)) = BCIntegrationUserFSRoleIdTxt then + ChosenUserHasBCFSSecurityRole := true; + end; + until CRMSystemuserroles.Next() = 0 + else + if ("Server Address" <> '') and ("Server Address" <> '@@test@@') then + Error(UserHasNoRolesErr, "User Name", "Server Address"); + + if ChosenUserIsSystemAdmin then + Error(SystemAdminErr, "User Name", SystemAdminCRMRoleName, "Server Address"); + + if BCIntegrationRolesDeployed and not ChosenUserHasBCFSSecurityRole then + Error(BCRolesErr, "User Name", BCIntAdminCRMRoleName, BCIntUserCRMRoleName, "Server Address"); + end; + + UnregisterConnectionWithName(TestConnectionName); + end; + + [TryFunction] + internal procedure TryReadSystemUsers() + var + CRMSystemuser: Record "CRM Systemuser"; + begin + if CRMSystemuser.Count() > 0 then + exit; + end; + + internal procedure UpdateFromWizard(var SourceFSConnectionSetup: Record "FS Connection Setup"; PasswordText: SecretText) + begin + if not Get() then begin + Init(); + Insert(); + end; + Validate("Server Address", SourceFSConnectionSetup."Server Address"); + Validate("Authentication Type", "Authentication Type"::Office365); + Validate("User Name", SourceFSConnectionSetup."User Name"); + SetPassword(PasswordText); + Validate("Proxy Version", SourceFSConnectionSetup."Proxy Version"); + Validate("Job Journal Template", SourceFSConnectionSetup."Job Journal Template"); + Validate("Job Journal Batch", SourceFSConnectionSetup."Job Journal Batch"); + Validate("Hour Unit of Measure", SourceFSConnectionSetup."Hour Unit of Measure"); + Validate("Line Synch. Rule", SourceFSConnectionSetup."Line Synch. Rule"); + Validate("Line Post Rule", SourceFSConnectionSetup."Line Post Rule"); + Modify(true); + end; + + internal procedure EnableFSConnectionFromWizard() + begin + Get(); + Validate("Is Enabled", true); + Modify(true); + end; + + local procedure UpdateConnectionDetails() + begin + if "Is Enabled" = xRec."Is Enabled" then + exit; + + if not UnregisterConnection() then + ClearLastError(); + + if "Is Enabled" then begin + VerifyTestConnection(); + RegisterConnection(); + InstallIntegrationSolution(); + EnableIntegrationTables(); + if "Disable Reason" <> '' then + Clear("Disable Reason"); + end else begin + "FS Version" := ''; + "Is FS Solution Installed" := false; + CurrencyDecimalPrecision := 0; + Clear(BaseCurrencyId); + BaseCurrencyPrecision := 0; + BaseCurrencySymbol := ''; + UpdateFSJobQueueEntriesStatus(); + end; + end; + + local procedure InstallIntegrationSolution() + var + AdminEmail: Text; + AdminPassword: SecretText; + AccessToken: SecretText; + AdminADDomain: Text; + ImportSolutionFailed: Boolean; + begin + if FSIntegrationMgt.IsFSSolutionInstalled() then + exit; + + case "Authentication Type" of + "Authentication Type"::Office365: + CDSIntegrationImpl.GetAccessToken("Server Address", true, AccessToken); + "Authentication Type"::AD: + if not PromptForCredentials(AdminEmail, AdminPassword, AdminADDomain) then + exit; + else + if not PromptForCredentials(AdminEmail, AdminPassword) then + exit; + end; + + FSIntegrationMgt.ImportFSSolution( + "Server Address", "User Name", AdminEmail, AdminPassword, AccessToken, AdminADDomain, GetProxyVersion(), false, ImportSolutionFailed); + end; + + local procedure EnableIntegrationTables() + var + FSSetupDefaults: Codeunit "FS Setup Defaults"; + begin + Modify(); // Job Queue to read "Is Enabled" + Commit(); + FSSetupDefaults.ResetConfiguration(Rec); + end; + + internal procedure RefreshDataFromFS() + begin + RefreshDataFromFS(true); + end; + + internal procedure RefreshDataFromFS(ResetSalesOrderMappingConfiguration: Boolean) + begin + if "Is Enabled" then begin + "Is FS Solution Installed" := FSIntegrationMgt.IsFSSolutionInstalled(); + if not TryRefreshFSSettings() then + exit; + end; + end; + + [TryFunction] + local procedure TryRefreshFSSettings() + var + CRMOrganization: Record "CRM Organization"; + begin + if CRMOrganization.FindFirst() then begin + CurrencyDecimalPrecision := CRMOrganization.CurrencyDecimalPrecision; + BaseCurrencyId := CRMOrganization.BaseCurrencyId; + BaseCurrencyPrecision := CRMOrganization.BaseCurrencyPrecision; + BaseCurrencySymbol := CRMOrganization.BaseCurrencySymbol; + end + end; + + [NonDebuggable] + internal procedure PromptForCredentials(var AdminEmail: Text; var AdminPassword: SecretText): Boolean + var + TempOfficeAdminCredentials: Record "Office Admin. Credentials" temporary; + begin + if TempOfficeAdminCredentials.IsEmpty() then begin + TempOfficeAdminCredentials.Init(); + TempOfficeAdminCredentials.Insert(true); + Commit(); + if Page.RunModal(Page::"Dynamics CRM Admin Credentials", TempOfficeAdminCredentials) <> Action::LookupOK then + exit(false); + end; + if (not TempOfficeAdminCredentials.FindFirst()) or + (TempOfficeAdminCredentials.Email = '') or (TempOfficeAdminCredentials.Password = '') + then begin + TempOfficeAdminCredentials.DeleteAll(true); + exit(false); + end; + + AdminEmail := TempOfficeAdminCredentials.Email; + AdminPassword := TempOfficeAdminCredentials.Password; + exit(true); + end; + + [NonDebuggable] + internal procedure PromptForCredentials(var AdminEmail: Text; var AdminPassword: SecretText; var AdminADDomain: Text): Boolean + var + TempOfficeAdminCredentials: Record "Office Admin. Credentials" temporary; + BackslashPos: Integer; + begin + if TempOfficeAdminCredentials.IsEmpty() then begin + TempOfficeAdminCredentials.Init(); + TempOfficeAdminCredentials.Insert(true); + Commit(); + if Page.RunModal(Page::"Dynamics CRM Admin Credentials", TempOfficeAdminCredentials) <> Action::LookupOK then + exit(false); + end; + if (not TempOfficeAdminCredentials.FindFirst()) or + (TempOfficeAdminCredentials.Email = '') or (TempOfficeAdminCredentials.Password = '') + then begin + TempOfficeAdminCredentials.DeleteAll(true); + exit(false); + end; + + BackslashPos := StrPos(TempOfficeAdminCredentials.Email, '\'); + if (BackslashPos <= 1) or (BackslashPos = StrLen(TempOfficeAdminCredentials.Email)) then + Error(UserNameMustIncludeDomainErr); + AdminADDomain := CopyStr(TempOfficeAdminCredentials.Email, 1, BackslashPos - 1); + AdminEmail := CopyStr(TempOfficeAdminCredentials.Email, BackslashPos + 1); + AdminPassword := TempOfficeAdminCredentials.Password; + exit(true); + end; + + local procedure GetDefaultFSConnection(ConnectionName: Text): Text + begin + OnGetDefaultFSConnection(ConnectionName); + exit(ConnectionName); + end; + + [IntegrationEvent(false, false)] + local procedure OnGetDefaultFSConnection(var ConnectionName: Text) + begin + end; + + local procedure CrmAuthenticationType(): Text + begin + case "Authentication Type" of + "Authentication Type"::Office365: + exit('AuthType=Office365;'); + "Authentication Type"::AD: + exit('AuthType=AD;' + GetDomain()); + "Authentication Type"::IFD: + exit('AuthType=IFD;' + GetDomain() + 'HomeRealmUri= ;'); + "Authentication Type"::OAuth: + exit('AuthType=OAuth;' + 'AppId= ;' + 'RedirectUri= ;' + 'TokenCacheStorePath= ;' + 'LoginPrompt=Auto;'); + end; + end; + + internal procedure UpdateConnectionString() ConnectionString: Text + begin + if "Authentication Type" <> "Authentication Type"::Office365 then + ConnectionString := StrSubstNo(ConnectionStringFormatTok, "Server Address", GetUserName(), MissingPasswordTok, GetProxyVersion(), CrmAuthenticationType()) + else + if CDSIntegrationImpl.GetCDSConnectionFirstPartyAppId() <> '' then + ConnectionString := StrSubstNo(CertificateConnectionStringFormatTxt, CertificateAuthTxt, "Server Address", ClientIdTok, CertificateTok, GetProxyVersion()) + else + ConnectionString := StrSubstNo(ClientSecretConnectionStringFormatTxt, ClientSecretAuthTxt, "Server Address", ClientIdTok, ClientSecretTok, GetProxyVersion()); + + SetConnectionString(ConnectionString); + end; + + local procedure UpdateProxyVersionInConnectionString() ConnectionString: Text + var + LeftPart: Text; + RightPart: Text; + ProxyVersionTok: Text; + IndexOfProxyVersion: Integer; + begin + ProxyVersionTok := 'ProxyVersion='; + ConnectionString := GetConnectionStringAsStoredInSetup(); + + // if the connection string is empty, just initialize it the standard way + if ConnectionString = '' then begin + ConnectionString := UpdateConnectionString(); + exit; + end; + + IndexOfProxyVersion := ConnectionString.IndexOf(ProxyVersionTok); + + // if there is no proxy version in the connection string, just add it to the end + if IndexOfProxyVersion = 0 then begin + ConnectionString += ('; ' + ProxyVersionTok + Format(GetProxyVersion())); + SetConnectionString(ConnectionString); + exit; + end; + + LeftPart := CopyStr(ConnectionString, 1, IndexOfProxyVersion - 1); + RightPart := CopyStr(ConnectionString, IndexOfProxyVersion); + + // RightPart starts with ProxyVersion= + // if there is no ; in it, then this is the end of the original connection string + // just add proxy version to the end of LeftPart + if RightPart.IndexOf(';') = 0 then begin + ConnectionString := LeftPart + ProxyVersionTok + Format(GetProxyVersion()); + SetConnectionString(ConnectionString); + exit; + end; + + // in the remaining case, ProxyVersion=XYZ is in the middle of the string + RightPart := CopyStr(RightPart, RightPart.IndexOf(';')); + ConnectionString := LeftPart + ProxyVersionTok + Format(GetProxyVersion()) + RightPart; + SetConnectionString(ConnectionString); + end; + + local procedure UpdateDomainName() + begin + if "User Name" <> '' then + if StrPos("User Name", '\') > 0 then + Validate(Domain, CopyStr("User Name", 1, StrPos("User Name", '\') - 1)) + else + Domain := ''; + end; + + local procedure CheckUserName() + begin + if "User Name" <> '' then + case "Authentication Type" of + "Authentication Type"::AD: + if StrPos("User Name", '\') = 0 then + Error(UserNameMustIncludeDomainErr); + "Authentication Type"::Office365: + if StrPos("User Name", '@') = 0 then + Error(UserNameMustBeEmailErr); + end; + end; + + local procedure GetDomain(): Text + var + DomainLbl: Label 'Domain=%1;', Locked = true; + begin + if Domain <> '' then + exit(StrSubstNo(DomainLbl, Domain)); + end; + + local procedure UpdateFSJobQueueEntriesStatus() + var + IntegrationTableMapping: Record "Integration Table Mapping"; + JobQueueEntry: Record "Job Queue Entry"; + NewStatus: Option; + begin + if "Is Enabled" then + NewStatus := JobQueueEntry.Status::Ready + else + NewStatus := JobQueueEntry.Status::"On Hold"; + IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse); + IntegrationTableMapping.SetRange("Synch. Codeunit ID", Codeunit::"CRM Integration Table Synch."); + IntegrationTableMapping.SetRange("Delete After Synchronization", false); + if CDSIntegrationImpl.IsIntegrationEnabled() then + IntegrationTableMapping.SetFilter("Table ID", StrSubstNo('<>%1&<>%2&<>%3&<>%4', Database::"Job Journal Line", Database::"Job Task", Database::Resource, Database::"Service Item")); + if IntegrationTableMapping.FindSet() then + repeat + JobQueueEntry.SetRange("Record ID to Process", IntegrationTableMapping.RecordId); + if JobQueueEntry.FindSet() then + repeat + JobQueueEntry.SetStatus(NewStatus); + until JobQueueEntry.Next() = 0; + until IntegrationTableMapping.Next() = 0; + end; + + internal procedure GetConnectionStringAsStoredInSetup() ConnectionString: Text + var + CRMConnectionSetup: Record "CRM Connection Setup"; + InStream: InStream; + begin + if CRMConnectionSetup.Get("Primary Key") then + CalcFields("Server Connection String"); + "Server Connection String".CreateInStream(InStream); + InStream.ReadText(ConnectionString); + end; + + internal procedure SetConnectionString(ConnectionString: Text) + var + OutStream: OutStream; + begin + if ConnectionString = '' then + Clear("Server Connection String") + else begin + if "Authentication Type" <> "Authentication Type"::Office365 then + if StrPos(ConnectionString, MissingPasswordTok) = 0 then + Error(ConnectionStringPwdPlaceHolderMissingErr); + + if "Authentication Type" = "Authentication Type"::Office365 then + if (StrPos(ConnectionString, MissingPasswordTok) = 0) and (StrPos(ConnectionString, ClientSecretTok) = 0) and (StrPos(ConnectionString, CertificateTok) = 0) then + Error(ConnectionStringPwdOrClientSecretPlaceHolderMissingErr); + + Clear("Server Connection String"); + "Server Connection String".CreateOutStream(OutStream); + OutStream.WriteText(ConnectionString); + end; + if not Modify() then; + end; + + internal procedure IsEnabled(): Boolean + begin + if not Get() then + exit(false); + exit("Is Enabled"); + end; + + internal procedure GetProxyVersion(): Integer + var + EnvironmentInformation: Codeunit "Environment Information"; + begin + if "Proxy Version" >= 100 then + exit("Proxy Version"); + + if not EnvironmentInformation.IsSaaS() then + exit("Proxy Version"); + + Session.LogMessage('0000K7P', DefaultingToDataverseServiceClientTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit(100); + end; + + local procedure CDSConnectionNotEnabledError() + var + AssistedSetup: Page "Assisted Setup"; + CDSConnectionNotEnabledErrorInfo: ErrorInfo; + begin + CDSConnectionNotEnabledErrorInfo.DataClassification := CDSConnectionNotEnabledErrorInfo.DataClassification::SystemMetadata; + CDSConnectionNotEnabledErrorInfo.ErrorType := CDSConnectionNotEnabledErrorInfo.ErrorType::Client; + CDSConnectionNotEnabledErrorInfo.Verbosity := CDSConnectionNotEnabledErrorInfo.Verbosity::Error; + CDSConnectionNotEnabledErrorInfo.Message := StrSubstNo(CDSConnectionMustBeEnabledErr, CRMProductName.FSServiceName(), AssistedSetup.Caption()); + CDSConnectionNotEnabledErrorInfo.AddNavigationAction(ShowDataverseConnectionSetupLbl); + CDSConnectionNotEnabledErrorInfo.PageNo(Page::"CDS Connection Setup Wizard"); + Error(CDSConnectionNotEnabledErrorInfo); + end; + + local procedure CRMConnectionNotEnabledError() + var + AssistedSetup: Page "Assisted Setup"; + CRMConnectionNotEnabledErrorInfo: ErrorInfo; + begin + CRMConnectionNotEnabledErrorInfo.DataClassification := CRMConnectionNotEnabledErrorInfo.DataClassification::SystemMetadata; + CRMConnectionNotEnabledErrorInfo.ErrorType := CRMConnectionNotEnabledErrorInfo.ErrorType::Client; + CRMConnectionNotEnabledErrorInfo.Verbosity := CRMConnectionNotEnabledErrorInfo.Verbosity::Error; + CRMConnectionNotEnabledErrorInfo.Message := StrSubstNo(CRMConnectionMustBeEnabledErr, CRMProductName.FSServiceName(), AssistedSetup.Caption()); + CRMConnectionNotEnabledErrorInfo.AddNavigationAction(ShowCRMConnectionSetupLbl); + CRMConnectionNotEnabledErrorInfo.PageNo(Page::"CRM Connection Setup Wizard"); + Error(CRMConnectionNotEnabledErrorInfo); + end; +} + diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAsset.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAsset.Table.al new file mode 100644 index 0000000000..f7abe0f081 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAsset.Table.al @@ -0,0 +1,423 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6613 "FS Customer Asset" +{ + ExternalName = 'msdyn_customerasset'; + TableType = CRM; + Description = 'Specify Customer Asset.'; + DataClassification = SystemMetadata; + + fields + { + field(1; CustomerAssetId; GUID) + { + ExternalName = 'msdyn_customerassetid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'Customer Asset'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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 = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Customer Asset'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Customer Asset'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[100]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Enter the name of the custom entity.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(36; Account; GUID) + { + ExternalName = 'msdyn_account'; + ExternalType = 'Lookup'; + Description = 'Parent Customer of this Asset'; + Caption = 'Account'; + TableRelation = "CRM Account".AccountId; + DataClassification = SystemMetadata; + } + field(37; CustomerAssetCategory; GUID) + { + ExternalName = 'msdyn_customerassetcategory'; + ExternalType = 'Lookup'; + Description = 'The category of the customer asset'; + Caption = 'Category'; + TableRelation = "FS Customer Asset Category".CustomerAssetCategoryId; + DataClassification = SystemMetadata; + } + field(38; Latitude; Decimal) + { + ExternalName = 'msdyn_latitude'; + ExternalType = 'Double'; + Description = ''; + Caption = 'Latitude'; + DataClassification = SystemMetadata; + } + field(39; Longitude; Decimal) + { + ExternalName = 'msdyn_longitude'; + ExternalType = 'Double'; + Description = ''; + Caption = 'Longitude'; + DataClassification = SystemMetadata; + } + field(40; MasterAsset; GUID) + { + ExternalName = 'msdyn_masterasset'; + ExternalType = 'Lookup'; + Description = 'Top-Level Asset, (if this asset is a sub asset)'; + Caption = 'Top-Level Asset'; + TableRelation = "FS Customer Asset".CustomerAssetId; + DataClassification = SystemMetadata; + } + field(41; ParentAsset; GUID) + { + ExternalName = 'msdyn_parentasset'; + ExternalType = 'Lookup'; + Description = 'Parent Asset'; + Caption = 'Parent Asset'; + TableRelation = "FS Customer Asset".CustomerAssetId; + DataClassification = SystemMetadata; + } + field(42; Product; GUID) + { + ExternalName = 'msdyn_product'; + ExternalType = 'Lookup'; + Description = 'Reference to Product associated with this Asset'; + Caption = 'Product'; + TableRelation = "CRM Product".ProductId; + DataClassification = SystemMetadata; + } + field(46; MasterAssetName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Customer Asset".Name where(CustomerAssetId = field(MasterAsset))); + ExternalName = 'msdyn_masterassetname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(47; ParentAssetName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Customer Asset".Name where(CustomerAssetId = field(ParentAsset))); + ExternalName = 'msdyn_parentassetname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(48; CustomerAssetCategoryName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Customer Asset Category".Name where(CustomerAssetCategoryId = field(CustomerAssetCategory))); + ExternalName = 'msdyn_customerassetcategoryname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(50; DeviceId; Text[100]) + { + ExternalName = 'msdyn_deviceid'; + ExternalType = 'String'; + Description = 'Device ID used to register with the IoT provider. This will not be used if there are two or more connected devices for this asset. This value will be updated based on the connected devices.'; + Caption = 'Device ID'; + DataClassification = SystemMetadata; + } + field(52; LastCommandSentTime; Date) + { + ExternalName = 'msdyn_lastcommandsenttime'; + ExternalType = 'DateTime'; + Description = 'The timestamp of the last command sent for any of the connected devices for this asset.'; + Caption = 'Last Command Sent Time'; + DataClassification = SystemMetadata; + } + field(53; RegistrationStatus; Option) + { + ExternalName = 'msdyn_registrationstatus'; + ExternalType = 'Picklist'; + Description = 'A status field that denotes whether all the devices connected to this asset are registered with the IoT provider.'; + Caption = 'Registration Status'; + InitValue = " "; + OptionMembers = " ",Unknown,Unregistered,InProgress,Registered,Error; + OptionOrdinalValues = -1, 192350000, 192350001, 192350002, 192350003, 192350004; + DataClassification = SystemMetadata; + } + field(56; Alert; Boolean) + { + ExternalName = 'msdyn_alert'; + ExternalType = 'Boolean'; + ExternalAccess = Read; + Description = 'If active parent alerts exist for the customer asset'; + Caption = 'Active or in-progress alerts'; + DataClassification = SystemMetadata; + } + field(58; AlertCount; Integer) + { + ExternalName = 'msdyn_alertcount'; + ExternalType = 'Integer'; + ExternalAccess = Read; + Description = 'Count of parent alerts for this customer asset'; + Caption = 'Alert Count'; + DataClassification = SystemMetadata; + } + field(59; AalertCount_Date; Datetime) + { + ExternalName = 'msdyn_alertcount_date'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Last Updated time of rollup field Alert Count.'; + Caption = 'Alert Count (Last Updated On)'; + DataClassification = SystemMetadata; + } + field(60; AlertCount_State; Integer) + { + ExternalName = 'msdyn_alertcount_state'; + ExternalType = 'Integer'; + ExternalAccess = Read; + Description = 'State of rollup field Alert Count.'; + Caption = 'Alert Count (State)'; + DataClassification = SystemMetadata; + } + field(61; LastAlertTime; Datetime) + { + ExternalName = 'msdyn_lastalerttime'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = ''; + Caption = 'Last active alert time'; + DataClassification = SystemMetadata; + } + field(62; LastAlertTime_Date; Datetime) + { + ExternalName = 'msdyn_lastalerttime_date'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Last Updated time of rollup field Last active alert time.'; + Caption = 'Last active alert time (Last Updated On)'; + DataClassification = SystemMetadata; + } + field(63; LastAlertTime_State; Integer) + { + ExternalName = 'msdyn_lastalerttime_state'; + ExternalType = 'Integer'; + ExternalAccess = Read; + Description = 'State of rollup field Last active alert time.'; + Caption = 'Last active alert time (State)'; + DataClassification = SystemMetadata; + } + field(64; AssetTag; Text[100]) + { + ExternalName = 'msdyn_assettag'; + ExternalType = 'String'; + Description = ''; + Caption = 'Asset Tag'; + DataClassification = SystemMetadata; + } + field(66; WorkOrderProduct; GUID) + { + ExternalName = 'msdyn_workorderproduct'; + ExternalType = 'Lookup'; + Description = 'Indicates a link to the Work Order Product from where this Asset was auto created by the system.'; + Caption = 'Work Order Product'; + TableRelation = "FS Work Order Product".WorkOrderProductId; + DataClassification = SystemMetadata; + } + field(67; WorkOrderProductName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order Product".Name where(WorkOrderProductId = field(WorkOrderProduct))); + ExternalName = 'msdyn_workorderproductname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(68; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; customerassetId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAssetCategory.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAssetCategory.Table.al new file mode 100644 index 0000000000..07936c2617 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSCustomerAssetCategory.Table.al @@ -0,0 +1,214 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6614 "FS Customer Asset Category" +{ + ExternalName = 'msdyn_customerassetcategory'; + TableType = CRM; + Description = 'The Category of Customer Asset.'; + DataClassification = SystemMetadata; + + fields + { + field(1; CustomerAssetCategoryId; GUID) + { + ExternalName = 'msdyn_customerassetcategoryid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Unique identifier for entity instances'; + Caption = 'Customer Asset Category'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who created the record.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who modified the record.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Customer Asset Category'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Customer Asset Category'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[100]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'The name of the custom entity.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; CustomerAssetCategoryId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSProjectTask.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSProjectTask.Table.al new file mode 100644 index 0000000000..838b8f7f89 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSProjectTask.Table.al @@ -0,0 +1,259 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6615 "FS Project Task" +{ + ExternalName = 'bcbi_projecttask'; + TableType = CRM; + Description = 'An entity for storing the Microsoft Dynamics 365 Business Central project task.'; + DataClassification = SystemMetadata; + + fields + { + field(1; ProjectTaskId; GUID) + { + ExternalName = 'bcbi_projecttaskid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Unique identifier for entity instances.'; + Caption = 'Business Central Project Task'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who created the record.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM SystemUser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who modified the record.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM SystemUser".SystemUserId; + DataClassification = SystemMetadata; + } + field(8; CreatedByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM SystemUser".FullName where(SystemUserId = field(CreatedBy))); + ExternalName = 'createdbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(10; CreatedOnBehalfByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM SystemUser".FullName where(SystemUserId = field(CreatedOnBehalfBy))); + ExternalName = 'createdonbehalfbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(12; ModifiedByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(ModifiedBy))); + ExternalName = 'modifiedbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(14; ModifiedOnBehalfByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(ModifiedOnBehalfBy))); + ExternalName = 'modifiedonbehalfbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(18; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Business Central Project Task'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(20; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Business Central Project Task'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(22; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(23; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(24; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(25; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(26; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(27; Description; Text[250]) + { + ExternalName = 'bcbi_projecttaskdescription'; + ExternalType = 'String'; + Description = 'Business Central Project Task Description'; + Caption = 'Business Central Project Task Description'; + DataClassification = SystemMetadata; + } + field(28; ProjectNumber; Text[250]) + { + ExternalName = 'bcbi_projectnumber'; + ExternalType = 'String'; + Description = 'Business Central Project Number'; + Caption = 'Business Central Project Number'; + DataClassification = SystemMetadata; + } + field(29; ProjectTaskNumber; Text[250]) + { + ExternalName = 'bcbi_projecttasknumber'; + ExternalType = 'String'; + Description = 'Business Central Project Task Number'; + Caption = 'Business Central Project Task Number'; + DataClassification = SystemMetadata; + } + field(30; ProjectDescription; Text[250]) + { + ExternalName = 'bcbi_projectdescription'; + ExternalType = 'String'; + Description = 'Business Central Project Description'; + Caption = 'Business Central Project Description'; + DataClassification = SystemMetadata; + } + field(31; ServiceAccountId; GUID) + { + ExternalName = 'bcbi_serviceaccountid'; + ExternalType = 'Lookup'; + Description = 'Account to be serviced'; + Caption = 'Service Account'; + TableRelation = "CRM Account".AccountId; + DataClassification = SystemMetadata; + } + field(32; BillingAccountId; GUID) + { + ExternalName = 'bcbi_billingaccountid'; + ExternalType = 'Lookup'; + Description = 'Account to be billed'; + Caption = 'Billing Account'; + TableRelation = "CRM Account".AccountId; + DataClassification = SystemMetadata; + } + field(33; CompanyId; GUID) + { + ExternalName = 'bcbi_companyid'; + ExternalType = 'Lookup'; + Description = 'The unique identifier of the company associated with the project task.'; + Caption = 'Company'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; ProjectTaskId) + { + Clustered = true; + } + key(Name; Description) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Description) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSResourcePayType.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSResourcePayType.Table.al new file mode 100644 index 0000000000..ac2279ceee --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSResourcePayType.Table.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.D365Sales; + +table 6616 "FS Resource Pay Type" +{ + ExternalName = 'msdyn_resourcepaytype'; + TableType = CRM; + Description = 'Pay Types of resources hourly rate to calculate the resource cost'; + DataClassification = SystemMetadata; + + fields + { + field(1; ResourcePayTypeId; GUID) + { + ExternalName = 'msdyn_resourcepaytypeid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'Resource Pay Type'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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; + } + field(4; ModifiedOn; Datetime) + { + ExternalName = 'modifiedon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(8; CreatedByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(CreatedBy))); + ExternalName = 'createdbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(10; CreatedOnBehalfByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(CreatedOnBehalfBy))); + ExternalName = 'createdonbehalfbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(12; ModifiedByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(ModifiedBy))); + ExternalName = 'modifiedbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(14; ModifiedOnBehalfByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(ModifiedOnBehalfBy))); + ExternalName = 'modifiedonbehalfbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM BusinessUnit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(24; OwningBusinessUnitName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM BusinessUnit".Name where(BusinessUnitId = field(OwningBusinessUnit))); + ExternalName = 'owningbusinessunitname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(25; statecode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Resource Pay Type'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; statuscode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Resource Pay Type'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[200]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Enter the resource pay type name.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(35; msdyn_HourlyMarkup; Decimal) + { + ExternalName = 'msdyn_hourlymarkup'; + ExternalType = 'Double'; + Description = 'Enter the markup percentage on the resource hourly cost. Use a value greater than 100% to mark it up and a value less than 100% to mark it down.'; + Caption = 'Hourly Markup %'; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; ResourcePayTypeId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al new file mode 100644 index 0000000000..a36c232f06 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWarehouse.Table.al @@ -0,0 +1,222 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6624 "FS Warehouse" +{ + ExternalName = 'msdyn_warehouse'; + TableType = CRM; + Description = 'An entity for storing the Microsoft Dynamics 365 Business Central location.'; + DataClassification = SystemMetadata; + + fields + { + field(1; WarehouseId; Guid) + { + Caption = 'Warehouse'; + Description = 'Unique identifier of the warehouse.'; + ExternalAccess = Insert; + ExternalName = 'msdyn_warehouseid'; + ExternalType = 'Uniqueidentifier'; + } + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who created the record.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM SystemUser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier of the delegate user who modified the record.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM SystemUser".SystemUserId; + DataClassification = SystemMetadata; + } + field(8; CreatedByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM SystemUser".FullName where(SystemUserId = field(CreatedBy))); + ExternalName = 'createdbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(10; CreatedOnBehalfByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM SystemUser".FullName where(SystemUserId = field(CreatedOnBehalfBy))); + ExternalName = 'createdonbehalfbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(12; ModifiedByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(ModifiedBy))); + ExternalName = 'modifiedbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(14; ModifiedOnBehalfByName; Text[200]) + { + FieldClass = FlowField; + CalcFormula = lookup("CRM Systemuser".FullName where(SystemUserId = field(ModifiedOnBehalfBy))); + ExternalName = 'modifiedonbehalfbyname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(18; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Business Central Location'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(20; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Business Central Location'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(22; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(23; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(24; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(25; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(26; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(27; Name; Text[20]) + { + Caption = 'Name'; + Description = 'Type location code.'; + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + } + field(28; Description; Text[100]) + { + Caption = 'Description'; + Description = 'Type location description.'; + ExternalName = 'msdyn_description'; + ExternalType = 'String'; + } + field(33; CompanyId; GUID) + { + ExternalName = 'bcbi_companyid'; + ExternalType = 'Lookup'; + Description = 'The unique identifier of the company associated with the location.'; + Caption = 'Company'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; WarehouseId) + { + Clustered = true; + } + key(Name; Description) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Description) + { + } + } +} \ 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 new file mode 100644 index 0000000000..fa94ea702b --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrder.Table.al @@ -0,0 +1,906 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6617 "FS Work Order" +{ + ExternalName = 'msdyn_workorder'; + TableType = CRM; + Description = 'Work orders store all information about the job performed for an account. Stores incident details, resource, expenses, tasks, communications, billing and more'; + DataClassification = SystemMetadata; + + fields + { + field(1; WorkOrderId; GUID) + { + ExternalName = 'msdyn_workorderId'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'WO Number'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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 = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Work Order'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Work Order'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[100]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Enter the name of the custom entity.'; + Caption = 'Work Order Number'; + DataClassification = SystemMetadata; + } + field(35; ProcessId; GUID) + { + ExternalName = 'processid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Shows the ID of the process associated with the entity.'; + Caption = 'Process Id'; + DataClassification = SystemMetadata; + } + field(36; StageId; GUID) + { + ExternalName = 'stageid'; + ExternalType = 'Uniqueidentifier'; + Description = 'Shows the ID of the stage where the entity is located.'; + Caption = 'Stage Id'; + DataClassification = SystemMetadata; + } + field(37; TraversedPath; Text[1250]) + { + ExternalName = 'traversedpath'; + ExternalType = 'String'; + Description = 'Shows a comma-separated list of string values representing the unique identifiers of stages in a business process flow instance in the order that they occur.'; + Caption = 'Traversed Path'; + DataClassification = SystemMetadata; + } + field(38; Address1; Text[250]) + { + ExternalName = 'msdyn_address1'; + ExternalType = 'String'; + Caption = 'Street 1'; + DataClassification = SystemMetadata; + } + field(39; Address2; Text[250]) + { + ExternalName = 'msdyn_address2'; + ExternalType = 'String'; + Caption = 'Street 2'; + DataClassification = SystemMetadata; + } + field(40; Address3; Text[250]) + { + ExternalName = 'msdyn_address3'; + ExternalType = 'String'; + Caption = 'Street 3'; + DataClassification = SystemMetadata; + } + field(41; AddressName; Text[250]) + { + ExternalName = 'msdyn_addressname'; + ExternalType = 'String'; + Caption = 'Address Name'; + DataClassification = SystemMetadata; + } + field(43; AutoNumbering; Text[100]) + { + ExternalName = 'msdyn_autonumbering'; + ExternalType = 'String'; + Description = 'Internal field used to generate the next name upon entity creation. It is optionally copied to the msdyn_name field.'; + Caption = 'Auto-Numbering'; + DataClassification = SystemMetadata; + } + field(44; BillingAccount; GUID) + { + ExternalName = 'msdyn_billingaccount'; + ExternalType = 'Lookup'; + Description = 'Account to be billed. If a billing account has been set on service account it will be populated by default. Otherwise, the billing account will be the same as the service account.'; + Caption = 'Billing Account'; + TableRelation = "CRM Account".AccountId; + DataClassification = SystemMetadata; + } + field(45; BookingSummary; BLOB) + { + ExternalName = 'msdyn_bookingsummary'; + ExternalType = 'Memo'; + Description = 'For internal use only.'; + Caption = 'Booking Summary'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(46; ChildIndex; Integer) + { + ExternalName = 'msdyn_childindex'; + ExternalType = 'Integer'; + Caption = 'Child Index'; + DataClassification = SystemMetadata; + } + field(47; City; Text[80]) + { + ExternalName = 'msdyn_city'; + ExternalType = 'String'; + Caption = 'City'; + DataClassification = SystemMetadata; + } + field(48; ClosedBy; GUID) + { + ExternalName = 'msdyn_closedby'; + ExternalType = 'Lookup'; + Description = 'The user that last closed this work order'; + Caption = 'Closed By'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(49; Country; Text[80]) + { + ExternalName = 'msdyn_country'; + ExternalType = 'String'; + Caption = 'Country/Region'; + DataClassification = SystemMetadata; + } + field(50; CustomerAsset; GUID) + { + ExternalName = 'msdyn_customerasset'; + ExternalType = 'Lookup'; + Description = 'Customer Asset related to this incident reported'; + Caption = 'Primary Incident Customer Asset'; + TableRelation = "FS Customer Asset".CustomerAssetId; + DataClassification = SystemMetadata; + } + field(51; DateWindowEnd; Date) + { + ExternalName = 'msdyn_datewindowend'; + ExternalType = 'DateTime'; + Caption = 'Date Window End'; + DataClassification = SystemMetadata; + } + field(52; DateWindowStart; Date) + { + ExternalName = 'msdyn_datewindowstart'; + ExternalType = 'DateTime'; + Caption = 'Date Window Start'; + DataClassification = SystemMetadata; + } + field(53; EstimateSubtotalAmount; Decimal) + { + ExternalName = 'msdyn_estimatesubtotalamount'; + ExternalType = 'Money'; + Description = 'Enter the summary of total estimated billing amount for this work order'; + Caption = 'Estimate Subtotal Amount'; + DataClassification = SystemMetadata; + } + field(54; TransactionCurrencyId; GUID) + { + ExternalName = 'transactioncurrencyid'; + ExternalType = 'Lookup'; + Description = 'Unique identifier of the currency associated with the entity.'; + Caption = 'Currency'; + TableRelation = "CRM Transactioncurrency".TransactionCurrencyId; + DataClassification = SystemMetadata; + } + field(56; ExchangeRate; Decimal) + { + ExternalName = 'exchangerate'; + ExternalType = 'Decimal'; + ExternalAccess = Read; + Description = 'Shows the exchange rate for the currency associated with the entity with respect to the base currency.'; + Caption = 'Exchange Rate'; + DataClassification = SystemMetadata; + } + field(57; EstimateSubtotalAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimatesubtotalamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate subtotal amount in the base currency.'; + Caption = 'Estimate Subtotal Amount (Base)'; + DataClassification = SystemMetadata; + } + field(58; FollowUpNote; BLOB) + { + ExternalName = 'msdyn_followupnote'; + ExternalType = 'Memo'; + Description = 'Indicate the details of the follow up work'; + Caption = 'Follow Up Note (Deprecated)'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(59; FollowUpRequired; Boolean) + { + ExternalName = 'msdyn_followuprequired'; + ExternalType = 'Boolean'; + Description = 'Allows indication if follow up work is required for a work order.'; + Caption = 'Follow Up Required (Deprecated)'; + DataClassification = SystemMetadata; + } + field(61; Instructions; BLOB) + { + ExternalName = 'msdyn_instructions'; + ExternalType = 'Memo'; + Description = 'Shows instructions for booked resources. By default, this information is taken from the work order instructions field on the service account.'; + Caption = 'Instructions'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(62; InternalFlags; BLOB) + { + ExternalName = 'msdyn_internalflags'; + ExternalType = 'Memo'; + Description = 'For internal use only.'; + Caption = 'Internal Flags'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(63; IsFollowUp; Boolean) + { + ExternalName = 'msdyn_isfollowup'; + ExternalType = 'Boolean'; + Caption = 'Is FollowUp (Deprecated)'; + DataClassification = SystemMetadata; + } + field(65; IsMobile; Boolean) + { + ExternalName = 'msdyn_ismobile'; + ExternalType = 'Boolean'; + Caption = 'Is Mobile'; + DataClassification = SystemMetadata; + } + field(67; Latitude; Decimal) + { + ExternalName = 'msdyn_latitude'; + ExternalType = 'Double'; + Description = ''; + Caption = 'Latitude'; + DataClassification = SystemMetadata; + } + field(68; Longitude; Decimal) + { + ExternalName = 'msdyn_longitude'; + ExternalType = 'Double'; + Description = ''; + Caption = 'Longitude'; + DataClassification = SystemMetadata; + } + field(69; MapControl; Text[100]) + { + ExternalName = 'msdyn_mapcontrol'; + ExternalType = 'String'; + ExternalAccess = Read; + Description = ''; + Caption = 'This field should only be used to load the custom map control'; + DataClassification = SystemMetadata; + } + field(70; OpportunityId; GUID) + { + ExternalName = 'msdyn_opportunityid'; + ExternalType = 'Lookup'; + Description = 'Unique identifier for Opportunity associated with Work Order.'; + Caption = 'Opportunity'; + TableRelation = "CRM Opportunity".OpportunityId; + DataClassification = SystemMetadata; + } + field(71; ParentWorkOrder; GUID) + { + ExternalName = 'msdyn_parentworkorder'; + ExternalType = 'Lookup'; + Description = 'Unique identifier for Work Order associated with Work Order.'; + Caption = 'Parent Work Order'; + TableRelation = "FS Work Order".WorkOrderId; + DataClassification = SystemMetadata; + } + field(72; PostalCode; Text[20]) + { + ExternalName = 'msdyn_postalcode'; + ExternalType = 'String'; + Caption = 'Postal Code'; + DataClassification = SystemMetadata; + } + field(73; PreferredResource; GUID) + { + ExternalName = 'msdyn_preferredresource'; + ExternalType = 'Lookup'; + Description = 'The customer Preferred Resource to work on this job. Should be taken into consideration while scheduling resources'; + Caption = 'Preferred Resource (Deprecated)'; + TableRelation = "FS Bookable Resource".BookableResourceId; + DataClassification = SystemMetadata; + } + field(74; PriceList; GUID) + { + ExternalName = 'msdyn_pricelist'; + ExternalType = 'Lookup'; + Description = 'Price List that controls pricing for products / services added to this work order. By default the system will use the Price List specified on the account'; + Caption = 'Price List'; + TableRelation = "CRM Pricelevel".PriceLevelId; + DataClassification = SystemMetadata; + } + field(75; PrimaryIncidentDescription; BLOB) + { + ExternalName = 'msdyn_primaryincidentdescription'; + ExternalType = 'Memo'; + Description = 'Incident description'; + Caption = 'Primary Incident Description'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(76; PrimaryIncidentEstimatedDuration; Integer) + { + ExternalName = 'msdyn_primaryincidentestimatedduration'; + ExternalType = 'Integer'; + Description = 'Shows the time estimated to resolve this incident.'; + Caption = 'Primary Incident Estimated Duration'; + DataClassification = SystemMetadata; + } + field(79; ReportedByContact; GUID) + { + ExternalName = 'msdyn_reportedbycontact'; + ExternalType = 'Lookup'; + Description = 'The contact that reported this Work Order'; + Caption = 'Reported By Contact'; + TableRelation = "CRM Contact".ContactId; + DataClassification = SystemMetadata; + } + field(80; ServiceAccount; GUID) + { + ExternalName = 'msdyn_serviceaccount'; + ExternalType = 'Lookup'; + Description = 'Account to be serviced'; + Caption = 'Service Account'; + TableRelation = "CRM Account".AccountId; + DataClassification = SystemMetadata; + } + field(81; ServiceRequest; GUID) + { + ExternalName = 'msdyn_servicerequest'; + ExternalType = 'Lookup'; + Description = 'Case of which this work order originates from'; + Caption = 'Case'; + TableRelation = "CRM Incident".IncidentId; + DataClassification = SystemMetadata; + } + field(83; StateOrProvince; Text[50]) + { + ExternalName = 'msdyn_stateorprovince'; + ExternalType = 'String'; + Caption = 'State Or Province'; + DataClassification = SystemMetadata; + } + field(84; SubStatus; GUID) + { + ExternalName = 'msdyn_substatus'; + ExternalType = 'Lookup'; + Description = 'Work Order subsstatus'; + Caption = 'Substatus'; + TableRelation = "FS Work Order Substatus".WorkOrderSubstatusId; + DataClassification = SystemMetadata; + } + field(85; SubtotalAmount; Decimal) + { + ExternalName = 'msdyn_subtotalamount'; + ExternalType = 'Money'; + Description = 'Enter the summary of subtotal billing amount excluding tax for this work order.'; + Caption = 'Subtotal Amount'; + DataClassification = SystemMetadata; + } + field(86; SubTotalAmount_Base; Decimal) + { + ExternalName = 'msdyn_subtotalamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the subtotal amount in the base currency.'; + Caption = 'Subtotal Amount (Base)'; + DataClassification = SystemMetadata; + } + field(87; SupportContact; GUID) + { + ExternalName = 'msdyn_supportcontact'; + ExternalType = 'Lookup'; + Description = 'A support contact can be specified so that the individual working on the work order has someone to contact for assistance.'; + Caption = 'Support Contact'; + TableRelation = "FS Bookable Resource".BookableResourceId; + DataClassification = SystemMetadata; + } + field(88; SystemStatus; Option) + { + ExternalName = 'msdyn_systemstatus'; + ExternalType = 'Picklist'; + Description = 'Tracks the current system status.'; + Caption = 'System Status'; + InitValue = " "; + OptionMembers = " ",Unscheduled,Scheduled,InProgress,Completed,Posted,Canceled; + OptionOrdinalValues = -1, 690970000, 690970001, 690970002, 690970003, 690970004, 690970005; + DataClassification = SystemMetadata; + } + field(90; Taxable; Boolean) + { + ExternalName = 'msdyn_taxable'; + ExternalType = 'Boolean'; + Description = 'Shows whether sales tax is to be charged for this work order.'; + Caption = 'Taxable'; + DataClassification = SystemMetadata; + } + field(93; TimeClosed; Datetime) + { + ExternalName = 'msdyn_timeclosed'; + ExternalType = 'DateTime'; + Description = 'Enter the time this work order was last closed.'; + Caption = 'Closed On'; + DataClassification = SystemMetadata; + } + field(94; TimeFromPromised; Datetime) + { + ExternalName = 'msdyn_timefrompromised'; + ExternalType = 'DateTime'; + Description = 'Enter the starting range of the time promised to the account that incidents will be resolved.'; + Caption = 'Time From Promised'; + DataClassification = SystemMetadata; + } + field(97; TimeToPromised; Datetime) + { + ExternalName = 'msdyn_timetopromised'; + ExternalType = 'DateTime'; + Description = 'Enter the ending range of the time promised to the account that incidents will be resolved.'; + Caption = 'Time To Promised'; + DataClassification = SystemMetadata; + } + field(98; TimeWindowEnd; Datetime) + { + ExternalName = 'msdyn_timewindowend'; + ExternalType = 'DateTime'; + Caption = 'Time Window End'; + DataClassification = SystemMetadata; + } + field(99; TimeWindowStart; Datetime) + { + ExternalName = 'msdyn_timewindowstart'; + ExternalType = 'DateTime'; + Caption = 'Time Window Start'; + DataClassification = SystemMetadata; + } + field(100; TotalAmount; Decimal) + { + ExternalName = 'msdyn_totalamount'; + ExternalType = 'Money'; + Description = 'Enter the summary of total billing amount for this work order.'; + Caption = 'Total Amount'; + DataClassification = SystemMetadata; + } + field(101; TotalAmount_Base; Decimal) + { + ExternalName = 'msdyn_totalamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the total amount in the base currency.'; + Caption = 'Total Amount (Base)'; + DataClassification = SystemMetadata; + } + field(102; TotalSalesTax; Decimal) + { + ExternalName = 'msdyn_totalsalestax'; + ExternalType = 'Money'; + Description = 'Enter the summary of total sales tax charged for this work order.'; + Caption = 'Total Sales Tax'; + DataClassification = SystemMetadata; + } + field(103; TotalSalesTax_Base; Decimal) + { + ExternalName = 'msdyn_totalsalestax_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the total sales tax in the base currency.'; + Caption = 'Total Sales Tax (Base)'; + DataClassification = SystemMetadata; + } + field(105; WorkLocation; Option) + { + ExternalName = 'msdyn_worklocation'; + ExternalType = 'Picklist'; + Caption = 'Work Location'; + InitValue = Onsite; + OptionMembers = Onsite,Facility,LocationAgnostic; + OptionOrdinalValues = 690970000, 690970001, 690970002; + DataClassification = SystemMetadata; + } + field(109; WorkOrderSummary; BLOB) + { + ExternalName = 'msdyn_workordersummary'; + ExternalType = 'Memo'; + Description = 'Type a summary description of the job.'; + Caption = 'Work Order Summary'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(110; WorkOrderType; GUID) + { + ExternalName = 'msdyn_workordertype'; + ExternalType = 'Lookup'; + Description = 'Work Order Type'; + Caption = 'Work Order Type'; + TableRelation = "FS Work Order Type".WorkOrderTypeId; + DataClassification = SystemMetadata; + } + field(115; PreferredResourceName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource".Name where(BookableResourceId = field(PreferredResource))); + ExternalName = 'msdyn_preferredresourcename'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(116; SupportContactName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource".Name where(BookableResourceId = field(SupportContact))); + ExternalName = 'msdyn_supportcontactname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(121; CustomerAssetName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Customer Asset".Name where(CustomerAssetId = field(CustomerAsset))); + ExternalName = 'msdyn_customerassetname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(128; ParentWorkOrderName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order".Name where(WorkOrderId = field(ParentWorkOrder))); + ExternalName = 'msdyn_parentworkordername'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(129; SubStatusName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order Substatus".Name where(WorkOrderSubstatusId = field(SubStatus))); + ExternalName = 'msdyn_substatusname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(130; WorkOrderTypeName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order Type".Name where(WorkOrderTypeId = field(WorkOrderType))); + ExternalName = 'msdyn_workordertypename'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(138; completedon; Datetime) + { + ExternalName = 'msdyn_completedon'; + ExternalType = 'DateTime'; + Description = 'When Bookings are used on a Work Order, this field is auto-populated based on the latest End Time from the related Bookings. Otherwise, this field is populated based on the change of System Status.'; + Caption = 'Completed On'; + DataClassification = SystemMetadata; + } + field(139; CostNTEPercent; Integer) + { + ExternalName = 'msdyn_costntepercent'; + ExternalType = 'Integer'; + Description = 'Indicates the percentage proximity or overage of the work order cost based on applied Cost not-to-exceed (rounded up to the nearest whole number).'; + Caption = 'Cost not-to-exceed'; + DataClassification = SystemMetadata; + } + field(140; firstarrivedon; Datetime) + { + ExternalName = 'msdyn_firstarrivedon'; + ExternalType = 'DateTime'; + Description = 'When Bookings are used on a Work Order, this field is auto-populated based on the earliest Actual Arrival Time from the related Bookings.'; + Caption = 'First Arrived On'; + DataClassification = SystemMetadata; + } + field(142; NotToExceedCostAmount; Decimal) + { + ExternalName = 'msdyn_nottoexceedcostamount'; + ExternalType = 'Money'; + Description = 'The value of not-to-exceed cost for the work order in base currency.'; + Caption = 'Cost not-to-exceed'; + DataClassification = SystemMetadata; + } + field(143; NotToExceedCostAmount_Base; Decimal) + { + ExternalName = 'msdyn_nottoexceedcostamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Value of the Cost not-to-exceed in base currency.'; + Caption = 'Cost not-to-exceed (Base)'; + DataClassification = SystemMetadata; + } + field(144; NotToExceedPriceAmount; Decimal) + { + ExternalName = 'msdyn_nottoexceedpriceamount'; + ExternalType = 'Money'; + Description = 'The value of not-to-exceed price for the work order in base currency.'; + Caption = 'Price not-to-exceed'; + DataClassification = SystemMetadata; + } + field(145; NotToExceedPriceAmount_Base; Decimal) + { + ExternalName = 'msdyn_nottoexceedpriceamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Value of the Price not-to-exceed in base currency.'; + Caption = 'Price not-to-exceed (Base)'; + DataClassification = SystemMetadata; + } + field(146; PhoneNumber; Text[250]) + { + ExternalName = 'msdyn_phoneNumber'; + ExternalType = 'String'; + Caption = 'Customer Phone Number'; + DataClassification = SystemMetadata; + } + field(147; PriceNTEPercent; Integer) + { + ExternalName = 'msdyn_pricentepercent'; + ExternalType = 'Integer'; + Description = 'Indicates the percentage proximity or overage of the work order price based on applied Price not-to-exceed (rounded up to the nearest whole number).'; + Caption = 'Price not-to-exceed'; + DataClassification = SystemMetadata; + } + field(149; ProductsServicesCost; Decimal) + { + ExternalName = 'msdyn_productsservicescost'; + ExternalType = 'Money'; + Description = 'The total actual cost of the products and services'; + Caption = 'Total Cost'; + DataClassification = SystemMetadata; + } + field(150; ProductsServicesCost_Base; Decimal) + { + ExternalName = 'msdyn_productsservicescost_base'; + ExternalType = 'Money'; + Description = 'Value of the Total Cost in base currency.'; + Caption = 'Total Cost (Base)'; + DataClassification = SystemMetadata; + } + field(151; ProductsServicesEstimatedCost; Decimal) + { + ExternalName = 'msdyn_productsservicesestimatedcost'; + ExternalType = 'Money'; + Description = 'The total estimated cost of the products and services'; + Caption = 'Total Estimated Cost'; + DataClassification = SystemMetadata; + } + field(152; ProductsServicesEstimatedCost_Base; Decimal) + { + ExternalName = 'msdyn_productsservicesestimatedcost_base'; + ExternalType = 'Money'; + Description = 'Value of the Total Estimated Cost in base currency.'; + Caption = 'Total Estimated Cost (Base)'; + DataClassification = SystemMetadata; + } + field(153; TotalEstimatedAfterTaxPrice; Decimal) + { + ExternalName = 'msdyn_totalestimatedaftertaxprice'; + ExternalType = 'Money'; + Description = 'The estimated price after adding tax to the subtotal'; + Caption = 'Total Estimated After Tax Price'; + DataClassification = SystemMetadata; + } + field(154; TotalEstimatedAfterTaxPrice_Base; Decimal) + { + ExternalName = 'msdyn_totalestimatedaftertaxprice_base'; + ExternalType = 'Money'; + Description = 'Value of the Total Estimated After Tax Price in base currency.'; + Caption = 'Total Estimated After Tax Price (Base)'; + DataClassification = SystemMetadata; + } + field(155; TotalEstimatedDuration; Integer) + { + ExternalName = 'msdyn_totalestimatedduration'; + ExternalType = 'Integer'; + Description = 'Calculated from the estimated duration of Work Order Incidents and Work Order Service Tasks not related to a Work Order Incident on the Work Order. Intended to be read-only.'; + Caption = 'Total Estimated Duration'; + DataClassification = SystemMetadata; + } + field(162; DisplayAddress; Text[2048]) + { + ExternalName = 'msdyn_displayaddress'; + ExternalType = 'String'; + ExternalAccess = Read; + Description = 'Combined address field suitable for display'; + Caption = 'Display Address'; + DataClassification = SystemMetadata; + } + field(163; ProjectTask; GUID) + { + ExternalName = 'bcbi_businesscentralprojecttask'; + ExternalType = 'Lookup'; + Description = 'Business Central Project Task'; + Caption = 'Business Central Project Task'; + TableRelation = "FS Project Task".ProjectTaskId; + DataClassification = SystemMetadata; + } + field(164; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; WorkOrderId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al new file mode 100644 index 0000000000..6837e3621c --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderIncident.Table.al @@ -0,0 +1,312 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6618 "FS Work Order Incident" +{ + ExternalName = 'msdyn_workorderincident'; + TableType = CRM; + Description = 'Specify work order incidents reported to you by the client. These are also referred to as problem codes.'; + DataClassification = SystemMetadata; + + fields + { + field(1; WorkOrderIncidentId; GUID) + { + ExternalName = 'msdyn_workorderincidentid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'Work Order Incident'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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 = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Work Order Incident'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Work Order Incident'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[100]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Enter the name of the custom entity.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(36; CustomerAsset; GUID) + { + ExternalName = 'msdyn_customerasset'; + ExternalType = 'Lookup'; + Description = 'Customer Asset related to this incident reported'; + Caption = 'Customer Asset'; + TableRelation = "FS Customer Asset".CustomerAssetId; + DataClassification = SystemMetadata; + } + field(37; Description; BLOB) + { + ExternalName = 'msdyn_description'; + ExternalType = 'Memo'; + Description = 'Incident description'; + Caption = 'Description'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(38; EstimatedDuration; Integer) + { + ExternalName = 'msdyn_estimatedduration'; + ExternalType = 'Integer'; + Description = 'Shows the time estimated to resolve this incident.'; + Caption = 'Estimated Duration'; + DataClassification = SystemMetadata; + } + field(39; IncidentResolved; Boolean) + { + ExternalName = 'msdyn_incidentresolved'; + ExternalType = 'Boolean'; + Description = 'Shows if the incident has been resolved by one of its related tasks.'; + Caption = 'Incident Resolved'; + DataClassification = SystemMetadata; + } + field(42; InternalFlags; BLOB) + { + ExternalName = 'msdyn_internalflags'; + ExternalType = 'Memo'; + Description = 'For internal use only.'; + Caption = 'Internal Flags'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(43; IsMobile; Boolean) + { + ExternalName = 'msdyn_ismobile'; + ExternalType = 'Boolean'; + Caption = 'Is Mobile'; + DataClassification = SystemMetadata; + } + field(45; IsPrimary; Boolean) + { + ExternalName = 'msdyn_isprimary'; + ExternalType = 'Boolean'; + Description = ''; + Caption = 'Is Primary'; + DataClassification = SystemMetadata; + } + field(47; ItemsPopulated; Boolean) + { + ExternalName = 'msdyn_itemspopulated'; + ExternalType = 'Boolean'; + Caption = 'Items Populated'; + DataClassification = SystemMetadata; + } + field(50; TasksPercentCompleted; Decimal) + { + ExternalName = 'msdyn_taskspercentcompleted'; + ExternalType = 'Double'; + Description = 'Shows the percent completed on associated tasks. This indicates the total of completed tasks, but not if the incident was resolved.'; + Caption = 'Tasks % Completed'; + DataClassification = SystemMetadata; + } + field(51; WorkOrder; GUID) + { + ExternalName = 'msdyn_workorder'; + ExternalType = 'Lookup'; + Description = 'Parent Work Order where incident was reported on'; + Caption = 'Work Order'; + TableRelation = "FS Work Order".WorkOrderId; + DataClassification = SystemMetadata; + } + field(53; CustomerAssetName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Customer Asset".Name where(CustomerAssetId = field(CustomerAsset))); + ExternalName = 'msdyn_customerassetname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(56; WorkOrderName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order".Name where(WorkOrderId = field(WorkOrder))); + ExternalName = 'msdyn_workordername'; + ExternalType = 'String'; + ExternalAccess = Read; + } + } + keys + { + key(PK; WorkOrderIncidentId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al new file mode 100644 index 0000000000..aff2eaf9fd --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderProduct.Table.al @@ -0,0 +1,726 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6619 "FS Work Order Product" +{ + ExternalName = 'msdyn_workorderproduct'; + TableType = CRM; + Description = 'In this entity you record all the products proposed and used for a work order'; + DataClassification = SystemMetadata; + + fields + { + field(1; WorkOrderProductId; GUID) + { + ExternalName = 'msdyn_workorderproductid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'Work Order Product'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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 = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Work Order Product'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Work Order Product'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[200]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Enter the name of the custom entity.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(35; AdditionalCost; Decimal) + { + ExternalName = 'msdyn_additionalcost'; + ExternalType = 'Money'; + Description = 'Enter any additional costs associated with this product. The values are manually entered. Note: additional cost is not unit dependent.'; + Caption = 'Additional Cost'; + DataClassification = SystemMetadata; + } + field(36; TransactionCurrencyId; GUID) + { + ExternalName = 'transactioncurrencyid'; + ExternalType = 'Lookup'; + Description = 'Unique identifier of the currency associated with the entity.'; + Caption = 'Currency'; + TableRelation = "CRM Transactioncurrency".TransactionCurrencyId; + DataClassification = SystemMetadata; + } + field(38; ExchangeRate; Decimal) + { + ExternalName = 'exchangerate'; + ExternalType = 'Decimal'; + ExternalAccess = Read; + Description = 'Shows the exchange rate for the currency associated with the entity with respect to the base currency.'; + Caption = 'Exchange Rate'; + DataClassification = SystemMetadata; + } + field(39; additionalcost_Base; Decimal) + { + ExternalName = 'msdyn_additionalcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the additional cost in the base currency.'; + Caption = 'Additional Cost (Base)'; + DataClassification = SystemMetadata; + } + field(40; WarehouseId; GUID) + { + ExternalName = 'msdyn_warehouse'; + ExternalType = 'Lookup'; + Description = 'Unique identifier of the warehouse associated with the entity.'; + Caption = 'Warehouse'; + TableRelation = "FS Warehouse".WarehouseId; + DataClassification = SystemMetadata; + } + field(41; Allocated; Boolean) + { + ExternalName = 'msdyn_allocated'; + ExternalType = 'Boolean'; + Caption = 'Allocated'; + DataClassification = SystemMetadata; + } + field(43; Booking; GUID) + { + ExternalName = 'msdyn_booking'; + ExternalType = 'Lookup'; + Description = 'The booking where this product was added'; + Caption = 'Booking'; + TableRelation = "FS Bookable Resource Booking".BookableResourceBookingId; + DataClassification = SystemMetadata; + } + field(44; CommissionCosts; Decimal) + { + ExternalName = 'msdyn_commissioncosts'; + ExternalType = 'Money'; + Description = 'Enter the commission costs associated with this product. The value is manually specified and isn''t automatically calculated.'; + Caption = 'Commission Costs'; + DataClassification = SystemMetadata; + } + field(45; commissioncosts_Base; Decimal) + { + ExternalName = 'msdyn_commissioncosts_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the commission costs in the base currency.'; + Caption = 'Commission Costs (Base)'; + DataClassification = SystemMetadata; + } + field(46; CustomerAsset; GUID) + { + ExternalName = 'msdyn_customerasset'; + ExternalType = 'Lookup'; + Description = 'Unique identifier for Customer Asset associated with Work Order Product.'; + Caption = 'Customer Asset'; + TableRelation = "FS Customer Asset".CustomerAssetId; + DataClassification = SystemMetadata; + } + field(47; Description; BLOB) + { + ExternalName = 'msdyn_description'; + ExternalType = 'Memo'; + Description = 'Enter the description of the product as presented to the customer. The value defaults to the description defined on the product.'; + Caption = 'Description'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(48; DisableEntitlement; Boolean) + { + ExternalName = 'msdyn_disableentitlement'; + ExternalType = 'Boolean'; + Description = 'Choose whether to disable entitlement selection for this work order product.'; + Caption = 'Disable Entitlement'; + DataClassification = SystemMetadata; + } + field(50; DiscountAmount; Decimal) + { + ExternalName = 'msdyn_discountamount'; + ExternalType = 'Money'; + Description = 'Specify any discount amount on this product. Note: If you enter a discount amount you cannot enter a discount %'; + Caption = 'Discount Amount'; + DataClassification = SystemMetadata; + } + field(51; DiscountAmount_Base; Decimal) + { + ExternalName = 'msdyn_discountamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the discount amount in the base currency.'; + Caption = 'Discount Amount (Base)'; + DataClassification = SystemMetadata; + } + field(52; DiscountPercent; Decimal) + { + ExternalName = 'msdyn_discountpercent'; + ExternalType = 'Float'; + Description = 'Specify any discount % on this product. Note: If you enter a discount % it will overwrite the discount $'; + Caption = 'Discount %'; + DataClassification = SystemMetadata; + } + field(54; EstimateDiscountAmount; Decimal) + { + ExternalName = 'msdyn_estimatediscountamount'; + ExternalType = 'Money'; + Description = 'Enter a discount amount on the subtotal amount. Note: If you enter a discount amount you cannot enter a discount %'; + Caption = 'Estimate Discount Amount'; + DataClassification = SystemMetadata; + } + field(55; EstimateDiscountAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimatediscountamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate discount amount in the base currency.'; + Caption = 'Estimate Discount Amount (Base)'; + DataClassification = SystemMetadata; + } + field(56; EstimateDiscountPercent; Decimal) + { + ExternalName = 'msdyn_estimatediscountpercent'; + ExternalType = 'Float'; + Description = 'Enter a discount % on the subtotal amount. Note: If you enter a discount % it will overwrite the discount $'; + Caption = 'Estimate Discount %'; + DataClassification = SystemMetadata; + } + field(57; EstimateQuantity; Decimal) + { + ExternalName = 'msdyn_estimatequantity'; + ExternalType = 'Float'; + Description = 'Enter the estimated required quantity of this product.'; + Caption = 'Estimate Quantity'; + DataClassification = SystemMetadata; + } + field(58; EstimateSubtotal; Decimal) + { + ExternalName = 'msdyn_estimatesubtotal'; + ExternalType = 'Money'; + Description = 'Shows the total amount for this product, excluding discounts.'; + Caption = 'Estimate Subtotal'; + DataClassification = SystemMetadata; + } + field(59; EstimateSubtotal_Base; Decimal) + { + ExternalName = 'msdyn_estimatesubtotal_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate subtotal in the base currency.'; + Caption = 'Estimate Subtotal (Base)'; + DataClassification = SystemMetadata; + } + field(60; EstimateTotalAmount; Decimal) + { + ExternalName = 'msdyn_estimatetotalamount'; + ExternalType = 'Money'; + Description = 'Shows the estimated total amount of this product, including discounts.'; + Caption = 'Estimate Total Amount'; + DataClassification = SystemMetadata; + } + field(61; EstimateTotalAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimatetotalamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate total amount in the base currency.'; + Caption = 'Estimate Total Amount (Base)'; + DataClassification = SystemMetadata; + } + field(62; EstimateTotalCost; Decimal) + { + ExternalName = 'msdyn_estimatetotalcost'; + ExternalType = 'Money'; + Description = 'Shows the estimated total cost of this product.'; + Caption = 'Estimate Total Cost'; + DataClassification = SystemMetadata; + } + field(63; EtimateTotalCost_Base; Decimal) + { + ExternalName = 'msdyn_estimatetotalcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate total cost in the base currency.'; + Caption = 'Estimate Total Cost (Base)'; + DataClassification = SystemMetadata; + } + field(64; EstimateUnitAmount; Decimal) + { + ExternalName = 'msdyn_estimateunitamount'; + ExternalType = 'Money'; + Description = 'Shows the estimated sale amount per unit.'; + Caption = 'Estimate Unit Amount'; + DataClassification = SystemMetadata; + } + field(65; EstimateUnitAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimateunitamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate unit amount in the base currency.'; + Caption = 'Estimate Unit Amount (Base)'; + DataClassification = SystemMetadata; + } + field(66; EstimateUnitCost; Decimal) + { + ExternalName = 'msdyn_estimateunitcost'; + ExternalType = 'Money'; + Description = 'Shows the estimated cost amount per unit.'; + Caption = 'Estimate Unit Cost'; + DataClassification = SystemMetadata; + } + field(67; EstimateUnitCost_Base; Decimal) + { + ExternalName = 'msdyn_estimateunitcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate unit cost in the base currency.'; + Caption = 'Estimate Unit Cost (Base)'; + DataClassification = SystemMetadata; + } + field(68; InternalDescription; BLOB) + { + ExternalName = 'msdyn_internaldescription'; + ExternalType = 'Memo'; + Description = 'Enter any internal notes you want to track on this product.'; + Caption = 'Internal Description'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(69; InternalFlags; BLOB) + { + ExternalName = 'msdyn_internalflags'; + ExternalType = 'Memo'; + Description = 'For internal use only.'; + Caption = 'Internal Flags'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(70; LineOrder; Integer) + { + ExternalName = 'msdyn_lineorder'; + ExternalType = 'Integer'; + Description = ''; + Caption = 'Line Order'; + DataClassification = SystemMetadata; + } + field(71; LineStatus; Option) + { + ExternalName = 'msdyn_linestatus'; + ExternalType = 'Picklist'; + Description = 'Enter the current status of the line, estimate or used.'; + Caption = 'Line Status'; + InitValue = Estimated; + OptionMembers = Estimated,Used; + OptionOrdinalValues = 690970000, 690970001; + DataClassification = SystemMetadata; + } + field(73; PriceList; GUID) + { + ExternalName = 'msdyn_pricelist'; + ExternalType = 'Lookup'; + Description = 'Price List that determines the pricing for this product'; + Caption = 'Price List'; + TableRelation = "CRM Pricelevel".PriceLevelId; + DataClassification = SystemMetadata; + } + field(74; Product; GUID) + { + ExternalName = 'msdyn_product'; + ExternalType = 'Lookup'; + Description = 'Product to use'; + Caption = 'Product'; + TableRelation = "CRM Product".ProductId; + DataClassification = SystemMetadata; + } + field(76; QtyToBill; Decimal) + { + ExternalName = 'msdyn_qtytobill'; + ExternalType = 'Float'; + Description = 'Enter the quantity you wish to bill the customer for. By default, this will default to the same value as "Quantity."'; + Caption = 'Quantity To Bill'; + DataClassification = SystemMetadata; + } + field(77; Quantity; Decimal) + { + ExternalName = 'msdyn_quantity'; + ExternalType = 'Float'; + Description = 'Shows the actual quantity of the product.'; + Caption = 'Quantity'; + DataClassification = SystemMetadata; + } + field(78; Subtotal; Decimal) + { + ExternalName = 'msdyn_subtotal'; + ExternalType = 'Money'; + Description = 'Enter the total amount excluding discounts.'; + Caption = 'Subtotal'; + DataClassification = SystemMetadata; + } + field(79; Subtotal_Base; Decimal) + { + ExternalName = 'msdyn_subtotal_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the subtotal in the base currency.'; + Caption = 'Subtotal (Base)'; + DataClassification = SystemMetadata; + } + field(80; Taxable; Boolean) + { + ExternalName = 'msdyn_taxable'; + ExternalType = 'Boolean'; + Description = 'Specify if product is taxable. If you do not wish to charge tax set this field to No.'; + Caption = 'Taxable'; + DataClassification = SystemMetadata; + } + field(82; TotalAmount; Decimal) + { + ExternalName = 'msdyn_totalamount'; + ExternalType = 'Money'; + Description = 'Enter the total amount charged to the customer.'; + Caption = 'Total Amount'; + DataClassification = SystemMetadata; + } + field(83; TotalAmount_Base; Decimal) + { + ExternalName = 'msdyn_totalamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the total amount in the base currency.'; + Caption = 'Total Amount (Base)'; + DataClassification = SystemMetadata; + } + field(84; TotalCost; Decimal) + { + ExternalName = 'msdyn_totalcost'; + ExternalType = 'Money'; + Description = 'Shows the total cost of this product. This is calculated by (Unit Cost * Units) + Additional Cost + Commission Costs.'; + Caption = 'Total Cost'; + DataClassification = SystemMetadata; + } + field(85; TotalCost_Base; Decimal) + { + ExternalName = 'msdyn_totalcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the total cost in the base currency.'; + Caption = 'Total Cost (Base)'; + DataClassification = SystemMetadata; + } + field(86; Unit; GUID) + { + ExternalName = 'msdyn_unit'; + ExternalType = 'Lookup'; + Description = 'The unit that determines the pricing and final quantity for this product'; + Caption = 'Unit'; + TableRelation = "CRM Uom".UoMId; + DataClassification = SystemMetadata; + } + field(87; UnitAmount; Decimal) + { + ExternalName = 'msdyn_unitamount'; + ExternalType = 'Money'; + Description = 'Enter the amount you want to charge the customer per unit. By default, this is calculated based on the selected price list. The amount can be changed.'; + Caption = 'Unit Amount'; + DataClassification = SystemMetadata; + } + field(88; UnitAmount_Base; Decimal) + { + ExternalName = 'msdyn_unitamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the unit amount in the base currency.'; + Caption = 'Unit Amount (Base)'; + DataClassification = SystemMetadata; + } + field(89; UnitCost; Decimal) + { + ExternalName = 'msdyn_unitcost'; + ExternalType = 'Money'; + Description = 'Shows the actual cost per unit.'; + Caption = 'Unit Cost'; + DataClassification = SystemMetadata; + } + field(90; UnitCost_Base; Decimal) + { + ExternalName = 'msdyn_unitcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the unit cost in the base currency.'; + Caption = 'Unit Cost (Base)'; + DataClassification = SystemMetadata; + } + field(92; WorkOrder; GUID) + { + ExternalName = 'msdyn_workorder'; + ExternalType = 'Lookup'; + Description = 'Unique identifier for Work Order associated with Work Order Product.'; + Caption = 'Work Order'; + TableRelation = "FS Work Order".WorkOrderId; + DataClassification = SystemMetadata; + } + field(93; WorkOrderIncident; GUID) + { + ExternalName = 'msdyn_workorderincident'; + ExternalType = 'Lookup'; + Description = 'The Incident related to this product'; + Caption = 'Work Order Incident'; + TableRelation = "FS Work Order Incident".WorkOrderIncidentId; + DataClassification = SystemMetadata; + } + field(94; BookingName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource Booking".Name where(BookableResourceBookingId = field(Booking))); + ExternalName = 'msdyn_bookingname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(97; CustomerAssetName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Customer Asset".Name where(CustomerAssetId = field(CustomerAsset))); + ExternalName = 'msdyn_customerassetname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(100; WorkOrderName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order".Name where(WorkOrderId = field(WorkOrder))); + ExternalName = 'msdyn_workordername'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(101; WorkOrderIncidentName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order Incident".Name where(WorkOrderIncidentId = field(WorkOrderIncident))); + ExternalName = 'msdyn_workorderincidentname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(105; ProjectTask; GUID) + { + ExternalName = 'bcbi_projecttask'; + ExternalType = 'Lookup'; + Description = 'Business Central Project Task'; + Caption = 'External Project'; + TableRelation = "FS Project Task".ProjectTaskId; + DataClassification = SystemMetadata; + } + field(106; WorkOrderStatus; Option) + { + ExternalName = 'bcbi_workorderstatus'; + ExternalType = 'Picklist'; + Description = 'The status of the work order'; + Caption = 'Work Order Status'; + InitValue = " "; + OptionMembers = " ",Unscheduled,Scheduled,InProgress,Completed,Posted,Canceled; + OptionOrdinalValues = -1, 690970000, 690970001, 690970002, 690970003, 690970004, 690970005; + DataClassification = SystemMetadata; + } + field(108; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + field(111; QuantityConsumed; Decimal) + { + ExternalName = 'bcbi_quantityconsumed'; + ExternalType = 'Float'; + Description = 'Quantity consumed in Dynamics 365 Business Central'; + Caption = 'Quantity Consumed'; + } + field(112; QuantityInvoiced; Decimal) + { + ExternalName = 'bcbi_quantityinvoiced'; + ExternalType = 'Float'; + 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'; + } + } + keys + { + key(PK; workorderproductId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al new file mode 100644 index 0000000000..4830d0383b --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderService.Table.al @@ -0,0 +1,767 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6620 "FS Work Order Service" +{ + ExternalName = 'msdyn_workorderservice'; + TableType = CRM; + Description = 'Record all services proposed and performed for work order'; + DataClassification = SystemMetadata; + + fields + { + field(1; WorkOrderServiceId; GUID) + { + ExternalName = 'msdyn_workorderserviceid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'Work Order Service'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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 = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Work Order Service'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Work Order Service'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[200]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Enter the name of the custom entity.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(35; AdditionalCost; Decimal) + { + ExternalName = 'msdyn_additionalcost'; + ExternalType = 'Money'; + Description = 'Enter any additional costs associated with this service. The values are manually entered. Note: additional cost is not unit dependent.'; + Caption = 'Additional Cost'; + DataClassification = SystemMetadata; + } + field(36; TransactionCurrencyId; GUID) + { + ExternalName = 'transactioncurrencyid'; + ExternalType = 'Lookup'; + Description = 'Unique identifier of the currency associated with the entity.'; + Caption = 'Currency'; + TableRelation = "CRM Transactioncurrency".TransactionCurrencyId; + DataClassification = SystemMetadata; + } + field(38; ExchangeRate; Decimal) + { + ExternalName = 'exchangerate'; + ExternalType = 'Decimal'; + ExternalAccess = Read; + Description = 'Shows the exchange rate for the currency associated with the entity with respect to the base currency.'; + Caption = 'Exchange Rate'; + DataClassification = SystemMetadata; + } + field(39; AdditionalCost_Base; Decimal) + { + ExternalName = 'msdyn_additionalcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the additional cost in the base currency.'; + Caption = 'Additional Cost (Base)'; + DataClassification = SystemMetadata; + } + field(41; Booking; GUID) + { + ExternalName = 'msdyn_booking'; + ExternalType = 'Lookup'; + Description = 'Shows the resource booking detail where this product was added.'; + Caption = 'Booking'; + TableRelation = "FS Bookable Resource Booking".BookableResourceBookingId; + DataClassification = SystemMetadata; + } + field(42; CalculatedUnitAmount; Decimal) + { + ExternalName = 'msdyn_calculatedunitamount'; + ExternalType = 'Money'; + Description = 'Shows the sale amount per unit calculated by the system considering the minimum charge, if applicable.'; + Caption = 'Calculated Unit Amount'; + DataClassification = SystemMetadata; + } + field(43; CalculatedUnitAmount_Base; Decimal) + { + ExternalName = 'msdyn_calculatedunitamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the calculated unit amount in the base currency.'; + Caption = 'Calculated Unit Amount (Base)'; + DataClassification = SystemMetadata; + } + field(44; CommissionCosts; Decimal) + { + ExternalName = 'msdyn_commissioncosts'; + ExternalType = 'Money'; + Description = 'Enter the commission costs associated with this service. The value is manually specified and isn''t automatically calculated.'; + Caption = 'Commission Costs'; + DataClassification = SystemMetadata; + } + field(45; CommissionCosts_Base; Decimal) + { + ExternalName = 'msdyn_commissioncosts_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the commission costs in the base currency.'; + Caption = 'Commission Costs (Base)'; + DataClassification = SystemMetadata; + } + field(46; CustomerAsset; GUID) + { + ExternalName = 'msdyn_customerasset'; + ExternalType = 'Lookup'; + Description = 'Unique identifier for Customer Asset associated with Work Order Service.'; + Caption = 'Customer Asset'; + TableRelation = "FS Customer Asset".CustomerAssetId; + DataClassification = SystemMetadata; + } + field(47; Description; BLOB) + { + ExternalName = 'msdyn_description'; + ExternalType = 'Memo'; + Description = 'Enter the description of the service as presented to the customer. The value defaults to the description defined on the service.'; + Caption = 'Description'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(48; DisableEntitlement; Boolean) + { + ExternalName = 'msdyn_disableentitlement'; + ExternalType = 'Boolean'; + Description = 'Choose whether to disable entitlement selection for this work order service.'; + Caption = 'Disable Entitlement'; + DataClassification = SystemMetadata; + } + field(50; DiscountAmount; Decimal) + { + ExternalName = 'msdyn_discountamount'; + ExternalType = 'Money'; + Description = 'Specify any discount amount on this service. Note: If you enter a discount amount you cannot enter a discount %'; + Caption = 'Discount Amount'; + DataClassification = SystemMetadata; + } + field(51; DiscountAmount_Base; Decimal) + { + ExternalName = 'msdyn_discountamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the discount Amount in the base currency.'; + Caption = 'Discount Amount (Base)'; + DataClassification = SystemMetadata; + } + field(52; DiscountPercent; Decimal) + { + ExternalName = 'msdyn_discountpercent'; + ExternalType = 'Float'; + Description = 'Specify any discount % on this service. Note: If you enter a discount % it will overwrite the discount $'; + Caption = 'Discount %'; + DataClassification = SystemMetadata; + } + field(53; Duration; Integer) + { + ExternalName = 'msdyn_duration'; + ExternalType = 'Integer'; + Description = 'Shows the actual duration of service.'; + Caption = 'Duration'; + DataClassification = SystemMetadata; + } + field(54; DurationToBill; Integer) + { + ExternalName = 'msdyn_durationtobill'; + ExternalType = 'Integer'; + Description = 'Enter the quantity you wish to bill the customer for. By default, this will default to the same value as "Quantity."'; + Caption = 'Duration To Bill'; + DataClassification = SystemMetadata; + } + field(56; EstimateCalculatedUnitAmount; Decimal) + { + ExternalName = 'msdyn_estimatecalculatedunitamount'; + ExternalType = 'Money'; + Description = 'Shows the estimated sale amount per unit calculated by the system considering the initial charge (if applicable).'; + Caption = 'Estimate Calculated Unit Amount'; + DataClassification = SystemMetadata; + } + field(57; EstimateCalculatedUnitAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimatecalculatedunitamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate calculated unit amount in the base currency.'; + Caption = 'Estimate Calculated Unit Amount (Base)'; + DataClassification = SystemMetadata; + } + field(58; EstimateDiscountAmount; Decimal) + { + ExternalName = 'msdyn_estimatediscountamount'; + ExternalType = 'Money'; + Description = 'Enter a discount amount on the subtotal amount. Note: If you enter a discount amount you cannot enter a discount %'; + Caption = 'Estimate Discount Amount'; + DataClassification = SystemMetadata; + } + field(59; EstimateDiscountAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimatediscountamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate discount amount in the base currency.'; + Caption = 'Estimate Discount Amount (Base)'; + DataClassification = SystemMetadata; + } + field(60; EstimateDiscountPercent; Decimal) + { + ExternalName = 'msdyn_estimatediscountpercent'; + ExternalType = 'Float'; + Description = 'Enter a discount % on the subtotal amount. Note: If you enter a discount % it will overwrite the discount $'; + Caption = 'Estimate Discount %'; + DataClassification = SystemMetadata; + } + field(61; EstimateDuration; Integer) + { + ExternalName = 'msdyn_estimateduration'; + ExternalType = 'Integer'; + Description = 'Enter the estimated duration of this service.'; + Caption = 'Estimate Duration'; + DataClassification = SystemMetadata; + } + field(62; EstimateSubtotal; Decimal) + { + ExternalName = 'msdyn_estimatesubtotal'; + ExternalType = 'Money'; + Description = 'Shows the total amount for this service, excluding discounts.'; + Caption = 'Estimate Subtotal'; + DataClassification = SystemMetadata; + } + field(63; EstimateSubtotal_Base; Decimal) + { + ExternalName = 'msdyn_estimatesubtotal_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate subtotal in the base currency.'; + Caption = 'Estimate Subtotal (Base)'; + DataClassification = SystemMetadata; + } + field(64; EstimateTotalAmount; Decimal) + { + ExternalName = 'msdyn_estimatetotalamount'; + ExternalType = 'Money'; + Description = 'Shows the estimated total amount of this service, including discounts.'; + Caption = 'Estimate Total Amount'; + DataClassification = SystemMetadata; + } + field(65; EstimateTotalAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimatetotalamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate total amount in the base currency.'; + Caption = 'Estimate Total Amount (Base)'; + DataClassification = SystemMetadata; + } + field(66; EstimateTotalCost; Decimal) + { + ExternalName = 'msdyn_estimatetotalcost'; + ExternalType = 'Money'; + Description = 'Shows the estimated total cost of this service.'; + Caption = 'Estimate Total Cost'; + DataClassification = SystemMetadata; + } + field(67; EstimateTotalCost_Base; Decimal) + { + ExternalName = 'msdyn_estimatetotalcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate total cost in the base currency.'; + Caption = 'Estimate Total Cost (Base)'; + DataClassification = SystemMetadata; + } + field(68; EstimateUnitAmount; Decimal) + { + ExternalName = 'msdyn_estimateunitamount'; + ExternalType = 'Money'; + Description = 'Shows the estimated sale amount per unit.'; + Caption = 'Estimate Unit Amount'; + DataClassification = SystemMetadata; + } + field(69; EtimateUnitAmount_Base; Decimal) + { + ExternalName = 'msdyn_estimateunitamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate unit amount in the base currency.'; + Caption = 'Estimate Unit Amount (Base)'; + DataClassification = SystemMetadata; + } + field(70; EstimateUnitCost; Decimal) + { + ExternalName = 'msdyn_estimateunitcost'; + ExternalType = 'Money'; + Description = 'Shows the estimated cost amount per unit.'; + Caption = 'Estimate Unit Cost'; + DataClassification = SystemMetadata; + } + field(71; EstimateUnitCost_Base; Decimal) + { + ExternalName = 'msdyn_estimateunitcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the estimate unit cost in the base currency.'; + Caption = 'Estimate Unit Cost (Base)'; + DataClassification = SystemMetadata; + } + field(72; InternalDescription; BLOB) + { + ExternalName = 'msdyn_internaldescription'; + ExternalType = 'Memo'; + Description = 'Enter any internal notes you want to track on this service.'; + Caption = 'Internal Description'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(73; InternalFlags; BLOB) + { + ExternalName = 'msdyn_internalflags'; + ExternalType = 'Memo'; + Description = ''; + Caption = 'Internal Flags'; + Subtype = Memo; + DataClassification = SystemMetadata; + } + field(74; LineOrder; Integer) + { + ExternalName = 'msdyn_lineorder'; + ExternalType = 'Integer'; + Caption = 'Line Order'; + DataClassification = SystemMetadata; + } + field(75; LineStatus; Option) + { + ExternalName = 'msdyn_linestatus'; + ExternalType = 'Picklist'; + Description = 'Enter the current status of the line, estimate or used.'; + Caption = 'Line Status'; + InitValue = Estimated; + OptionMembers = Estimated,Used; + OptionOrdinalValues = 690970000, 690970001; + DataClassification = SystemMetadata; + } + field(77; MinimumChargeAmount; Decimal) + { + ExternalName = 'msdyn_minimumchargeamount'; + ExternalType = 'Money'; + Description = 'Enter the amount charged as a minimum charge.'; + Caption = 'Minimum Charge Amount'; + DataClassification = SystemMetadata; + } + field(78; MinimumChargeAmount_Base; Decimal) + { + ExternalName = 'msdyn_minimumchargeamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the minimum charge amount in the base currency.'; + Caption = 'Minimum Charge Amount (Base)'; + DataClassification = SystemMetadata; + } + field(79; MinimumChargeDuration; Integer) + { + ExternalName = 'msdyn_minimumchargeduration'; + ExternalType = 'Integer'; + Description = 'Enter the duration of up to how long the minimum charge applies.'; + Caption = 'Minimum Charge Duration'; + DataClassification = SystemMetadata; + } + field(80; PriceList; GUID) + { + ExternalName = 'msdyn_pricelist'; + ExternalType = 'Lookup'; + Description = 'Price List that determines the pricing for this service'; + Caption = 'Price List'; + TableRelation = "CRM Pricelevel".PriceLevelId; + DataClassification = SystemMetadata; + } + field(81; Service; GUID) + { + ExternalName = 'msdyn_service'; + ExternalType = 'Lookup'; + Description = 'Service proposed or used for this work order'; + Caption = 'Service'; + TableRelation = "CRM Product".ProductId; + DataClassification = SystemMetadata; + } + field(82; Subtotal; Decimal) + { + ExternalName = 'msdyn_subtotal'; + ExternalType = 'Money'; + Description = 'Enter the total amount excluding discounts.'; + Caption = 'Subtotal'; + DataClassification = SystemMetadata; + } + field(83; Subtotal_Base; Decimal) + { + ExternalName = 'msdyn_subtotal_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the subtotal in the base currency.'; + Caption = 'Subtotal (Base)'; + DataClassification = SystemMetadata; + } + field(84; Taxable; Boolean) + { + ExternalName = 'msdyn_taxable'; + ExternalType = 'Boolean'; + Description = 'Specify if service is taxable. If you do not wish to charge tax set this field to No.'; + Caption = 'Taxable'; + DataClassification = SystemMetadata; + } + field(86; TotalAmount; Decimal) + { + ExternalName = 'msdyn_totalamount'; + ExternalType = 'Money'; + Caption = 'Total Amount'; + DataClassification = SystemMetadata; + } + field(87; TotalAmount_Base; Decimal) + { + ExternalName = 'msdyn_totalamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the total amount in the base currency.'; + Caption = 'Total Amount (Base)'; + DataClassification = SystemMetadata; + } + field(88; TotalCost; Decimal) + { + ExternalName = 'msdyn_totalcost'; + ExternalType = 'Money'; + Description = 'Shows the total cost of this service. This is calculated by (Unit Cost * Units) + Additional Cost + Commission Costs.'; + Caption = 'Total Cost'; + DataClassification = SystemMetadata; + } + field(89; TotalCost_Base; Decimal) + { + ExternalName = 'msdyn_totalcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the total cost in the base currency.'; + Caption = 'Total Cost (Base)'; + DataClassification = SystemMetadata; + } + field(90; Unit; GUID) + { + ExternalName = 'msdyn_unit'; + ExternalType = 'Lookup'; + Description = 'The unit that determines the final quantity for this service'; + Caption = 'Unit'; + TableRelation = "CRM Uom".UoMId; + DataClassification = SystemMetadata; + } + field(91; UnitAmount; Decimal) + { + ExternalName = 'msdyn_unitamount'; + ExternalType = 'Money'; + Description = 'Enter the amount you want to charge the customer per unit. By default, this is calculated based on the selected price list. The amount can be changed.'; + Caption = 'Unit Amount'; + DataClassification = SystemMetadata; + } + field(92; UnitAmount_Base; Decimal) + { + ExternalName = 'msdyn_unitamount_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the unit amount in the base currency.'; + Caption = 'Unit Amount (Base)'; + DataClassification = SystemMetadata; + } + field(93; UnitCost; Decimal) + { + ExternalName = 'msdyn_unitcost'; + ExternalType = 'Money'; + Description = 'Shows the actual cost per unit.'; + Caption = 'Unit Cost'; + DataClassification = SystemMetadata; + } + field(94; UnitCost_Base; Decimal) + { + ExternalName = 'msdyn_unitcost_base'; + ExternalType = 'Money'; + ExternalAccess = Read; + Description = 'Shows the value of the unit cost in the base currency.'; + Caption = 'Unit Cost (Base)'; + DataClassification = SystemMetadata; + } + field(95; WorkOrder; GUID) + { + ExternalName = 'msdyn_workorder'; + ExternalType = 'Lookup'; + Description = 'The work order this service relates to'; + Caption = 'Work Order'; + TableRelation = "FS Work Order".WorkOrderId; + DataClassification = SystemMetadata; + } + field(96; WorkOrderIncident; GUID) + { + ExternalName = 'msdyn_workorderincident'; + ExternalType = 'Lookup'; + Description = 'The Incident related to this product'; + Caption = 'Work Order Incident'; + TableRelation = "FS Work Order Incident".WorkOrderIncidentId; + DataClassification = SystemMetadata; + } + field(97; BookingName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Bookable Resource Booking".Name where(BookableResourceBookingId = field(Booking))); + ExternalName = 'msdyn_bookingname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(100; CustomerAssetName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Customer Asset".Name where(CustomerAssetId = field(CustomerAsset))); + ExternalName = 'msdyn_customerassetname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(101; WorkOrderName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order".Name where(WorkOrderId = field(WorkOrder))); + ExternalName = 'msdyn_workordername'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(102; WorkOrderIncidentName; Text[100]) + { + FieldClass = FlowField; + CalcFormula = lookup("FS Work Order Incident".Name where(WorkOrderIncidentId = field(WorkOrderIncident))); + ExternalName = 'msdyn_workorderincidentname'; + ExternalType = 'String'; + ExternalAccess = Read; + } + field(106; CompanyId; GUID) + { + ExternalName = 'bcbi_company'; + ExternalType = 'Lookup'; + Description = 'Business Central Company'; + Caption = 'Company Id'; + TableRelation = "CDS Company".CompanyId; + DataClassification = SystemMetadata; + } + field(107; ProjectTask; GUID) + { + ExternalName = 'bcbi_projecttask'; + ExternalType = 'Lookup'; + Description = 'Business Central Project Task'; + Caption = 'External Project'; + TableRelation = "FS Project Task".ProjectTaskId; + DataClassification = SystemMetadata; + } + field(108; WorkOrderStatus; Option) + { + ExternalName = 'bcbi_workorderstatus'; + ExternalType = 'Picklist'; + Description = 'The system status of the work order'; + Caption = 'Work Order Status'; + InitValue = " "; + OptionMembers = " ",Unscheduled,Scheduled,InProgress,Completed,Posted,Canceled; + OptionOrdinalValues = -1, 690970000, 690970001, 690970002, 690970003, 690970004, 690970005; + DataClassification = SystemMetadata; + } + field(112; DurationConsumed; Integer) + { + ExternalName = 'bcbi_durationconsumed'; + ExternalType = 'Integer'; + Description = 'Duration consumed in Dynamics 365 Business Central'; + Caption = 'Duration Consumed'; + } + field(113; DurationInvoiced; Integer) + { + ExternalName = 'bcbi_durationinvoiced'; + ExternalType = 'Integer'; + 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'; + } + } + keys + { + key(PK; workorderserviceId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderSubstatus.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderSubstatus.Table.al new file mode 100644 index 0000000000..62b6e4fd77 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderSubstatus.Table.al @@ -0,0 +1,233 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6621 "FS Work Order Substatus" +{ + ExternalName = 'msdyn_workordersubstatus'; + TableType = CRM; + Description = 'Specify custom work order substatuses, which can be used to specify the current work order status more precisely.'; + DataClassification = SystemMetadata; + + fields + { + field(1; WorkOrderSubstatusId; GUID) + { + ExternalName = 'msdyn_workordersubstatusid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'Work Order Substatus'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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 = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Work Order Substatus'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Work Order Substatus'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[100]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Shows the work order status name.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(35; DefaultSubStatus; Boolean) + { + ExternalName = 'msdyn_defaultsubstatus'; + ExternalType = 'Boolean'; + Description = 'Specify whether this substatus should be the default substatus for this status type.'; + Caption = 'Default Substatus'; + DataClassification = SystemMetadata; + } + field(37; SystemStatus; Option) + { + ExternalName = 'msdyn_systemstatus'; + ExternalType = 'Picklist'; + Description = 'Specify the system status.'; + Caption = 'System Status'; + InitValue = " "; + OptionMembers = " ",Unscheduled,Scheduled,InProgress,Completed,Posted,Canceled; + OptionOrdinalValues = -1, 690970000, 690970001, 690970002, 690970003, 690970004, 690970005; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; WorkOrderSubstatusId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al new file mode 100644 index 0000000000..5929311a8b --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/app/src/Tables/FSWorkOrderType.Table.al @@ -0,0 +1,239 @@ +// ------------------------------------------------------------------------------------------------ +// 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 6622 "FS Work Order Type" +{ + ExternalName = 'msdyn_workordertype'; + TableType = CRM; + Description = 'Create different work order types to reflect the different types of work that your company offers. Work Order types are used to control various settings on a work order.'; + DataClassification = SystemMetadata; + + fields + { + field(1; WorkOrderTypeId; GUID) + { + ExternalName = 'msdyn_workordertypeid'; + ExternalType = 'Uniqueidentifier'; + ExternalAccess = Insert; + Description = 'Shows the entity instances.'; + Caption = 'Work Order Type'; + DataClassification = SystemMetadata; + } + field(2; CreatedOn; Datetime) + { + ExternalName = 'createdon'; + ExternalType = 'DateTime'; + ExternalAccess = Read; + Description = 'Shows the date and time when the record was created. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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 = 'Shows the date and time when the record was last updated. The date and time are displayed in the time zone selected in Microsoft Dynamics 365 options.'; + 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(6; CreatedOnBehalfBy; GUID) + { + ExternalName = 'createdonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who created the record on behalf of another user.'; + Caption = 'Created By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(7; ModifiedOnBehalfBy; GUID) + { + ExternalName = 'modifiedonbehalfby'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Shows who last updated the record on behalf of another user.'; + Caption = 'Modified By (Delegate)'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(16; OwnerId; GUID) + { + ExternalName = 'ownerid'; + ExternalType = 'Owner'; + Description = 'Owner Id'; + Caption = 'Owner'; + DataClassification = SystemMetadata; + } + field(21; OwningBusinessUnit; GUID) + { + ExternalName = 'owningbusinessunit'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the business unit that owns the record'; + Caption = 'Owning Business Unit'; + TableRelation = "CRM Businessunit".BusinessUnitId; + DataClassification = SystemMetadata; + } + field(22; OwningUser; GUID) + { + ExternalName = 'owninguser'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the user that owns the record.'; + Caption = 'Owning User'; + TableRelation = "CRM Systemuser".SystemUserId; + DataClassification = SystemMetadata; + } + field(23; OwningTeam; GUID) + { + ExternalName = 'owningteam'; + ExternalType = 'Lookup'; + ExternalAccess = Read; + Description = 'Unique identifier for the team that owns the record.'; + Caption = 'Owning Team'; + TableRelation = "CRM Team".TeamId; + DataClassification = SystemMetadata; + } + field(25; StateCode; Option) + { + ExternalName = 'statecode'; + ExternalType = 'State'; + ExternalAccess = Modify; + Description = 'Status of the Work Order Type'; + Caption = 'Status'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 0, 1; + DataClassification = SystemMetadata; + } + field(27; StatusCode; Option) + { + ExternalName = 'statuscode'; + ExternalType = 'Status'; + Description = 'Reason for the status of the Work Order Type'; + Caption = 'Status Reason'; + InitValue = " "; + OptionMembers = " ",Active,Inactive; + OptionOrdinalValues = -1, 1, 2; + DataClassification = SystemMetadata; + } + field(29; VersionNumber; BigInteger) + { + ExternalName = 'versionnumber'; + ExternalType = 'BigInt'; + ExternalAccess = Read; + Description = 'Version Number'; + Caption = 'Version Number'; + DataClassification = SystemMetadata; + } + field(30; ImportSequenceNumber; Integer) + { + ExternalName = 'importsequencenumber'; + ExternalType = 'Integer'; + ExternalAccess = Insert; + Description = 'Shows the sequence number of the import that created this record.'; + Caption = 'Import Sequence Number'; + DataClassification = SystemMetadata; + } + field(31; OverriddenCreatedOn; Date) + { + ExternalName = 'overriddencreatedon'; + ExternalType = 'DateTime'; + ExternalAccess = Insert; + Description = 'Shows the date and time that the record was migrated.'; + Caption = 'Record Created On'; + DataClassification = SystemMetadata; + } + field(32; TimeZoneRuleVersionNumber; Integer) + { + ExternalName = 'timezoneruleversionnumber'; + ExternalType = 'Integer'; + Description = 'For internal use only.'; + Caption = 'Time Zone Rule Version Number'; + DataClassification = SystemMetadata; + } + field(33; UTCConversionTimeZoneCode; Integer) + { + ExternalName = 'utcconversiontimezonecode'; + ExternalType = 'Integer'; + Description = 'Shows the time zone code that was in use when the record was created.'; + Caption = 'UTC Conversion Time Zone Code'; + DataClassification = SystemMetadata; + } + field(34; Name; Text[100]) + { + ExternalName = 'msdyn_name'; + ExternalType = 'String'; + Description = 'Type the name of the work order type.'; + Caption = 'Name'; + DataClassification = SystemMetadata; + } + field(35; IncidentRequired; Boolean) + { + ExternalName = 'msdyn_incidentrequired'; + ExternalType = 'Boolean'; + Description = 'Select whether work orders of this type require an incident to be recorded on the work order.'; + Caption = 'Incident Required'; + DataClassification = SystemMetadata; + } + field(37; PriceList; GUID) + { + ExternalName = 'msdyn_pricelist'; + ExternalType = 'Lookup'; + Description = 'Default Price List to be used on Work Orders linked to this Work Order Type. Please review the help for further details on Price Lists'; + Caption = 'Price List'; + TableRelation = "CRM Pricelevel".PriceLevelId; + DataClassification = SystemMetadata; + } + field(38; Taxable; Boolean) + { + ExternalName = 'msdyn_taxable'; + ExternalType = 'Boolean'; + Description = 'Select whether work orders of this type are taxable.'; + Caption = 'Taxable'; + DataClassification = SystemMetadata; + } + } + keys + { + key(PK; workordertypeId) + { + Clustered = true; + } + key(Name; Name) + { + } + } + fieldgroups + { + fieldgroup(DropDown; Name) + { + } + } +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/test library/ExtensionLogo.png b/Apps/W1/FieldServiceIntegration/test library/ExtensionLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..4d2c9a626cb9617350617c40cd73904129d4c108 GIT binary patch literal 5446 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 literal 0 HcmV?d00001 diff --git a/Apps/W1/FieldServiceIntegration/test library/app.json b/Apps/W1/FieldServiceIntegration/test library/app.json new file mode 100644 index 0000000000..8cb20a4d39 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/test library/app.json @@ -0,0 +1,43 @@ +{ + "id": "41b3ab6e-3f20-47c7-a67f-feccc4d58a55", + "name": "Field Service Integration Test Library", + "publisher": "Microsoft", + "brief": "Test library for the Field Service Integration extension.", + "description": "Test library for the Field Service Integration extension.", + "version": "25.0.0.0", + "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009", + "EULA": "https://go.microsoft.com/fwlink/?linkid=2182906", + "help": "https://go.microsoft.com/fwlink/?linkid=2206519", + "contextSensitiveHelpUrl": "https://learn.microsoft.com/dynamics365/business-central/", + "url": "https://go.microsoft.com/fwlink/?LinkId=724011", + "logo": "ExtensionLogo.png", + "dependencies": [ + { + "id": "1ba1031e-eae9-4f20-b9d2-d19b6d1e3f29", + "name": "Field Service Integration", + "publisher": "Microsoft", + "version": "25.0.0.0" + } + ], + "screenshots": [ + + ], + "platform": "25.0.0.0", + "target": "OnPrem", + "resourceExposurePolicy": { + "allowDebugging": false, + "allowDownloadingSource": true, + "includeSourceInSymbolFile": true + }, + "application": "25.0.0.0", + "idRanges": [ + { + "from": 139205, + "to": 139205 + } + ], + "features": [ + "NoImplicitWith", + "TranslationFile" + ] +} \ No newline at end of file diff --git a/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al b/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al new file mode 100644 index 0000000000..dcc5054f25 --- /dev/null +++ b/Apps/W1/FieldServiceIntegration/test library/src/FSIntegrationTestLibrary.Codeunit.al @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.TestLibraries.DynamicsFieldService; + +using Microsoft.Integration.DynamicsFieldService; + +codeunit 139205 "FS Integration Test Library" +{ + procedure RegisterConnection(var FSConnectionSetup: Record "FS Connection Setup") + begin + FSConnectionSetup.RegisterConnection(); + end; + + procedure UnregisterConnection(var FSConnectionSetup: Record "FS Connection Setup") + begin + FSConnectionSetup.UnregisterConnection(); + end; + + procedure SetPassword(var FSConnectionSetup: Record "FS Connection Setup"; Password: SecretText) + begin + FSConnectionSetup.SetPassword(Password); + end; + + procedure PerformTestConnection(var FSConnectionSetup: Record "FS Connection Setup") + begin + FSConnectionSetup.PerformTestConnection(); + end; +} \ No newline at end of file diff --git a/Apps/W1/HybridBCLast/app/src/codeunits/ExecuteNonCompanyUpgrade.Codeunit.al b/Apps/W1/HybridBCLast/app/src/codeunits/ExecuteNonCompanyUpgrade.Codeunit.al index 572a005a0c..aa6e495eae 100644 --- a/Apps/W1/HybridBCLast/app/src/codeunits/ExecuteNonCompanyUpgrade.Codeunit.al +++ b/Apps/W1/HybridBCLast/app/src/codeunits/ExecuteNonCompanyUpgrade.Codeunit.al @@ -7,9 +7,6 @@ codeunit 4055 "Execute Non-Company Upgrade" { Description = 'This codeunit executes Non-Company upgrade.'; TableNo = "Hybrid Replication Summary"; - ObsoleteState = Pending; - ObsoleteReason = 'This functionality will be replaced by invoking the actual upgrade from each of the apps'; - ObsoleteTag = '17.0'; trigger OnRun() begin diff --git a/Apps/W1/HybridGP/app/src/Migration/Items/GPItemMigrator.codeunit.al b/Apps/W1/HybridGP/app/src/Migration/Items/GPItemMigrator.codeunit.al index 5d7a1ab3d1..616b63f03c 100644 --- a/Apps/W1/HybridGP/app/src/Migration/Items/GPItemMigrator.codeunit.al +++ b/Apps/W1/HybridGP/app/src/Migration/Items/GPItemMigrator.codeunit.al @@ -346,6 +346,7 @@ codeunit 4019 "GP Item Migrator" TempTrackingSpecification: Record "Tracking Specification" temporary; DataMigrationErrorLogging: Codeunit "Data Migration Error Logging"; CreateReserveEntry: Codeunit "Create Reserv. Entry"; + ItemJnlLineReserve: Codeunit "Item Jnl. Line-Reserve"; ExpirationDate: Date; begin if GPItem.ItemTrackingCode = '' then @@ -353,7 +354,7 @@ codeunit 4019 "GP Item Migrator" DataMigrationErrorLogging.SetLastRecordUnderProcessing(Format(GPItemTransactions.RecordId)); - TempTrackingSpecification.InitFromItemJnlLine(ItemJnlLine); + ItemJnlLineReserve.InitFromItemJnlLine(TempTrackingSpecification, ItemJnlLine); if GPItemTransactions.ExpirationDate = DMY2Date(1, 1, 1900) then ExpirationDate := 0D diff --git a/Apps/W1/MasterDataManagement/app/app.json b/Apps/W1/MasterDataManagement/app/app.json index 3c24d8b34d..9f80f8cc52 100644 --- a/Apps/W1/MasterDataManagement/app/app.json +++ b/Apps/W1/MasterDataManagement/app/app.json @@ -19,7 +19,7 @@ "name": "_Exclude_Master_Data_Management_Test_Library", "publisher": "Microsoft" } - ], + ], "screenshots": [ ], diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/MSUniversalPrintAdmin.PermissionSet.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/MSUniversalPrintAdmin.PermissionSet.al index 82d9eee056..4ee3e1ad8c 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/MSUniversalPrintAdmin.PermissionSet.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/MSUniversalPrintAdmin.PermissionSet.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; permissionset 2758 "MS Universal Print - Admin" { diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintEdit.PermissionSet.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintEdit.PermissionSet.al index dae5f80962..cbcd959de1 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintEdit.PermissionSet.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintEdit.PermissionSet.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; permissionset 2757 "UniversalPrint - Edit" { diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintObjects.PermissionSet.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintObjects.PermissionSet.al index 963dfc3ece..baa6d1d706 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintObjects.PermissionSet.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintObjects.PermissionSet.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; permissionset 2752 "UniversalPrint - Objects" { diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintRead.PermissionSet.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintRead.PermissionSet.al index d38bc0639c..dbe21f04e8 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintRead.PermissionSet.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/UniversalPrintRead.PermissionSet.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; permissionset 2759 "UniversalPrint - Read" { diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicMicrosoftUniversalPrint.permissionsetext.al index 0cf5dbfe40..477419d752 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2751 "D365 BASIC - Microsoft Universal Print" extends "D365 BASIC" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicisvMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicisvMicrosoftUniversalPrint.permissionsetext.al index eec2c90f0f..27e50c99fa 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicisvMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365basicisvMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2750 "D365 BASIC ISV - Microsoft Universal Print" extends "D365 BASIC ISV" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365busfullaccessMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365busfullaccessMicrosoftUniversalPrint.permissionsetext.al index 5068c04582..54a0410906 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365busfullaccessMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365busfullaccessMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2752 "D365 BUS FULL ACCESS - Microsoft Universal Print" extends "D365 BUS FULL ACCESS" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365buspremiumMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365buspremiumMicrosoftUniversalPrint.permissionsetext.al index 4c03176dcc..db4d02a76a 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365buspremiumMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365buspremiumMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2753 "D365 BUS PREMIUM - Microsoft Universal Print" extends "D365 BUS PREMIUM" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365fullaccessMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365fullaccessMicrosoftUniversalPrint.permissionsetext.al index 1629f6f451..3e0ad4c5ff 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365fullaccessMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365fullaccessMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2754 "D365 FULL ACCESS - Microsoft Universal Print" extends "D365 FULL ACCESS" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365readMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365readMicrosoftUniversalPrint.permissionsetext.al index 7e83942c47..3eacc8f552 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365readMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365readMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2755 "D365 READ - Microsoft Universal Print" extends "D365 READ" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365teammemberMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365teammemberMicrosoftUniversalPrint.permissionsetext.al index d2f7706a54..dab3662b75 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365teammemberMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/d365teammemberMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2756 "D365 TEAM MEMBER - Microsoft Universal Print" extends "D365 TEAM MEMBER" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/intelligentcloudMicrosoftUniversalPrint.permissionsetext.al b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/intelligentcloudMicrosoftUniversalPrint.permissionsetext.al index e318527f20..c3df9eaef3 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/Permissions/intelligentcloudMicrosoftUniversalPrint.permissionsetext.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/Permissions/intelligentcloudMicrosoftUniversalPrint.permissionsetext.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Security.AccessControl; permissionsetextension 2757 "INTELLIGENT CLOUD - Microsoft Universal Print" extends "INTELLIGENT CLOUD" diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/AddUniversalPrintersWizard.Page.al b/Apps/W1/MicrosoftUniversalPrint/app/src/AddUniversalPrintersWizard.Page.al index acb4739deb..eb7b974bff 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/AddUniversalPrintersWizard.Page.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/AddUniversalPrintersWizard.Page.al @@ -3,7 +3,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Environment; using System.Telemetry; @@ -22,9 +22,9 @@ page 2752 "Add Universal Printers Wizard" { Editable = false; ShowCaption = false; - Visible = TopBannerVisible and (CurrentStep <> CurrentStep::Done); + Visible = this.TopBannerVisible and (this.CurrentStep <> this.CurrentStep::Done); - field(""; MediaResourcesStandard."Media Reference") + field(""; this.MediaResourcesStandard."Media Reference") { ApplicationArea = All; Caption = ''; @@ -36,9 +36,9 @@ page 2752 "Add Universal Printers Wizard" { Editable = false; ShowCaption = false; - Visible = TopBannerVisible and (CurrentStep = CurrentStep::Done); + Visible = this.TopBannerVisible and (this.CurrentStep = this.CurrentStep::Done); - field(""; MediaResourcesDone."Media Reference") + field(""; this.MediaResourcesDone."Media Reference") { ApplicationArea = All; Caption = ''; @@ -49,7 +49,7 @@ page 2752 "Add Universal Printers Wizard" group(Intro) { Caption = 'Intro'; - Visible = CurrentStep = CurrentStep::Intro; + Visible = this.CurrentStep = this.CurrentStep::Intro; group("Para0.1") { @@ -64,7 +64,7 @@ page 2752 "Add Universal Printers Wizard" ApplicationArea = All; Caption = 'Note that a Universal Print subscription is required and Universal Print needs to be available in your region.'; } - field(LearMore; LearnMoreTxt) + field(LearMore; this.LearnMoreTxt) { ApplicationArea = All; Editable = false; @@ -72,7 +72,7 @@ page 2752 "Add Universal Printers Wizard" trigger OnDrillDown() begin - Hyperlink(UniversalPrintUrlTxt); + Hyperlink(this.UniversalPrintUrlTxt); end; } } @@ -85,7 +85,7 @@ page 2752 "Add Universal Printers Wizard" group(PrivacyNoticeStep) { ShowCaption = false; - Visible = CurrentStep = CurrentStep::PrivacyNotice; + Visible = this.CurrentStep = this.CurrentStep::PrivacyNotice; group(PrivacyNoticeInner) { Caption = 'Your privacy is important to us'; @@ -95,7 +95,7 @@ page 2752 "Add Universal Printers Wizard" ApplicationArea = All; Caption = 'This feature utilizes Microsoft Universal Print. By continuing you are affirming that you understand that the data handling and compliance standards of Microsoft Universal Print may not be the same as those provided by Microsoft Dynamics 365 Business Central. Please consult the documentation for Universal Print to learn more.'; } - field(PrivacyNoticeStatement; PrivacyStatementTxt) + field(PrivacyNoticeStatement; this.PrivacyStatementTxt) { ApplicationArea = All; Editable = false; @@ -111,7 +111,7 @@ page 2752 "Add Universal Printers Wizard" group(OnPremAadSetup) { Caption = 'Connect your Microsoft Entra application'; - Visible = CurrentStep = CurrentStep::OnPremAadSetup; + Visible = this.CurrentStep = this.CurrentStep::OnPremAadSetup; group("Para1.1") { @@ -122,7 +122,7 @@ page 2752 "Add Universal Printers Wizard" ApplicationArea = All; Caption = 'To add Universal Print printers to Business Central on-premises, you''ll first need a registered application for Business Central in Microsoft Entra ID.'; } - field("Para1.1.3"; LearnMoreAzureAppTxt) + field("Para1.1.3"; this.LearnMoreAzureAppTxt) { ApplicationArea = All; Editable = false; @@ -130,7 +130,7 @@ page 2752 "Add Universal Printers Wizard" trigger OnDrillDown() begin - Hyperlink(AzureAppLinkTxt); + Hyperlink(this.AzureAppLinkTxt); end; } label("Para1.1.2") @@ -143,19 +143,19 @@ page 2752 "Add Universal Printers Wizard" group(AutoAdd) { Caption = 'Adding Universal Print Printers'; - Visible = CurrentStep = CurrentStep::AutoAdd; + Visible = this.CurrentStep = this.CurrentStep::AutoAdd; group("Para2.1") { Caption = ''; - Visible = not HasLicense; + Visible = not this.HasLicense; label("Para2.1.1") { ApplicationArea = All; Caption = 'You don''t seem to have access to Universal Print. Make sure you have a Universal Print subscription, and that your account has been assigned a Universal Print license.'; Style = Attention; } - field("Para2.1.2"; LearnMoreSignupTxt) + field("Para2.1.2"; this.LearnMoreSignupTxt) { ApplicationArea = All; Editable = false; @@ -164,7 +164,7 @@ page 2752 "Add Universal Printers Wizard" trigger OnDrillDown() begin - Hyperlink(UniversalPrintUrlTxt); + Hyperlink(this.UniversalPrintUrlTxt); end; } } @@ -182,7 +182,7 @@ page 2752 "Add Universal Printers Wizard" ApplicationArea = All; Caption = 'You''ll first need to manage and share printers in Universal Print portal.'; } - field("Para2.2.3"; LearnMoreUniversalPrintPortalTxt) + field("Para2.2.3"; this.LearnMoreUniversalPrintPortalTxt) { ApplicationArea = All; Editable = false; @@ -190,7 +190,7 @@ page 2752 "Add Universal Printers Wizard" trigger OnDrillDown() begin - Hyperlink(UniversalPrintGraphHelper.GetUniversalPrintPortalUrl()); + Hyperlink(this.UniversalPrintGraphHelper.GetUniversalPrintPortalUrl()); end; } label(EmptySpace2) @@ -209,19 +209,19 @@ page 2752 "Add Universal Printers Wizard" group(Done) { Caption = 'Done'; - Visible = CurrentStep = CurrentStep::Done; + Visible = this.CurrentStep = this.CurrentStep::Done; group("Para3.1") { Caption = 'That''s it!'; - Visible = TotalAddedPrinters <> 0; + Visible = this.TotalAddedPrinters <> 0; label("Para3.1.1") { ApplicationArea = All; Caption = 'Printers that are shared with you through Universal Print have been added to Business Central.'; } - field(NumberOfPrintersAddedField; NumberOfPrintersAddedText) + field(NumberOfPrintersAddedField; this.NumberOfPrintersAddedText) { ShowCaption = false; ApplicationArea = All; @@ -248,7 +248,7 @@ page 2752 "Add Universal Printers Wizard" group("Para3.2") { Caption = 'That''s it!'; - Visible = TotalAddedPrinters = 0; + Visible = this.TotalAddedPrinters = 0; label("Para3.2.1") { @@ -278,33 +278,33 @@ page 2752 "Add Universal Printers Wizard" { ApplicationArea = All; Caption = 'Back'; - Enabled = BackEnabled; + Enabled = this.BackEnabled; Image = PreviousRecord; InFooterBar = true; trigger OnAction() begin - GoToNextStep(false); + this.GoToNextStep(false); end; } action(ActionNext) { ApplicationArea = All; Caption = 'Next'; - Enabled = NextEnabled; + Enabled = this.NextEnabled; Image = NextRecord; InFooterBar = true; trigger OnAction() begin - GoToNextStep(true); + this.GoToNextStep(true); end; } action(ActionFinish) { ApplicationArea = All; Caption = 'Finish'; - Enabled = FinishEnabled; + Enabled = this.FinishEnabled; Image = Approve; InFooterBar = true; @@ -320,32 +320,32 @@ page 2752 "Add Universal Printers Wizard" var EnvironmentInformation: Codeunit "Environment Information"; begin - LoadTopBanners(); + this.LoadTopBanners(); - IsOnPrem := EnvironmentInformation.IsOnPrem(); + this.IsOnPrem := EnvironmentInformation.IsOnPrem(); end; trigger OnOpenPage() var UniversalPrinterSettings: Record "Universal Printer Settings"; begin - FeatureTelemetry.LogUptake('0000GFV', UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::Discovered); + this.FeatureTelemetry.LogUptake('0000GFV', this.UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::Discovered); if not UniversalPrinterSettings.WritePermission() then - Error(NoTablePermissionsErr); + Error(this.NoTablePermissionsErr); - SetStep(CurrentStep::Intro); + this.SetStep(this.CurrentStep::Intro); end; local procedure SetStep(NewStep: Option) begin - if (NewStep < CurrentStep::Intro) or (NewStep > CurrentStep::Done) then - Error(StepOutOfRangeErr); + if (NewStep < this.CurrentStep::Intro) or (NewStep > this.CurrentStep::Done) then + Error(this.StepOutOfRangeErr); - CurrentStep := NewStep; + this.CurrentStep := NewStep; - FinishEnabled := CurrentStep = CurrentStep::Done; - BackEnabled := (CurrentStep > CurrentStep::Intro) and (CurrentStep <> CurrentStep::Done); - NextEnabled := CurrentStep < CurrentStep::Done; + this.FinishEnabled := this.CurrentStep = this.CurrentStep::Done; + this.BackEnabled := (this.CurrentStep > this.CurrentStep::Intro) and (this.CurrentStep <> this.CurrentStep::Done); + this.NextEnabled := this.CurrentStep < this.CurrentStep::Done; CurrPage.Update(); end; @@ -361,16 +361,16 @@ page 2752 "Add Universal Printers Wizard" // Go to next step, but if that step sould be hidden, jump again. Notice that this works because it's never // the case that two subsequent steps are hidden (in which case either forward or backwards will break). - NextStep := CurrentStep + StepValue; + NextStep := this.CurrentStep + StepValue; - if NextStep = CurrentStep::OnPremAadSetup then - if not ShowOnPremAadSetupStep() then + if NextStep = this.CurrentStep::OnPremAadSetup then + if not this.ShowOnPremAadSetupStep() then NextStep += StepValue; - if (NextStep < CurrentStep::Intro) or (NextStep > CurrentStep::Done) then begin - NextStep := CurrentStep; - Session.LogMessage('0000EJW', StrSubstNo(StepOutOfRangeTelemetryTxt, CurrentStep, Forward), Verbosity::Warning, DataClassification::SystemMetadata, - TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + if (NextStep < this.CurrentStep::Intro) or (NextStep > this.CurrentStep::Done) then begin + NextStep := this.CurrentStep; + Session.LogMessage('0000EJW', StrSubstNo(this.StepOutOfRangeTelemetryTxt, this.CurrentStep, Forward), Verbosity::Warning, DataClassification::SystemMetadata, + TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); end; end; @@ -379,28 +379,28 @@ page 2752 "Add Universal Printers Wizard" NextStep: Option; begin if Forward then - PerformOperationAfterStep(CurrentStep); + this.PerformOperationAfterStep(this.CurrentStep); - NextStep := CalculateNextStep(Forward); - if NextStep = CurrentStep::AutoAdd then - CheckLicense(); + NextStep := this.CalculateNextStep(Forward); + if NextStep = this.CurrentStep::AutoAdd then + this.CheckLicense(); - SetStep(NextStep); + this.SetStep(NextStep); end; local procedure PerformOperationAfterStep(AfterStep: Option) begin case AfterStep of - CurrentStep::OnPremAadSetup: - AadOnpremSetup(); - CurrentStep::AutoAdd: - StartAutoAdd(); + this.CurrentStep::OnPremAadSetup: + this.AadOnpremSetup(); + this.CurrentStep::AutoAdd: + this.StartAutoAdd(); end; end; local procedure CheckLicense() begin - HasLicense := UniversalPrintGraphHelper.CheckLicense(); + this.HasLicense := this.UniversalPrintGraphHelper.CheckLicense(); end; local procedure AadOnpremSetup() @@ -408,16 +408,16 @@ page 2752 "Add Universal Printers Wizard" [NonDebuggable] AccessToken: Text; begin - if not UniversalPrintGraphHelper.TryGetAccessToken(AccessToken, true) then - Error(NoTokenForOnPremErr); + if not this.UniversalPrintGraphHelper.TryGetAccessToken(AccessToken, true) then + Error(this.NoTokenForOnPremErr); end; local procedure StartAutoAdd() begin - TotalAddedPrinters := UniversalPrinterSetup.AddAllPrintShares(); - if TotalAddedPrinters > 0 then - FeatureTelemetry.LogUptake('0000GFW', UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::"Set up"); - NumberOfPrintersAddedText := StrSubstNo(NumberOfPrintersAddedTemplateTxt, TotalAddedPrinters); + this.TotalAddedPrinters := this.UniversalPrinterSetup.AddAllPrintShares(); + if this.TotalAddedPrinters > 0 then + this.FeatureTelemetry.LogUptake('0000GFW', this.UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::"Set up"); + this.NumberOfPrintersAddedText := StrSubstNo(this.NumberOfPrintersAddedTemplateTxt, this.TotalAddedPrinters); end; local procedure ShowOnPremAadSetupStep(): Boolean @@ -426,8 +426,8 @@ page 2752 "Add Universal Printers Wizard" AccessToken: Text; begin // Show only if OnPrem and the setup is not done - if IsOnPrem then - if not UniversalPrintGraphHelper.TryGetAccessToken(AccessToken, false) then + if this.IsOnPrem then + if not this.UniversalPrintGraphHelper.TryGetAccessToken(AccessToken, false) then exit(true); exit(false); end; @@ -437,13 +437,13 @@ page 2752 "Add Universal Printers Wizard" MediaRepositoryStandard: Record "Media Repository"; MediaRepositoryDone: Record "Media Repository"; begin - if MediaRepositoryStandard.Get('AssistedSetup-NoText-400px.png', Format(ClientTypeManagement.GetCurrentClientType())) and - MediaRepositoryDone.Get('AssistedSetupDone-NoText-400px.png', Format(ClientTypeManagement.GetCurrentClientType())) + if MediaRepositoryStandard.Get('AssistedSetup-NoText-400px.png', Format(this.ClientTypeManagement.GetCurrentClientType())) and + MediaRepositoryDone.Get('AssistedSetupDone-NoText-400px.png', Format(this.ClientTypeManagement.GetCurrentClientType())) then - if MediaResourcesStandard.Get(MediaRepositoryStandard."Media Resources Ref") and - MediaResourcesDone.Get(MediaRepositoryDone."Media Resources Ref") + if this.MediaResourcesStandard.Get(MediaRepositoryStandard."Media Resources Ref") and + this.MediaResourcesDone.Get(MediaRepositoryDone."Media Resources Ref") then - TopBannerVisible := MediaResourcesDone."Media Reference".HasValue; + this.TopBannerVisible := this.MediaResourcesDone."Media Reference".HasValue; end; var diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintDocumentReady.Codeunit.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintDocumentReady.Codeunit.al index 70acf2fadc..1ee21c11d8 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintDocumentReady.Codeunit.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintDocumentReady.Codeunit.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Environment; using System.Telemetry; @@ -44,9 +44,9 @@ codeunit 2751 "Universal Print Document Ready" if ObjectPayload.Get('documenttype', PropertyBag) then DocumentType := PropertyBag.AsValue().AsText(); - FileNameWithExtension := GetFileNameWithExtension(FileName, DocumentType); + FileNameWithExtension := this.GetFileNameWithExtension(FileName, DocumentType); - Success := SendPrintJob(UniversalPrinterSettings, DocumentStream, FileNameWithExtension, DocumentType); + Success := this.SendPrintJob(UniversalPrinterSettings, DocumentStream, FileNameWithExtension, DocumentType); end; procedure SendPrintJob(UniversalPrinterSettings: Record "Universal Printer Settings"; DocumentInStream: InStream; FileNameWithExtension: Text; DocumentType: Text): Boolean @@ -70,13 +70,13 @@ codeunit 2751 "Universal Print Document Ready" if FileNameWithExtension = '' then exit(false); - FeatureTelemetry.LogUptake('0000GFX', UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::Used); + this.FeatureTelemetry.LogUptake('0000GFX', this.UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::Used); // check if the printer is shared to user if not UniversalPrinterSetup.PrintShareExists(UniversalPrinterSettings."Print Share ID") then begin if GuiAllowed() then - Message(NoAccessToPrinterErr, UniversalPrinterSettings.Name); - Session.LogMessage('0000EFB', PrintShareNotFoundTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Message(this.NoAccessToPrinterErr, UniversalPrinterSettings.Name); + Session.LogMessage('0000EFB', this.PrintShareNotFoundTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit(false); end; @@ -86,48 +86,48 @@ codeunit 2751 "Universal Print Document Ready" // https://go.microsoft.com/fwlink/?linkid=2206361 // check the maximum bytes in any given request is less than 10 MB. - if Size > MaximumRequestSizeInBytes() then begin + if Size > this.MaximumRequestSizeInBytes() then begin if GuiAllowed() then - Message(PrintJobTooLargeErr); - Session.LogMessage('0000EJX', strSubstNo(PrintJobTooLargeTelemetryTxt, Size), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Message(this.PrintJobTooLargeErr); + Session.LogMessage('0000EJX', strSubstNo(this.PrintJobTooLargeTelemetryTxt, Size), Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit(false); end; // TODO: Split larger upload requests. // create a print job and store the resulting Job ID and Document ID. - if not UniversalPrintGraphHelper.CreatePrintJobRequest(UniversalPrinterSettings, JobID, DocumentID, ErrorMessage) then begin + if not this.UniversalPrintGraphHelper.CreatePrintJobRequest(UniversalPrinterSettings, JobID, DocumentID, ErrorMessage) then begin if GuiAllowed() then - Message(UnableToCreateJobErr, ErrorMessage); - Session.LogMessage('0000EFC', PrintJobNotCreatedTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Message(this.UnableToCreateJobErr, ErrorMessage); + Session.LogMessage('0000EFC', this.PrintJobNotCreatedTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit(false); end; // create an upload session - if not UniversalPrintGraphHelper.CreateUploadSessionRequest(UniversalPrinterSettings."Print Share ID", FileNameWithExtension, DocumentType, Size, JobID, DocumentID, UploadUrl, ErrorMessage) then begin + if not this.UniversalPrintGraphHelper.CreateUploadSessionRequest(UniversalPrinterSettings."Print Share ID", FileNameWithExtension, DocumentType, Size, JobID, DocumentID, UploadUrl, ErrorMessage) then begin if GuiAllowed() then - Message(UnableToUploadDocErr, JobID, ErrorMessage); - Session.LogMessage('0000EFZ', strSubstNo(PrintJobUploadSessionNotCreatedTxt, Size), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Message(this.UnableToUploadDocErr, JobID, ErrorMessage); + Session.LogMessage('0000EFZ', strSubstNo(this.PrintJobUploadSessionNotCreatedTxt, JobID, Size), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit(false); end; // upload document data to the document. - if not UniversalPrintGraphHelper.UploadDataRequest(UniversalPrinterSettings."Print Share ID", UploadUrl, TempBlob, 0, Size - 1, Size, JobID, DocumentID, ErrorMessage) then begin + if not this.UniversalPrintGraphHelper.UploadDataRequest(UniversalPrinterSettings."Print Share ID", UploadUrl, TempBlob, 0, Size - 1, Size, JobID, DocumentID, ErrorMessage) then begin if GuiAllowed() then - Message(UnableToUploadDocErr, JobID, ErrorMessage); - Session.LogMessage('0000EFD', strSubstNo(PrintJobNotUploadedTelemetryTxt, Size), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Message(this.UnableToUploadDocErr, JobID, ErrorMessage); + Session.LogMessage('0000EFD', strSubstNo(this.PrintJobNotUploadedTelemetryTxt, JobID, Size), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit(false); end; // Start the print job. - if not UniversalPrintGraphHelper.StartPrintJobRequest(UniversalPrinterSettings."Print Share ID", JobID, JobStateDescription, ErrorMessage) then begin + if not this.UniversalPrintGraphHelper.StartPrintJobRequest(UniversalPrinterSettings."Print Share ID", JobID, JobStateDescription, ErrorMessage) then begin if GuiAllowed() then - Message(UnableToStartJobErr, JobID, ErrorMessage); - Session.LogMessage('0000EFE', PrintJobNotStartedTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Message(this.UnableToStartJobErr, JobID, ErrorMessage); + Session.LogMessage('0000EFE', strSubstNo(this.PrintJobNotStartedTxt, JobID, Size), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit(false); end; - FeatureTelemetry.LogUsage('0000GFY', UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), 'Universal Print Job Sent'); - Session.LogMessage('0000FSY', JobSentTelemtryTxt, Verbosity::Verbose, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + this.FeatureTelemetry.LogUsage('0000GFY', this.UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), 'Universal Print Job Sent'); + Session.LogMessage('0000FSY', this.JobSentTelemtryTxt, Verbosity::Verbose, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit(true); end; @@ -167,8 +167,8 @@ codeunit 2751 "Universal Print Document Ready" JobSentTelemtryTxt: Label 'The print job has been sent for processing in Universal Print.', Locked = true; PrintShareNotFoundTelemetryTxt: Label 'Universal Print share is not found.', Locked = true; PrintJobNotCreatedTelemetryTxt: Label 'Creating Universal Print job failed.', Locked = true; - PrintJobNotUploadedTelemetryTxt: Label 'Uploading Universal Print job of size %1 failed.', Locked = true, Comment = '%1 = Size of print job'; - PrintJobUploadSessionNotCreatedTxt: Label 'Creating Universal Print job upload session of size %1 failed.', Locked = true, Comment = '%1 = Size of print job'; - PrintJobNotStartedTxt: Label 'Starting Universal Print job failed.', Locked = true; + PrintJobNotUploadedTelemetryTxt: Label 'Uploading Universal Print data for job %1 of size %2 failed.', Locked = true, Comment = '%1 = Print job ID, %2 = Size of the print job'; + PrintJobUploadSessionNotCreatedTxt: Label 'Creating Universal Print upload session for job %1 of size %2 failed.', Locked = true, Comment = '%1 = Print job ID, %2 = Size of the print job'; + PrintJobNotStartedTxt: Label 'Starting Universal Print job %1 of size %2 failed.', Locked = true, Comment = '%1 = Print job ID, %2 = Size of the print job'; PrintJobTooLargeTelemetryTxt: Label 'The Universal Print job of size %1 is too large.', Locked = true, Comment = '%1 = Size of print job'; } \ No newline at end of file diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintGraphHelper.Codeunit.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintGraphHelper.Codeunit.al index b63764f53d..4d4875a310 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintGraphHelper.Codeunit.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintGraphHelper.Codeunit.al @@ -2,9 +2,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System; +using System.Device; using System.Azure.Identity; using System.Integration; using System.Utilities; @@ -28,9 +29,9 @@ codeunit 2752 "Universal Print Graph Helper" RequestURL: Text; MorePrintSharesExist: Boolean; begin - RequestURL := GetGraphPrintSharesUrl(); + RequestURL := this.GetGraphPrintSharesUrl(); repeat - if not InvokeRequest(RequestURL, 'GET', '', ResponseContent, ErrorMessage) then + if not this.InvokeRequest(RequestURL, 'GET', '', ResponseContent, ErrorMessage) then exit(false); if not PrintShareJsonObject.ReadFrom(ResponseContent) then @@ -60,7 +61,7 @@ codeunit 2752 "Universal Print Graph Helper" var ResponseContent: Text; begin - if not InvokeRequest(GetGraphPrintShareSelectUrl(ID), 'GET', '', ResponseContent, ErrorMessage) then + if not this.InvokeRequest(this.GetGraphPrintShareSelectUrl(ID), 'GET', '', ResponseContent, ErrorMessage) then exit(false); if not PrintShare.ReadFrom(ResponseContent) then @@ -79,7 +80,7 @@ codeunit 2752 "Universal Print Graph Helper" ErrorMessage: Text; StatusCode: DotNet HttpStatusCode; begin - if InvokeRequest(GetGraphPrintSharesUrl(), 'GET', '', ResponseContent, ErrorMessage, StatusCode) then + if this.InvokeRequest(this.GetGraphPrintSharesUrl(), 'GET', '', ResponseContent, ErrorMessage, StatusCode) then exit(true); if not IsNull(StatusCode) then @@ -101,17 +102,18 @@ codeunit 2752 "Universal Print Graph Helper" ResponseContent: Text; begin BodyConfigJsonObject.Add('outputBin', UniversalPrinterSettings."Paper Tray"); + BodyConfigJsonObject.Add('mediaSize', this.ConvertPaperSizeToUniversalPrintMediaSize(UniversalPrinterSettings."Paper Size")); if UniversalPrinterSettings.Landscape then - BodyConfigJsonObject.Add('orientation', GetOrientationName(Enum::"Universal Printer Orientation"::landscape)); + BodyConfigJsonObject.Add('orientation', this.GetOrientationName(Enum::"Universal Printer Orientation"::landscape)); BodyJsonObject.Add('configuration', BodyConfigJsonObject); - if not InvokeRequest(GetGraphPrintShareJobsUrl(UniversalPrinterSettings."Print Share ID"), 'POST', Format(BodyJsonObject), ResponseContent, ErrorMessage) then + if not this.InvokeRequest(this.GetGraphPrintShareJobsUrl(UniversalPrinterSettings."Print Share ID"), 'POST', Format(BodyJsonObject), ResponseContent, ErrorMessage) then exit(false); if not ResponseJsonObject.ReadFrom(ResponseContent) then exit(false); - if not GetJsonKeyValue(ResponseJsonObject, 'id', JobId) then + if not this.GetJsonKeyValue(ResponseJsonObject, 'id', JobId) then exit(false); if not ResponseJsonObject.SelectToken('documents', DocumentsJsonToken) then @@ -122,7 +124,7 @@ codeunit 2752 "Universal Print Graph Helper" exit(false); FirstDocument := DocumentJsonToken.AsObject(); - if not GetJsonKeyValue(FirstDocument, 'id', DocumentId) then + if not this.GetJsonKeyValue(FirstDocument, 'id', DocumentId) then exit(false); exit(true); @@ -145,13 +147,13 @@ codeunit 2752 "Universal Print Graph Helper" BodyPropertiesJsonObject.Add('size', Size); BodyJsonObject.Add('properties', BodyPropertiesJsonObject); - if not InvokeRequest(GetGraphDocumentCreateUploadSessionUrl(PrintShareId, JobId, DocumentId), 'POST', Format(BodyJsonObject), ResponseContent, ErrorMessage) then + if not this.InvokeRequest(this.GetGraphDocumentCreateUploadSessionUrl(PrintShareId, JobId, DocumentId), 'POST', Format(BodyJsonObject), ResponseContent, ErrorMessage) then exit(false); if not ResponseJsonObject.ReadFrom(ResponseContent) then exit(false); - if not GetJsonKeyValue(ResponseJsonObject, 'uploadUrl', UploadUrl) then + if not this.GetJsonKeyValue(ResponseJsonObject, 'uploadUrl', UploadUrl) then exit(false); exit(true); @@ -164,7 +166,7 @@ codeunit 2752 "Universal Print Graph Helper" ResponseContent: Text; StatusCode: DotNet HttpStatusCode; begin - if not AddHeaders(UploadUrl, 'PUT', HttpWebRequestMgt) then + if not this.AddHeaders(UploadUrl, 'PUT', HttpWebRequestMgt) then exit(false); // E.g. value for 'Content-Range' is 'bytes 0-72796/4533322' @@ -173,7 +175,7 @@ codeunit 2752 "Universal Print Graph Helper" HttpWebRequestMgt.SetContentLength(TotalSize); HttpWebRequestMgt.AddBodyBlob(TempBlob); - exit(InvokeRequestAndReadResponse(HttpWebRequestMgt, ResponseContent, ErrorMessage, StatusCode)); + exit(this.InvokeRequestAndReadResponse(HttpWebRequestMgt, ResponseContent, ErrorMessage, StatusCode)); end; procedure StartPrintJobRequest(PrintShareId: Text; JobId: Text; var JobStateDescription: Text; var ErrorMessage: Text): Boolean @@ -181,13 +183,13 @@ codeunit 2752 "Universal Print Graph Helper" ResponseJsonObject: JsonObject; ResponseContent: Text; begin - if not InvokeRequest(GetGraphStartPrintJobUrl(PrintShareId, JobId), 'POST', '', ResponseContent, ErrorMessage) then + if not this.InvokeRequest(this.GetGraphStartPrintJobUrl(PrintShareId, JobId), 'POST', '', ResponseContent, ErrorMessage) then exit(false); if not ResponseJsonObject.ReadFrom(ResponseContent) then exit(false); - if not GetJsonKeyValue(ResponseJsonObject, 'description', JobStateDescription) then + if not this.GetJsonKeyValue(ResponseJsonObject, 'description', JobStateDescription) then exit(false); exit(true); @@ -210,10 +212,10 @@ codeunit 2752 "Universal Print Graph Helper" var AzureADMgt: Codeunit "Azure AD Mgt."; begin - AccessToken := AzureADMgt.GetAccessToken(GetGraphDomain(), '', ShowDialog); + AccessToken := AzureADMgt.GetAccessToken(this.GetGraphDomain(), '', ShowDialog); if AccessToken = '' then begin - Session.LogMessage('0000EFG', NoTokenTelemetryTxt, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintTelemetryCategoryTxt); - Error(UserNotAuthenticatedTxt); + Session.LogMessage('0000EFG', this.NoTokenTelemetryTxt, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintTelemetryCategoryTxt); + Error(this.UserNotAuthenticatedTxt); end; end; @@ -235,7 +237,7 @@ codeunit 2752 "Universal Print Graph Helper" if Verb <> 'GET' then HttpWebRequestMgt.AddBodyAsText(Body); - exit(InvokeRequestAndReadResponse(HttpWebRequestMgt, ResponseContent, ErrorMessage, StatusCode)); + exit(this.InvokeRequestAndReadResponse(HttpWebRequestMgt, ResponseContent, ErrorMessage, StatusCode)); end; local procedure InvokeRequestAndReadResponse(var HttpWebRequestMgt: Codeunit "Http Web Request Mgt."; var ResponseContent: Text; var ErrorMessage: Text; var StatusCode: DotNet HttpStatusCode): Boolean @@ -248,21 +250,21 @@ codeunit 2752 "Universal Print Graph Helper" if HttpWebRequestMgt.SendRequestAndReadTextResponse(ResponseContent, ResponseErrorMessage, ResponseErrorDetails, StatusCode, ResponseHeaders) then exit(true); - if not TryGetRequestIdfromHeaders(ResponseHeaders, RequestId) then - RequestId := NotFoundTelemetryTxt; + if not this.TryGetRequestIdfromHeaders(ResponseHeaders, RequestId) then + RequestId := this.NotFoundTelemetryTxt; - Session.LogMessage('0000EG1', StrSubstNo(InvokeWebRequestFailedTelemetryTxt, StatusCode, ResponseErrorMessage, RequestId), - Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintTelemetryCategoryTxt); + Session.LogMessage('0000EG1', StrSubstNo(this.InvokeWebRequestFailedTelemetryTxt, StatusCode, ResponseErrorMessage, RequestId), + Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintTelemetryCategoryTxt); Clear(ErrorMessage); if not IsNull(StatusCode) then if StatusCode in [401, 403, 404] then begin - ErrorMessage := NoAccessTxt; + ErrorMessage := this.NoAccessTxt; exit(false); end; if ErrorMessage = '' then - ErrorMessage := GetMessageFromErrorJSON(ResponseErrorDetails); + ErrorMessage := this.GetMessageFromErrorJSON(ResponseErrorDetails); if ErrorMessage = '' then ErrorMessage := ResponseErrorMessage; @@ -276,6 +278,72 @@ codeunit 2752 "Universal Print Graph Helper" RequestId := ResponseHeaders.Get('Request-Id'); end; + local procedure ConvertPaperSizeToUniversalPrintMediaSize(PaperSize: Enum "Printer Paper Kind"): Text + var + UniversalPrintMediaSize: Text; + begin + // For universal print supported media sizes, refer https://learn.microsoft.com/en-us/graph/api/resources/printercapabilities?view=graph-rest-1.0#mediasizes-values + case PaperSize of + PaperSize::A3: + UniversalPrintMediaSize := A3SizeTxt; + PaperSize::A4: + UniversalPrintMediaSize := A4SizeTxt; + PaperSize::A5: + UniversalPrintMediaSize := A5SizeTxt; + PaperSize::A6: + UniversalPrintMediaSize := A6SizeTxt; + PaperSize::JapanesePostcard: + UniversalPrintMediaSize := JPNHagakiSizeTxt; + PaperSize::Executive: + UniversalPrintMediaSize := NorthAmericaExecutiveSizeTxt; + PaperSize::Ledger: + UniversalPrintMediaSize := NorthAmericaLedgerSizeTxt; + PaperSize::Legal: + UniversalPrintMediaSize := NorthAmericaLegalSizeTxt; + PaperSize::Letter: + UniversalPrintMediaSize := NorthAmericaLetterSizeTxt; + PaperSize::Statement: + UniversalPrintMediaSize := NorthAmericaInvoiceSizeTxt + else + // Use the name value of the enum + UniversalPrintMediaSize := Format(PaperSize); + end; + + exit(UniversalPrintMediaSize); + end; + + internal procedure GetPaperSizeFromUniversalPrintMediaSize(textValue: Text): Enum "Printer Paper Kind" + var + PrinterPaperKind: Enum "Printer Paper Kind"; + OrdinalValue: Integer; + Index: Integer; + begin + // For universal print supported media sizes, refer https://learn.microsoft.com/en-us/graph/api/resources/printercapabilities?view=graph-rest-1.0#mediasizes-values + case textValue of + JPNHagakiSizeTxt: + PrinterPaperKind := Enum::"Printer Paper Kind"::JapanesePostcard; + NorthAmericaExecutiveSizeTxt: + PrinterPaperKind := PrinterPaperKind::Executive; + NorthAmericaLedgerSizeTxt: + PrinterPaperKind := PrinterPaperKind::Ledger; + NorthAmericaLegalSizeTxt: + PrinterPaperKind := PrinterPaperKind::Legal; + NorthAmericaLetterSizeTxt: + PrinterPaperKind := PrinterPaperKind::Letter; + NorthAmericaInvoiceSizeTxt: + PrinterPaperKind := PrinterPaperKind::Statement; + else begin + Index := PrinterPaperKind.Names.IndexOf(textValue); + if Index = 0 then + exit(Enum::"Printer Paper Kind"::A4); + + OrdinalValue := PrinterPaperKind.Ordinals.Get(Index); + PrinterPaperKind := Enum::"Printer Paper Kind".FromInteger(OrdinalValue); + end; + end; + exit(PrinterPaperKind); + end; + local procedure GetMessageFromErrorJSON(ErrorResponseContent: Text): Text var ResponseJsonObject: JsonObject; @@ -293,7 +361,7 @@ codeunit 2752 "Universal Print Graph Helper" exit; ErrorJsonObject := PropertyBag.AsObject(); - if GetJsonKeyValue(ErrorJsonObject, 'message', MessageValue) then + if this.GetJsonKeyValue(ErrorJsonObject, 'message', MessageValue) then exit(MessageValue); end; @@ -302,7 +370,7 @@ codeunit 2752 "Universal Print Graph Helper" [NonDebuggable] AccessToken: Text; begin - if not TryGetAccessToken(AccessToken, false) then + if not this.TryGetAccessToken(AccessToken, false) then exit(false); HttpWebRequestMgt.Initialize(Url); HttpWebRequestMgt.DisableUI(); @@ -314,17 +382,17 @@ codeunit 2752 "Universal Print Graph Helper" procedure GetUniversalPrintTelemetryCategory(): Text begin - exit(UniversalPrintTelemetryCategoryTxt); + exit(this.UniversalPrintTelemetryCategoryTxt); end; procedure GetUniversalPrintFeatureTelemetryName(): Text begin - exit(UniversalPrintFeatureTelemetryNameTxt); + exit(this.UniversalPrintFeatureTelemetryNameTxt); end; procedure GetUniversalPrintPortalUrl(): Text begin - exit(UniversalPrintPortalUrlTxt); + exit(this.UniversalPrintPortalUrlTxt); end; local procedure GetGuidAsString(GuidValue: Guid): Text @@ -355,37 +423,37 @@ codeunit 2752 "Universal Print Graph Helper" begin // https://graph.microsoft.com/v1.0/print/shares/?$top=1000 // NOTE: by default universal print returns 10 records only, which is too low for some customers. - exit(GetGraphDomain() + GetGraphAPIVersion() + '/print/shares/?$top=1000'); + exit(this.GetGraphDomain() + this.GetGraphAPIVersion() + '/print/shares/?$top=1000'); end; local procedure GetGraphPrintShareSelectUrl(PrintShareID: Text): Text begin // https://graph.microsoft.com/v1.0/print/shares/{PrintShareID}?$select=id,displayName,defaults,capabilities - exit(GetGraphDomain() + GetGraphAPIVersion() + '/print/shares/' + GetGuidAsString(PrintShareID) + '?$select=id,displayName,defaults,capabilities'); + exit(this.GetGraphDomain() + this.GetGraphAPIVersion() + '/print/shares/' + this.GetGuidAsString(PrintShareID) + '?$select=id,displayName,defaults,capabilities'); end; local procedure GetGraphPrintShareUrl(PrintShareID: Text): Text begin // https://graph.microsoft.com/v1.0/print/shares/{PrintShareID} - exit(GetGraphDomain() + GetGraphAPIVersion() + '/print/shares/' + GetGuidAsString(PrintShareID)); + exit(this.GetGraphDomain() + this.GetGraphAPIVersion() + '/print/shares/' + this.GetGuidAsString(PrintShareID)); end; local procedure GetGraphPrintShareJobsUrl(PrintShareID: Text): Text begin // https://graph.microsoft.com/v1.0/print/shares/{PrintShareID}/jobs - exit(GetGraphPrintShareUrl(GetGuidAsString(PrintShareID)) + '/jobs'); + exit(this.GetGraphPrintShareUrl(this.GetGuidAsString(PrintShareID)) + '/jobs'); end; local procedure GetGraphDocumentCreateUploadSessionUrl(PrintShareID: Text; PrintJobID: Text; PrintDocumentID: Text): Text begin // https://graph.microsoft.com/v1.0/print/shares/{PrintShareID}/jobs/{PrintJobID}/documents/{PrintDocumentID}/createUploadSession' - exit(GetGraphPrintShareJobsUrl(GetGuidAsString(PrintShareID)) + '/' + PrintJobID + '/documents/' + PrintDocumentID + '/createUploadSession'); + exit(this.GetGraphPrintShareJobsUrl(this.GetGuidAsString(PrintShareID)) + '/' + PrintJobID + '/documents/' + PrintDocumentID + '/createUploadSession'); end; local procedure GetGraphStartPrintJobUrl(PrintShareID: Text; PrintJobID: Text): Text begin // https://graph.microsoft.com/v1.0/print/shares/{PrintShareID}/jobs/{PrintJobID}/start - exit(GetGraphPrintShareJobsUrl(GetGuidAsString(PrintShareID)) + '/' + PrintJobID + '/start'); + exit(this.GetGraphPrintShareJobsUrl(this.GetGuidAsString(PrintShareID)) + '/' + PrintJobID + '/start'); end; var @@ -397,5 +465,15 @@ codeunit 2752 "Universal Print Graph Helper" InvokeWebRequestFailedTelemetryTxt: Label 'Invoking web request has failed. Status %1, Message %2, RequestId %3', Locked = true; NotFoundTelemetryTxt: Label 'Not Found.', Locked = true; UniversalPrintPortalUrlTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2153618', Locked = true; + A3SizeTxt: Label 'A3', Locked = true; + A4SizeTxt: Label 'A4', Locked = true; + A5SizeTxt: Label 'A5', Locked = true; + A6SizeTxt: Label 'A6', Locked = true; + JPNHagakiSizeTxt: Label 'JPN Hagaki', Locked = true; + NorthAmericaExecutiveSizeTxt: Label 'North America Executive', Locked = true; + NorthAmericaInvoiceSizeTxt: Label 'North America Invoice', Locked = true; + NorthAmericaLedgerSizeTxt: Label 'North America Ledger', Locked = true; + NorthAmericaLegalSizeTxt: Label 'North America Legal', Locked = true; + NorthAmericaLetterSizeTxt: Label 'North America Letter', Locked = true; } diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintShareBuffer.Table.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintShareBuffer.Table.al index f3ebb3bdf1..7867ba9882 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintShareBuffer.Table.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintShareBuffer.Table.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; /// /// Provides functionality to manage Print Shares. @@ -51,10 +51,10 @@ table 2752 "Universal Print Share Buffer" UniversalPrinterSetup: Codeunit "Universal Printer Setup"; begin UniversalPrintShareBuffer.CopyFilters(Rec); - Reset(); - DeleteAll(); + Rec.Reset(); + Rec.DeleteAll(); UniversalPrinterSetup.AddAllPrintSharesToBuffer(Rec); - CopyFilters(UniversalPrintShareBuffer); + Rec.CopyFilters(UniversalPrintShareBuffer); if Rec.FindFirst() then; end; } \ No newline at end of file diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintSharesList.Page.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintSharesList.Page.al index 805653efd6..796962323a 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintSharesList.Page.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrintSharesList.Page.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; /// /// Exposes functionality to manage configuration settings of universal Printers. diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterManagement.PageExt.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterManagement.PageExt.al index c29880673b..dc2f94451d 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterManagement.PageExt.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterManagement.PageExt.al @@ -2,7 +2,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; + +using System.Device; /// /// Exposes functionality to get Universal Print printersers in Printer Management. diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterOrientation.Enum.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterOrientation.Enum.al index b4a4ccafae..7cf8f7ace0 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterOrientation.Enum.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterOrientation.Enum.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; /// /// Specifies orientations used for printing. The value of each ID corresponds to the value assigned by Universal Print. diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterPaperUnit.Enum.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterPaperUnit.Enum.al index 15583fbb3b..b26e04b725 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterPaperUnit.Enum.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterPaperUnit.Enum.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; /// /// Specifies several of the units of measure used for printing. diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Page.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Page.al index 6509c5ac0c..8739441028 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Page.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Page.al @@ -2,9 +2,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using System.Telemetry; + +using System.Device; + /// /// Exposes functionality to manage configuration settings of Universal Print printers. /// @@ -23,7 +26,7 @@ page 2750 "Universal Printer Settings" Caption = 'Name'; ApplicationArea = All; ToolTip = 'Specifies the unique name of the printer settings.'; - Editable = NewMode; + Editable = this.NewMode; } field(PrintShareName; Rec."Print Share Name") { @@ -35,8 +38,8 @@ page 2750 "Universal Printer Settings" ShowMandatory = true; trigger OnAssistEdit() begin - UniversalPrinterSetup.LookupPrintShares(Rec); - IsSizeCustom := UniversalPrinterSetup.IsPaperSizeCustom(Rec."Paper Size"); + this.UniversalPrinterSetup.LookupPrintShares(Rec); + this.IsSizeCustom := this.UniversalPrinterSetup.IsPaperSizeCustom(Rec."Paper Size"); end; } field(Description; Rec.Description) @@ -45,7 +48,6 @@ page 2750 "Universal Printer Settings" ApplicationArea = All; ToolTip = 'Specifies the description of the printer.'; } - field(PaperKind; Rec."Paper Size") { Caption = 'Paper Size'; @@ -53,9 +55,9 @@ page 2750 "Universal Printer Settings" ToolTip = 'Specifies the printer''s selected paper size.'; trigger OnValidate() begin - IsSizeCustom := UniversalPrinterSetup.IsPaperSizeCustom(Rec."Paper Size"); + this.IsSizeCustom := this.UniversalPrinterSetup.IsPaperSizeCustom(Rec."Paper Size"); - if IsSizeCustom and ((Rec."Paper Width" <= 0) or (Rec."Paper Height" <= 0)) then begin + if this.IsSizeCustom and ((Rec."Paper Width" <= 0) or (Rec."Paper Height" <= 0)) then begin // Set default to A4 inches Rec."Paper Height" := 8.3; Rec."Paper Width" := 11.7; @@ -66,7 +68,7 @@ page 2750 "Universal Printer Settings" group(CustomProperties) { ShowCaption = false; - Visible = IsSizeCustom; + Visible = this.IsSizeCustom; group(Custom) { ShowCaption = false; @@ -77,7 +79,7 @@ page 2750 "Universal Printer Settings" ToolTip = 'Specifies the height of the paper.'; trigger OnValidate() begin - UniversalPrinterSetup.ValidatePaperHeight(Rec."Paper Height"); + this.UniversalPrinterSetup.ValidatePaperHeight(Rec."Paper Height"); end; } field(PaperWidth; Rec."Paper Width") @@ -87,7 +89,7 @@ page 2750 "Universal Printer Settings" ToolTip = 'Specifies the width of the paper.'; trigger OnValidate() begin - UniversalPrinterSetup.ValidatePaperWidth(Rec."Paper Width"); + this.UniversalPrinterSetup.ValidatePaperWidth(Rec."Paper Width"); end; } field(PaperUnit; Rec."Paper Unit") @@ -106,7 +108,7 @@ page 2750 "Universal Printer Settings" ToolTip = 'Specifies the printer''s output paper tray.'; trigger OnAssistEdit() begin - UniversalPrinterSetup.LookupPaperTrays(Rec); + this.UniversalPrinterSetup.LookupPaperTrays(Rec); end; } field(Landscape; Rec.Landscape) @@ -125,7 +127,7 @@ page 2750 "Universal Printer Settings" ApplicationArea = All; Caption = 'This feature utilizes Microsoft Universal Print. By continuing you are affirming that you understand that the data handling and compliance standards of Microsoft Universal Print may not be the same as those provided by Microsoft Dynamics 365 Business Central. Please consult the documentation for Universal Print to learn more.'; } - field(Privacy; PrivacyStatementTxt) + field(Privacy; this.PrivacyStatementTxt) { ApplicationArea = All; Editable = false; @@ -134,7 +136,7 @@ page 2750 "Universal Printer Settings" ToolTip = 'Opens a privacy help article.'; trigger OnDrillDown() begin - Hyperlink(PrivacyUrlTxt); + Hyperlink(this.PrivacyUrlTxt); end; } } @@ -163,34 +165,34 @@ page 2750 "Universal Printer Settings" trigger OnNewRecord(BelowxRec: Boolean) begin - NewMode := true; - InsertDefaults(Rec); + this.NewMode := true; + this.InsertDefaults(Rec); end; trigger OnOpenPage() var FeatureTelemetry: Codeunit "Feature Telemetry"; begin - FeatureTelemetry.LogUptake('0000GFZ', UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::Discovered); + FeatureTelemetry.LogUptake('0000GFZ', this.UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::Discovered); end; trigger OnDeleteRecord(): Boolean begin - UniversalPrinterSetup.DeletePrinterSettings(Rec.Name); - DeleteMode := true; + this.UniversalPrinterSetup.DeletePrinterSettings(Rec.Name); + this.DeleteMode := true; end; trigger OnAfterGetCurrRecord() begin - IsSizeCustom := UniversalPrinterSetup.IsPaperSizeCustom(Rec."Paper Size"); + this.IsSizeCustom := this.UniversalPrinterSetup.IsPaperSizeCustom(Rec."Paper Size"); end; trigger OnQueryClosePage(CloseAction: Action): Boolean begin - if DeleteMode then + if this.DeleteMode then exit(true); if (CloseAction in [ACTION::OK, ACTION::LookupOK]) then - exit(UniversalPrinterSetup.OnQueryClosePrinterSettingsPage(Rec)); + exit(this.UniversalPrinterSetup.OnQueryClosePrinterSettingsPage(Rec)); end; internal procedure InsertDefaults(var UniversalPrinterSettings: Record "Universal Printer Settings") diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Table.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Table.al index b8188d721d..d300e7e49c 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Table.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSettings.Table.al @@ -2,7 +2,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; + +using System.Device; /// /// Provides functionality to manage configuration for Universal Printers. diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSetup.Codeunit.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSetup.Codeunit.al index 96415e4e22..7cec109613 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSetup.Codeunit.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterSetup.Codeunit.al @@ -2,11 +2,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using Microsoft.Utilities; using System.Environment; using System.Telemetry; +using System.Device; /// /// Handles the set up of Universal Printers and their configuration settings @@ -32,15 +33,15 @@ codeunit 2750 "Universal Printer Setup" Clear(PaperTrays); Clear(Payload); - PaperTray.Add('papersourcekind', GetPaperTray(UniversalPrinterSettings."Paper Tray").AsInteger()); + PaperTray.Add('papersourcekind', this.GetPaperTray(UniversalPrinterSettings."Paper Tray").AsInteger()); PaperTray.Add('paperkind', UniversalPrinterSettings."Paper Size".AsInteger()); // If paper size is custom and no height and width is specified then set the paper size to A4 - if IsPaperSizeCustom(UniversalPrinterSettings."Paper Size") then begin + if this.IsPaperSizeCustom(UniversalPrinterSettings."Paper Size") then begin if (UniversalPrinterSettings."Paper Height" <= 0) or (UniversalPrinterSettings."Paper Width" <= 0) then begin PaperTray.Replace('paperkind', UniversalPrinterSettings."Paper Size"::A4.AsInteger()); - Session.LogMessage('0000EFY', CustomSizeErrorTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Session.LogMessage('0000EFY', this.CustomSizeErrorTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); end; - ConvertAndAddPrinterPaperDimensions(UniversalPrinterSettings, PaperTray); + this.ConvertAndAddPrinterPaperDimensions(UniversalPrinterSettings, PaperTray); end; PaperTray.Add('landscape', UniversalPrinterSettings.Landscape); @@ -50,7 +51,7 @@ codeunit 2750 "Universal Printer Setup" Payload.Add('papertrays', PaperTrays); Printers.Add(UniversalPrinterSettings.Name, Payload); end else - Session.LogMessage('0000EFH', PrinterIDMissingTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Session.LogMessage('0000EFH', this.PrinterIDMissingTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); until UniversalPrinterSettings.Next() = 0; end; @@ -70,13 +71,13 @@ codeunit 2750 "Universal Printer Setup" internal procedure ValidatePaperHeight(PaperHeight: Decimal) begin if PaperHeight <= 0 then - Error(HeightInputErr); + Error(this.HeightInputErr); end; internal procedure ValidatePaperWidth(PaperWidth: Decimal) begin if PaperWidth <= 0 then - Error(WidthInputErr); + Error(this.WidthInputErr); end; internal procedure ConvertAndAddPrinterPaperDimensions(UniversalPrinterSettings: Record "Universal Printer Settings"; var PaperTray: JsonObject) @@ -104,14 +105,14 @@ codeunit 2750 "Universal Printer Setup" JArrayElement: JsonToken; ErrorMessage: Text; begin - if not UniversalPrintGraphHelper.GetPrintSharesList(PrintSharesArray, ErrorMessage) then - Error(GetPrintSharesErr, ErrorMessage); + if not this.UniversalPrintGraphHelper.GetPrintSharesList(PrintSharesArray, ErrorMessage) then + Error(this.GetPrintSharesErr, ErrorMessage); foreach JArrayElement in PrintSharesArray do if not JArrayElement.IsObject() then - Session.LogMessage('0000EG2', ParseWarningTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()) + Session.LogMessage('0000EG2', this.ParseWarningTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()) else - if InsertPrinterSetting(UniversalPrinterSettings, JArrayElement.AsObject()) then + if this.InsertPrinterSetting(UniversalPrinterSettings, JArrayElement.AsObject()) then TotalAddedPrinters += 1; end; @@ -121,14 +122,14 @@ codeunit 2750 "Universal Printer Setup" JArrayElement: JsonToken; ErrorMessage: Text; begin - if not UniversalPrintGraphHelper.GetPrintSharesList(PrintSharesArray, ErrorMessage) then - Error(GetPrintSharesErr, ErrorMessage); + if not this.UniversalPrintGraphHelper.GetPrintSharesList(PrintSharesArray, ErrorMessage) then + Error(this.GetPrintSharesErr, ErrorMessage); foreach JArrayElement in PrintSharesArray do if JArrayElement.IsObject then - InsertPrintShare(TempUniversalPrintShareBuffer, JArrayElement.AsObject()) + this.InsertPrintShare(TempUniversalPrintShareBuffer, JArrayElement.AsObject()) else - Session.LogMessage('0000EG3', ParseWarningTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + Session.LogMessage('0000EG3', this.ParseWarningTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); exit; end; @@ -137,7 +138,7 @@ codeunit 2750 "Universal Printer Setup" ErrorMessage: Text; PrintShareJsonObject: JsonObject; begin - exit(UniversalPrintGraphHelper.GetPrintShare(PrintShareID, PrintShareJsonObject, ErrorMessage)); + exit(this.UniversalPrintGraphHelper.GetPrintShare(PrintShareID, PrintShareJsonObject, ErrorMessage)); end; local procedure InsertPrinterSetting(var UniversalPrinterSettings: Record "Universal Printer Settings"; PrintShareJsonObject: JsonObject): Boolean @@ -147,7 +148,7 @@ codeunit 2750 "Universal Printer Setup" PrintShareIDValue: Text; PrinterDefaultsJsonObject: JsonObject; begin - if not UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'id', PrintShareIDValue) then + if not this.UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'id', PrintShareIDValue) then exit(false); if PrintShareIDValue = '' then @@ -158,7 +159,7 @@ codeunit 2750 "Universal Printer Setup" if UniversalPrinterSettings.FindFirst() then exit(false); - if not UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'displayName', PrintShareNameValue) then + if not this.UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'displayName', PrintShareNameValue) then exit(false); if PrintShareNameValue = '' then @@ -168,21 +169,21 @@ codeunit 2750 "Universal Printer Setup" UniversalPrinterSettings.Validate("Print Share ID", PrintShareIDValue); UniversalPrinterSettings.Validate(Name, CopyStr(PrintShareNameValue, 1, MaxStrLen(UniversalPrinterSettings.Name))); UniversalPrinterSettings.Validate("Print Share Name", CopyStr(PrintShareNameValue, 1, MaxStrLen(UniversalPrinterSettings."Print Share Name"))); - UniversalPrinterSettings.Validate(Description, StrSubstNo(DefaultDescriptionTxt, UniversalPrinterSettings.Name)); + UniversalPrinterSettings.Validate(Description, StrSubstNo(this.DefaultDescriptionTxt, UniversalPrinterSettings.Name)); if PrintShareJsonObject.Get('allowAllUsers', PropertyBag) then UniversalPrinterSettings.Validate(AllowAllUsers, PropertyBag.AsValue().AsBoolean()); if PrintShareJsonObject.Get('defaults', PropertyBag) and PropertyBag.IsObject() then begin PrinterDefaultsJsonObject := PropertyBag.AsObject(); - UpdateDefaults(UniversalPrinterSettings, PrinterDefaultsJsonObject); + this.UpdateDefaults(UniversalPrinterSettings, PrinterDefaultsJsonObject); end else begin // Bug 393946: Universal Print: Default settings for printers are not loaded // According to documentation, the Graph APIs for the print share list should return capabilities and defaults, but that is not the case // even if the fields are $select-ed explicitly. Getting the defaults for each single printer is slower but works. // Visit the docs: https://go.microsoft.com/fwlink/?linkid=2206184 - Session.LogMessage('0000EUQ', NoDefaultsAvailableTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); - GetDefaults(UniversalPrinterSettings); + Session.LogMessage('0000EUQ', this.NoDefaultsAvailableTelemetryTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.UniversalPrintGraphHelper.GetUniversalPrintTelemetryCategory()); + this.GetDefaults(UniversalPrinterSettings); end; exit(UniversalPrinterSettings.Insert(true)); @@ -198,12 +199,12 @@ codeunit 2750 "Universal Printer Setup" if IsNullGuid(UniversalPrinterSettings."Print Share ID") then exit; - if not UniversalPrintGraphHelper.GetPrintShare(UniversalPrinterSettings."Print Share ID", PrintShareJsonObject, ErrorMessage) then - Error(GetPrintShareDetailsErr, ErrorMessage); + if not this.UniversalPrintGraphHelper.GetPrintShare(UniversalPrinterSettings."Print Share ID", PrintShareJsonObject, ErrorMessage) then + Error(this.GetPrintShareDetailsErr, ErrorMessage); if PrintShareJsonObject.Get('defaults', PropertyBag) and PropertyBag.IsObject() then begin PrinterDefaultsJsonObject := PropertyBag.AsObject(); - UpdateDefaults(UniversalPrinterSettings, PrinterDefaultsJsonObject); + this.UpdateDefaults(UniversalPrinterSettings, PrinterDefaultsJsonObject); end; end; @@ -220,8 +221,8 @@ codeunit 2750 "Universal Printer Setup" if IsNullGuid(PrintShareID) then exit; - if not UniversalPrintGraphHelper.GetPrintShare(PrintShareID, PrintShareJsonObject, ErrorMessage) then - Error(GetPrintShareDetailsErr, ErrorMessage); + if not this.UniversalPrintGraphHelper.GetPrintShare(PrintShareID, PrintShareJsonObject, ErrorMessage) then + Error(this.GetPrintShareDetailsErr, ErrorMessage); if not PrintShareJsonObject.Get('capabilities', PropertyBag) then exit; @@ -251,13 +252,13 @@ codeunit 2750 "Universal Printer Setup" var PrinterPropValue: Text; begin - if UniversalPrintGraphHelper.GetJsonKeyValue(PrinterDefaultsJsonObject, 'orientation', PrinterPropValue) then - UniversalPrinterSettings.Validate(Landscape, IsLandscape(GetOrientation(PrinterPropValue))); + if this.UniversalPrintGraphHelper.GetJsonKeyValue(PrinterDefaultsJsonObject, 'orientation', PrinterPropValue) then + UniversalPrinterSettings.Validate(Landscape, this.IsLandscape(this.GetOrientation(PrinterPropValue))); - if UniversalPrintGraphHelper.GetJsonKeyValue(PrinterDefaultsJsonObject, 'mediaSize', PrinterPropValue) then - UniversalPrinterSettings.Validate("Paper Size", GetPaperSize(PrinterPropValue)); + if this.UniversalPrintGraphHelper.GetJsonKeyValue(PrinterDefaultsJsonObject, 'mediaSize', PrinterPropValue) then + UniversalPrinterSettings.Validate("Paper Size", this.UniversalPrintGraphHelper.GetPaperSizeFromUniversalPrintMediaSize(PrinterPropValue)); - if UniversalPrintGraphHelper.GetJsonKeyValue(PrinterDefaultsJsonObject, 'outputBin', PrinterPropValue) then + if this.UniversalPrintGraphHelper.GetJsonKeyValue(PrinterDefaultsJsonObject, 'outputBin', PrinterPropValue) then UniversalPrinterSettings.Validate("Paper Tray", CopyStr(PrinterPropValue, 1, MaxStrLen(UniversalPrinterSettings."Paper Tray"))); end; @@ -265,10 +266,10 @@ codeunit 2750 "Universal Printer Setup" var PrintSharePropValue: Text; begin - if UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'displayName', PrintSharePropValue) then + if this.UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'displayName', PrintSharePropValue) then TempUniversalPrintShareBuffer.Validate(Name, CopyStr(PrintSharePropValue, 1, MaxStrLen(TempUniversalPrintShareBuffer.Name))); - if UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'id', PrintSharePropValue) then + if this.UniversalPrintGraphHelper.GetJsonKeyValue(PrintShareJsonObject, 'id', PrintSharePropValue) then TempUniversalPrintShareBuffer.Validate(ID, PrintSharePropValue); if TempUniversalPrintShareBuffer.Insert(true) then; @@ -289,20 +290,6 @@ codeunit 2750 "Universal Printer Setup" exit(UniversalPrinterOrientation); end; - local procedure GetPaperSize(textValue: Text): Enum "Printer Paper Kind" - var - PrinterPaperKind: Enum "Printer Paper Kind"; - OrdinalValue: Integer; - Index: Integer; - begin - Index := PrinterPaperKind.Names.IndexOf(textValue); - if Index = 0 then - exit(Enum::"Printer Paper Kind"::A4); - - OrdinalValue := PrinterPaperKind.Ordinals.Get(Index); - PrinterPaperKind := Enum::"Printer Paper Kind".FromInteger(OrdinalValue); - exit(PrinterPaperKind); - end; local procedure GetPaperTray(textValue: Text): Enum "Printer Paper Source Kind" var @@ -335,17 +322,17 @@ codeunit 2750 "Universal Printer Setup" exit(true); if IsNullGuid(UniversalPrinterSettings."Print Share ID") then begin - if Confirm(InvalidPrintShareClosePageQst, true) then + if Confirm(this.InvalidPrintShareClosePageQst, true) then exit(true); Error(''); end; - if IsPaperSizeCustom(UniversalPrinterSettings."Paper Size") then begin - ValidatePaperHeight(UniversalPrinterSettings."Paper Height"); - ValidatePaperWidth(UniversalPrinterSettings."Paper Width"); + if this.IsPaperSizeCustom(UniversalPrinterSettings."Paper Size") then begin + this.ValidatePaperHeight(UniversalPrinterSettings."Paper Height"); + this.ValidatePaperWidth(UniversalPrinterSettings."Paper Width"); end; - FeatureTelemetry.LogUptake('0000GG0', UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::"Set up"); + FeatureTelemetry.LogUptake('0000GG0', this.UniversalPrintGraphHelper.GetUniversalPrintFeatureTelemetryName(), Enum::"Feature Uptake Status"::"Set up"); exit(true); end; @@ -358,7 +345,7 @@ codeunit 2750 "Universal Printer Setup" PrinterSelection.SetRange("Printer Name", Name); if not PrinterSelection.IsEmpty() then - Error(UsedInPrinterSelectionErr, Name); + Error(this.UsedInPrinterSelectionErr, Name); end; internal procedure LookupPaperTrays(var UniversalPrinterSettings: Record "Universal Printer Settings") @@ -366,7 +353,7 @@ codeunit 2750 "Universal Printer Setup" TempNameValueBuffer: Record "Name/Value Buffer" temporary; UniversalPrinterTrayList: Page "Universal Printer Tray List"; begin - GetPaperTrayCapabilities(UniversalPrinterSettings."Print Share ID", TempNameValueBuffer); + this.GetPaperTrayCapabilities(UniversalPrinterSettings."Print Share ID", TempNameValueBuffer); UniversalPrinterTrayList.SetPaperTrayBuffer(TempNameValueBuffer); UniversalPrinterTrayList.LookupMode(true); if UniversalPrinterTrayList.RunModal() = ACTION::LookupOK then begin @@ -385,7 +372,7 @@ codeunit 2750 "Universal Printer Setup" UniversalPrintSharesList.GetRecord(TempUniversalPrintShareBuffer); UniversalPrinterSettings.Validate("Print Share ID", TempUniversalPrintShareBuffer.ID); UniversalPrinterSettings.Validate("Print Share Name", TempUniversalPrintShareBuffer.Name); - GetDefaults(UniversalPrinterSettings); + this.GetDefaults(UniversalPrinterSettings); end; end; diff --git a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterTrayList.Page.al b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterTrayList.Page.al index 1b1d56c961..672503da98 100644 --- a/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterTrayList.Page.al +++ b/Apps/W1/MicrosoftUniversalPrint/app/src/UniversalPrinterTrayList.Page.al @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -namespace System.Device; +namespace System.Device.UniversalPrint; using Microsoft.Utilities; /// diff --git a/Apps/W1/QBMigration/app/src/Support/MSQBODataMigration.Page.al b/Apps/W1/QBMigration/app/src/Support/MSQBODataMigration.Page.al index 9e9fd35c86..045ab910bc 100644 --- a/Apps/W1/QBMigration/app/src/Support/MSQBODataMigration.Page.al +++ b/Apps/W1/QBMigration/app/src/Support/MSQBODataMigration.Page.al @@ -53,6 +53,7 @@ page 1830 "MS - QBO Data Migration" } } } +#if not CLEAN25 group("2") { ObsoleteState = Pending; @@ -67,8 +68,12 @@ page 1830 "MS - QBO Data Migration" Editable = false; MultiLine = true; ShowCaption = false; + ObsoleteState = Pending; + ObsoleteReason = 'Not used anymore'; + ObsoleteTag = '25.0'; } } +#endif group("3") { InstructionalText = 'Enter the accounts to use when you post sales and purchase transactions to the general ledger.'; diff --git a/Apps/W1/ReportLayouts/app/ExtensionLogo.png b/Apps/W1/ReportLayouts/app/ExtensionLogo.png deleted file mode 100644 index b2797835f39c3ada708ae26fdf75a66ec0ca2069..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7091 zcmchcRa6wvx5i;$B!=#i?xCei=|(z+A*69gX-66)hDM}8QYD9yh8YEW1g4|mxA|;W!~}emJb$X|Aobr}P3F2#TmDwh8>fLC3dMvNn=G?K`e1gM|j$nT&^qxOF&3C$B-5o?^Y zdo;KYjKG-BNLo5Yc)!Nq#v{ik3}R!Wb{d<=?AymM_-`olyDuA|M((vTgZe4r!P0;A z3{%9l3}y;bZacmc0&5vyb<~H;L-t6qLFn4g#t@9PE8o@f(4Cz0Ad9rYsqBf8i2w~! zbxlM8aSidk{EDoiAN?l?t6O4bTT|;?^xuwd_;Xjm%J1OvmAp#l3|}`f+j4C*3{P+V~9>3BgE*^m~J89qIxjsy0Nhm6!t6eu9ls0Zgz72aWF*J1T%WcsDgP)nrwib87qQ1;K)Cg)0Br zpA3qfe(Zd$u{K) z324gthp;jRvTh0iU@g?kV7 zr~NfT9uWGNrfMpUep*`${2kIYv|pG$hFss}i2GsJuyp z2eN7DeZs9*z9xfuTDpsaLq=ob0EV%x7Y>O9iVS

~oa;EMU_Bkqf*KYMK|5MkuA` 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 -/// A dialog page for editting report layout information. -///

-page 9661 "Report Layout Edit Dialog" -{ - Caption = 'Edit Report Layout'; - PageType = StandardDialog; - Extensible = false; - Permissions = tabledata "Tenant Report Layout" = r; - - layout - { - area(content) - { - field(ReportID; ReportID) - { - ApplicationArea = Basic, Suite; - Caption = 'Report ID'; - Enabled = false; - ToolTip = 'Specifies the ID of the report.'; - } - field(ReportName; ReportName) - { - ApplicationArea = Basic, Suite; - Caption = 'Report Name'; - Enabled = false; - ToolTip = 'Specifies the name of the report.'; - } - field(LayoutName; NewLayoutName) - { - ApplicationArea = Basic, Suite; - NotBlank = true; - ShowMandatory = true; - Caption = 'Layout Name'; - ToolTip = 'Specifies the name of the layout.'; - - trigger OnValidate() - begin - NewLayoutName := NewLayoutName.Trim(); - if NewLayoutName = '' then - Error(LayoutNameEmptyErr); - - if TenantReportLayout.Get(ReportID, NewLayoutName, emptyGuid) then - if CreateCopy or (OldLayoutName <> NewLayoutName) then - Error(LayoutAlreadyExistsErr, NewLayoutName); - end; - } - field(Description; Description) - { - ApplicationArea = Basic, Suite; - Caption = 'Description'; - ToolTip = 'Specifies a description for the layout.'; - - trigger OnValidate() - begin - Description := Description.Trim(); - end; - } - field(CreateCopy; CreateCopy) - { - ApplicationArea = Basic, Suite; - Caption = 'Save Changes to a Copy'; - ToolTip = 'Create a copy of the selected layout with the specified changes.'; - Editable = CreateCopyEditable; - - trigger OnValidate() - begin - if (CreateCopy) then - AvailableInAllCompaniesEditable := true - else - if (IsLayoutOwnedByCurrentCompany) then begin - AvailableInAllCompaniesEditable := true; - AvailableInAllCompanies := false; - end else begin - AvailableInAllCompaniesEditable := false; - AvailableInAllCompanies := true; - end; - end; - } - field(AvailableInAllCompanies; AvailableInAllCompanies) - { - ApplicationArea = Basic, Suite; - Caption = 'Available in All Companies'; - ToolTip = 'Specifies whether the layout should be available in all companies or just the current company.'; - Editable = AvailableInAllCompaniesEditable; - } - } - } - - var - TenantReportLayout: Record "Tenant Report Layout"; - ReportID: Integer; - OldLayoutName: Text[250]; - NewLayoutName: Text[250]; - ReportName: Text; - Description: Text[250]; - LayoutAlreadyExistsErr: Label 'A layout named %1 already exists.', Comment = '%1 = Layout Name'; - LayoutNameEmptyErr: Label 'The layout name cannot be an empty value.'; - emptyGuid: Guid; - CreateCopy: Boolean; - CreateCopyEditable: Boolean; - AvailableInAllCompanies: Boolean; - AvailableInAllCompaniesEditable: Boolean; - IsLayoutOwnedByCurrentCompany: Boolean; - - internal procedure SelectedLayoutDescription(): Text[250] - begin - exit(Description); - end; - - internal procedure SelectedLayoutName(): Text[250] - begin - exit(NewLayoutName); - end; - - internal procedure SelectedAvailableInAllCompanies(): Boolean - begin - exit(AvailableInAllCompanies); - end; - - internal procedure CopyOperationEnabled(): Boolean - begin - exit(CreateCopy); - end; - - internal procedure SetupDialog(ReportLayoutList: Record "Report Layout List"; CurrentSelectedCompany: Text[30]): Text - begin - ReportID := ReportLayoutList."Report ID"; - ReportName := ReportLayoutList."Report Name"; - Description := ReportLayoutList."Description"; - OldLayoutName := ReportLayoutList."Caption"; - NewLayoutName := OldLayoutName; - - if not ReportLayoutList."User Defined" then begin - CreateCopy := true; - CreateCopyEditable := false; - AvailableInAllCompaniesEditable := true; - AvailableInAllCompanies := true; - - end else begin - CreateCopy := false; - CreateCopyEditable := true; - - TenantReportLayout.Get(ReportID, ReportLayoutList.Name, emptyGuid); - if (TenantReportLayout."Company Name" = CurrentSelectedCompany) then begin - AvailableInAllCompaniesEditable := true; - AvailableInAllCompanies := false; - IsLayoutOwnedByCurrentCompany := true; - end else begin - AvailableInAllCompaniesEditable := false; - AvailableInAllCompanies := true; - end; - end; - end; -} diff --git a/Apps/W1/ReportLayouts/app/src/ReportLayoutNewDialog.page.al b/Apps/W1/ReportLayouts/app/src/ReportLayoutNewDialog.page.al deleted file mode 100644 index e3e0a89902..0000000000 --- a/Apps/W1/ReportLayouts/app/src/ReportLayoutNewDialog.page.al +++ /dev/null @@ -1,156 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace Microsoft.Shared.Report; - -using System.Environment.Configuration; -using System.Reflection; -/// -/// A dialog page for adding new report layouts. -/// -page 9662 "Report Layout New Dialog" -{ - Caption = 'Add New Layout for a Report'; - PageType = StandardDialog; - Extensible = false; - Permissions = tabledata "Tenant Report Layout" = r; - - layout - { - area(content) - { - field(ReportID; ReportID) - { - ApplicationArea = Basic, Suite; - Caption = 'Report ID'; - Enabled = true; - TableRelation = "Report Metadata"."ID"; - ToolTip = 'Specifies the ID of the report.'; - - trigger OnValidate() - begin - if not ReportMetadata.Get(ReportID) then - Error(ReportNotFoundErr, ReportID); - end; - } - field(ReportName; ReportMetadata."Caption") - { - ApplicationArea = Basic, Suite; - Caption = 'Report Name'; - Enabled = false; - ToolTip = 'Specifies the name of the report.'; - } - field(LayoutName; LayoutName) - { - ApplicationArea = Basic, Suite; - NotBlank = true; - ShowMandatory = true; - Caption = 'Layout Name'; - ToolTip = 'Specifies the name of the layout.'; - - trigger OnValidate() - begin - "LayoutName" := "LayoutName".Trim(); - if "LayoutName" = '' then - Error(LayoutNameEmptyErr); - if TenantReportLayout.Get(ReportID, LayoutName, emptyGuid) then - Error(LayoutAlreadyExistsErr, LayoutName); - end; - } - field(Description; Description) - { - ApplicationArea = Basic, Suite; - Caption = 'Description'; - ToolTip = 'Specifies a description for the layout.'; - - trigger OnValidate() - begin - Description := Description.Trim(); - end; - } - field("Format Options"; FormatOptions) - { - ApplicationArea = Basic, Suite; - Visible = true; - Caption = 'Format Options'; - ToolTip = 'Specifies the format of the layout.'; - OptionCaption = 'RDLC,Word,Excel,External'; - } - field(AvailableInAllCompanies; AvailableInAllCompanies) - { - ApplicationArea = Basic, Suite; - Caption = 'Available in All Companies'; - ToolTip = 'Specifies whether the layout should be available in all companies or just the current company.'; - } - } - } - - trigger OnOpenPage() - begin - FormatOptions := FormatOptions::Excel; - LayoutName := ''; - AvailableInAllCompanies := true; - if ReportID <> 0 then - if ReportMetadata.Get(ReportID) then; - end; - - var - ReportMetadata: Record "Report Metadata"; - TenantReportLayout: Record "Tenant Report Layout"; - ReportID: Integer; - LayoutName: Text[250]; - Description: Text[250]; - LayoutAlreadyExistsErr: Label 'A layout named "%1" already exists.', Comment = '%1 = LayoutName'; - LayoutNameEmptyErr: Label 'The layout name cannot be an empty value.'; - ReportNotFoundErr: Label 'A report with ID "%1" does not exist.', Comment = '%1 = ReportID'; - FormatOptions: Option "RDLC","Word","Excel","Custom"; // For Custom type, 'External' will be shown in UI - AvailableInAllCompanies: Boolean; - emptyGuid: Guid; - - internal procedure SetReportID(NewReportID: Integer) - begin - ReportID := NewReportID; - end; - - internal procedure SelectedReportID(): Integer - begin - exit(ReportID); - end; - - internal procedure SelectedLayoutName(): Text[250] - begin - exit(LayoutName); - end; - - internal procedure SelectedLayoutDescription(): Text[250] - begin - exit(Description); - end; - - internal procedure SelectedAddCustomLayout(): Boolean - begin - exit(FormatOptions = FormatOptions::Custom); - end; - - internal procedure SelectedAddExcelLayout(): Boolean - begin - exit(FormatOptions = FormatOptions::Excel); - end; - - internal procedure SelectedAddRDLCLayout(): Boolean - begin - exit(FormatOptions = FormatOptions::RDLC); - end; - - internal procedure SelectedAddWordLayout(): Boolean - begin - exit(FormatOptions = FormatOptions::Word); - end; - - internal procedure SelectedLayoutIsGlobal(): Boolean - begin - exit(AvailableInAllCompanies); - end; -} diff --git a/Apps/W1/ReportLayouts/app/src/ReportLayouts.page.al b/Apps/W1/ReportLayouts/app/src/ReportLayouts.page.al deleted file mode 100644 index 8345707820..0000000000 --- a/Apps/W1/ReportLayouts/app/src/ReportLayouts.page.al +++ /dev/null @@ -1,387 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace Microsoft.Shared.Report; - -using System.Reflection; -using System.Environment.Configuration; -using System.Integration; -/// -/// The report layouts page, used for adding/deleting/editing user and extension defined report layouts. -/// -page 9660 "Report Layouts" -{ - ApplicationArea = Basic, Suite; - Caption = 'Report Layouts'; - InsertAllowed = false; - ModifyAllowed = false; - DeleteAllowed = true; - PromotedActionCategories = 'New,Process,Report,Approve'; - AdditionalSearchTerms = 'Custom Report Layouts, Report Layout Selection'; - PageType = List; - SourceTable = "Report Layout List"; - SourceTableView = sorting("Report ID", "Layout Format"); - UsageCategory = Administration; - Extensible = true; - Permissions = tabledata "Tenant Report Layout" = rd; - - layout - { - area(content) - { - repeater(Group) - { - field("Report ID"; Rec."Report ID") - { - ApplicationArea = Basic, Suite; - Editable = false; - ToolTip = 'Specifies the object ID of the report.'; - } - field("Report Name"; Rec."Report Name") - { - ApplicationArea = Basic, Suite; - Editable = false; - Caption = 'Report Name'; - ToolTip = 'Specifies the name of the report.'; - } - field("Layout Name"; Rec."Caption") - { - ApplicationArea = Basic, Suite; - Editable = false; - Caption = 'Layout Name'; - ToolTip = 'Specifies the unique name of the layout.'; - } - field(Description; Rec."Description") - { - ApplicationArea = Basic, Suite; - Editable = false; - Caption = 'Description'; - ToolTip = 'Specifies a description of the report layout.'; - } - - field(IsDefaultLayout; IsDefaultLayout) - { - ApplicationArea = Basic, Suite; - Editable = not IsDefaultLayout; - Caption = 'Default'; - ToolTip = 'Specifies whether this is the default layout selected for the report.'; - trigger OnValidate() - begin - ReportLayoutsImpl.SetDefaultReportLayoutSelection(Rec, false); - IsDefaultLayout := true; - - // Update the default layout list. - DefaultReportLayoutList.Init(); - DefaultReportLayoutList.SetRange("Name", Rec."Name"); - DefaultReportLayoutList.SetRange("Application ID", Rec."Application ID"); - DefaultReportLayoutList.SetRange("Report ID", Rec."Report ID"); - DefaultReportLayoutList.FindFirst(); - CurrPage.Update(false); - end; - } - - field("Layout Publisher"; Rec."Layout Publisher") - { - ApplicationArea = Basic, Suite; - Editable = false; - Caption = 'Extension'; - ToolTip = 'Specifies the name and publisher of the extension that the layout belongs to. If this field is empty, it means that layout is user-defined and does not belong to an extension.'; - } - field("Layout Format"; Rec."Layout Format") - { - ApplicationArea = Basic, Suite; - Editable = false; - Caption = 'Type'; - OptionCaption = 'RDLC,Word,Excel,External'; - ToolTip = 'Specifies the format of the report layout.'; - } - field("User Defined"; Rec."User Defined") - { - ApplicationArea = Basic, Suite; - Editable = false; - Visible = false; - ToolTip = 'Specifies whether the layout was created by a user.'; - } - } - } - area(factboxes) - { - systempart(Control11; Notes) - { - ApplicationArea = Notes; - Visible = false; - } - systempart(Control12; Links) - { - ApplicationArea = RecordLinks; - Visible = false; - } - } - } - - actions - { - area(Processing) - { - action(NewLayout) - { - ApplicationArea = Basic, Suite; - Caption = 'New'; - Image = NewDocument; - Promoted = true; - PromotedOnly = true; - PromotedIsBig = true; - Scope = Repeater; - PromotedCategory = Process; - ToolTip = 'Create a new layout.'; - - trigger OnAction() - var - ReturnReportID: Integer; - ReturnLayoutName: Text; - begin - ReportLayoutsImpl.CreateNewReportLayout(Rec, ReturnReportID, ReturnLayoutName); - SetFocusedRecord(ReturnReportID, ReturnLayoutName); - end; - } - - action(EditLayout) - { - ApplicationArea = Basic, Suite; - Caption = 'Edit Info'; - Image = Edit; - Promoted = true; - PromotedOnly = true; - PromotedIsBig = true; - Scope = Repeater; - PromotedCategory = Process; - Enabled = LayoutIsSelected; - ToolTip = 'Edit layout information.'; - - trigger OnAction() - var - NewEditedLayoutName: Text; - begin - if not Rec."User Defined" then begin - if Dialog.Confirm(EditInfoExtensionLayoutTxt, false) then - ReportLayoutsImpl.EditReportLayout(Rec, NewEditedLayoutName); - end else - ReportLayoutsImpl.EditReportLayout(Rec, NewEditedLayoutName); - SetFocusedRecord(Rec."Report ID", NewEditedLayoutName); - end; - } - - action(RunReport) - { - ApplicationArea = Basic, Suite; - Caption = 'Run Report'; - Image = "Report"; - PromotedOnly = true; - Promoted = true; - PromotedIsBig = true; - PromotedCategory = Process; - Enabled = LayoutIsSelected; - ToolTip = 'Run the report using the selected layout.'; - - trigger OnAction() - begin - ReportLayoutsImpl.RunCustomReport(Rec); - end; - } - - action(DefaulLayoutSelection) - { - ApplicationArea = Basic, Suite; - Caption = 'Set Default'; - Image = ListPage; - PromotedOnly = true; - Promoted = true; - PromotedIsBig = true; - PromotedCategory = Process; - Enabled = LayoutIsSelected; - ToolTip = 'Set the current layout as the default layout for the specified report.'; - trigger OnAction() - begin - ReportLayoutsImpl.SetDefaultReportLayoutSelection(Rec, true); - CurrPage.Update(false); - end; - } - - action(ExportLayout) - { - ApplicationArea = Basic, Suite; - Caption = 'Export Layout'; - Image = Export; - PromotedOnly = true; - Promoted = true; - PromotedIsBig = true; - PromotedCategory = Process; - Enabled = LayoutIsSelected; - ToolTip = 'Export the selected layout file.'; - - trigger OnAction() - begin - ReportLayoutsImpl.ExportReportLayout(Rec); - end; - } - - action(ReplaceLayout) - { - ApplicationArea = Basic, Suite; - Caption = 'Replace Layout'; - Image = Import; - PromotedOnly = true; - Promoted = true; - PromotedIsBig = true; - PromotedCategory = Process; - Enabled = LayoutIsSelected; - ToolTip = 'Replace the existing layout file.'; - - trigger OnAction() - var - ReturnReportID: Integer; - ReturnLayoutName: Text; - begin - if not Rec."User Defined" then - Error(ModifyNonUserLayoutErr); - - if Dialog.Confirm(StrSubstNo(ReplaceConfirmationTxt, Rec."Name"), false) then - ReportLayoutsImpl.ReplaceLayout(Rec."Report ID", Rec."Name", Rec."Description", Rec."Layout Format", ReturnReportID, ReturnLayoutName); - end; - } - - action(OpenInOneDrive) - { - ApplicationArea = Basic, Suite; - Caption = 'Open in OneDrive'; - ToolTip = 'Copy the file to your Business Central folder in OneDrive and open it in a new window so you can manage or share the file.', Comment = 'OneDrive should not be translated'; - Image = Cloud; - Visible = ShareOptionsVisible; - Enabled = ShareOptionsEnabled; - Scope = Repeater; - trigger OnAction() - begin - ReportLayoutsImpl.OpenInOneDrive(Rec); - end; - } - action(EditInOneDrive) - { - ApplicationArea = Basic, Suite; - Caption = 'Edit in OneDrive'; - ToolTip = 'Copy the file to your Business Central folder in OneDrive and open it in a new window so you can edit the file.', Comment = 'OneDrive should not be translated'; - Image = Cloud; - Visible = ShareOptionsVisible; - Enabled = ShareOptionsEnabled; - Scope = Repeater; - - trigger OnAction() - begin - ReportLayoutsImpl.EditInOneDrive(Rec); - end; - } - action(ShareWithOneDrive) - { - ApplicationArea = Basic, Suite; - Caption = 'Share'; - ToolTip = 'Copy the file to your Business Central folder in OneDrive and share the file. You can also see who it''s already shared with.', Comment = 'OneDrive should not be translated'; - Image = Share; - Visible = ShareOptionsVisible; - Enabled = ShareOptionsEnabled; - Scope = Repeater; - trigger OnAction() - begin - ReportLayoutsImpl.ShareWithOneDrive(Rec); - end; - } - } - } - - views - { - view(UserDefined) - { - Caption = 'User-Defined'; - Filters = where("User Defined" = Const(true)); - } - view(Extensions) - { - Caption = 'Extensions'; - Filters = where("User Defined" = Const(false)); - } - } - - trigger OnOpenPage() - begin - ReportLayoutsImpl.SetSelectedCompany(CompanyName()); - end; - - trigger OnDeleteRecord(): Boolean - var - TenantReportLayout: Record "Tenant Report Layout"; - begin - TenantReportLayout.Init(); - if Rec."User Defined" then begin - TenantReportLayout.Get(Rec."Report ID", Rec."Name", EmptyGuid); - // If the selected layout is the default layout for the current report. - if TenantReportLayoutSelection.Get(Rec."Report ID", CompanyName(), EmptyGuid) then - if TenantReportLayoutSelection."Layout Name" = Rec.Name then - // selected layout is the default layout. In this case we confirm the deletion. - if not ReportLayoutsImpl.ConfirmDeleteDefaultLayoutSelection(Rec, TenantReportLayoutSelection) then - exit(false); - TenantReportLayout.Delete(true); - end else - Error(ModifyNonUserLayoutErr); - - CurrPage.Update(false); - exit(false); - end; - - trigger OnAfterGetCurrRecord() - var - SelectedReportLayoutList: Record "Report Layout List"; - DocumentSharing: Codeunit "Document Sharing"; - begin - LayoutIsSelected := not ((Rec."Report ID" = 0) and (Rec.Name = '')); - - CurrPage.SetSelectionFilter(SelectedReportLayoutList); - IsMultiSelect := SelectedReportLayoutList.Count() > 1; - ShareOptionsVisible := DocumentSharing.ShareEnabled(Enum::"Document Sharing Source"::System); - ShareOptionsEnabled := LayoutIsSelected and (not IsMultiSelect) and Rec."User Defined" and (Rec."Layout Format" <> Rec."Layout Format"::RDLC); - end; - - trigger OnAfterGetRecord() - begin - if DefaultReportLayoutList."Report ID" <> Rec."Report ID" then - ReportLayoutsImpl.GetDefaultReportLayoutSelection(Rec."Report ID", DefaultReportLayoutList); - - IsDefaultLayout := (DefaultReportLayoutList."Report ID" = Rec."Report ID") and (DefaultReportLayoutList.Name = Rec.Name) and (DefaultReportLayoutList."Application ID" = Rec."Application ID"); - end; - - var - DefaultReportLayoutList: Record "Report Layout List"; - TenantReportLayoutSelection: Record "Tenant Report Layout Selection"; - ReportLayoutsImpl: Codeunit "Report Layouts Impl."; - EmptyGuid: Guid; - IsDefaultLayout: Boolean; - IsMultiSelect: Boolean; - LayoutIsSelected: Boolean; - ShareOptionsVisible: Boolean; - ShareOptionsEnabled: Boolean; - ModifyNonUserLayoutErr: Label 'Only user-defined layouts can be modified or removed.'; - EditInfoExtensionLayoutTxt: Label 'It is not possible to modify the layout info for this layout because it is provided by an extension. Do you want to edit a copy of the layout instead ?'; - ReplaceConfirmationTxt: Label 'This action will replace the layout file of the currently selected layout "%1". Do you want to continue ?', Comment = '%1 = LayoutName'; - - local procedure SetFocusedRecord(ReportID: Integer; LayoutName: Text) - var - CurrReportLayoutList: Record "Report Layout List"; - begin - if (ReportID = 0) or (LayoutName = '') then - exit; - if CurrReportLayoutList.get(ReportID, LayoutName, EmptyGuid) then begin - CurrPage.SetRecord(CurrReportLayoutList); - CurrPage.Update(false); - end; - end; -} \ No newline at end of file diff --git a/Apps/W1/ReportLayouts/app/src/ReportLayoutsImpl.codeunit.al b/Apps/W1/ReportLayouts/app/src/ReportLayoutsImpl.codeunit.al deleted file mode 100644 index 2fb56f83cd..0000000000 --- a/Apps/W1/ReportLayouts/app/src/ReportLayoutsImpl.codeunit.al +++ /dev/null @@ -1,526 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ -namespace Microsoft.Shared.Report; - -using Microsoft.EServices.EDocument; -using Microsoft.Foundation.Reporting; -using System.Environment.Configuration; -using System.Reflection; -using System.IO; -using System.Utilities; -/// -/// This code unit supports the 'Report Layouts' page and provides implementations for adding/deleting/editing user and extension defined report layouts. -/// - -codeunit 9660 "Report Layouts Impl." -{ - Access = Internal; - Permissions = tabledata "Tenant Report Layout" = rimd, - tabledata "Tenant Report Layout Selection" = rimd; - - var - TenantReportLayoutSelection: Record "Tenant Report Layout Selection"; - SelectedCompany: Text[30]; - EmptyGuid: Guid; - ImportWordTxt: Label 'Choose Word layout file'; - ImportRdlcTxt: Label 'Choose RDLC layout file'; - ImportExcelTxt: Label 'Choose Excel layout file'; - ImportExternalTxt: Label 'Choose External layout file'; - DefaultLayoutDeleteTxt: Label 'You are about to delete the currently selected default layout "%1", for report "%2". Do you want to continue? A new default layout must be selected manually from the Report Layout Selection page.', Comment = '%1 = Layout Name, %2 = Report Name'; - DefaultLayoutSetTxt: Label '"%1" has been set as the default layout for Report "%2"', Comment = '%1 = Layout Name, %2 = Report Name'; - FileFilterWordTxt: Label 'Word Files (*.docx)|*.docx', Comment = '{Split=r''\|''}{Locked=s''1''}'; - FileFilterRdlcTxt: Label 'SQL Report Builder (*.rdl;*.rdlc)|*.rdl;*.rdlc', Comment = '{Split=r''\|''}{Locked=s''1''}'; - FileFilterExcelTxt: Label 'Excel Files (*.xlsx)|*.xlsx', Comment = '{Split=r''\|''}{Locked=s''1''}'; - FileFilterExternalTxt: Label 'All Files (*.*)|*.*', Comment = '{Split=r''\|''}{Locked=s''1''}'; - EmptyLayoutNameTxt: Label 'A layout name must be specified.'; - LayoutAlreadyExistsErr: Label 'A layout named "%1" already exists.', Comment = '%1 = Layout Name'; - - internal procedure SetSelectedCompany(NewCompanyName: Text) - begin - SelectedCompany := CopyStr(NewCompanyName, 1, MaxStrLen(SelectedCompany)); - end; - - internal procedure RunCustomReport(SelectedReportLayoutList: Record "Report Layout List") - var - DesignTimeReportSelection: codeunit "Design-time Report Selection"; - begin - if SelectedReportLayoutList."Report ID" = 0 then - exit; - - DesignTimeReportSelection.SetSelectedLayout(SelectedReportLayoutList.Name, SelectedReportLayoutList."Application ID"); - Commit(); // Since we run the report modally, we cannot have any active transactions. - if TryRunCustomReport(SelectedReportLayoutList) then - DesignTimeReportSelection.ClearLayoutSelection() - else begin - DesignTimeReportSelection.ClearLayoutSelection(); - Error(GetLastErrorText()); - end; - end; - - [TryFunction] - local procedure TryRunCustomReport(SelectedReportLayoutList: Record "Report Layout List") - begin - Report.RunModal(SelectedReportLayoutList."Report ID"); - end; - - local procedure AddLayoutSelection(SelectedReportLayoutList: Record "Report Layout List"; UserId: Guid): Boolean - begin - TenantReportLayoutSelection.Init(); - TenantReportLayoutSelection."App ID" := SelectedReportLayoutList."Application ID"; - TenantReportLayoutSelection."Company Name" := SelectedCompany; - TenantReportLayoutSelection."Layout Name" := SelectedReportLayoutList."Name"; - TenantReportLayoutSelection."Report ID" := SelectedReportLayoutList."Report ID"; - TenantReportLayoutSelection."User ID" := UserId; - - if not TenantReportLayoutSelection.Insert(true) then - TenantReportLayoutSelection.Modify(true); - end; - - internal procedure CreateNewReportLayout(SelectedReportLayoutList: Record "Report Layout List"; var ReturnReportID: Integer; var ReturnLayoutName: Text) - var - ReportLayoutNewDialog: Page "Report Layout New Dialog"; - begin - ReportLayoutNewDialog.SetReportID(SelectedReportLayoutList."Report ID"); - if ReportLayoutNewDialog.RunModal() = Action::OK then - case true of - ReportLayoutNewDialog.SelectedAddCustomLayout(): - InsertNewLayout( - ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(), - ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::Custom, - ReportLayoutNewDialog.SelectedLayoutIsGlobal(), ReturnReportID, ReturnLayoutName); - - ReportLayoutNewDialog.SelectedAddWordLayout(): - InsertNewLayout( - ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(), - ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::Word, - ReportLayoutNewDialog.SelectedLayoutIsGlobal(), ReturnReportID, ReturnLayoutName); - - ReportLayoutNewDialog.SelectedAddRDLCLayout(): - InsertNewLayout( - ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(), - ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::RDLC, - ReportLayoutNewDialog.SelectedLayoutIsGlobal(), ReturnReportID, ReturnLayoutName); - - ReportLayoutNewDialog.SelectedAddExcelLayout(): - InsertNewLayout( - ReportLayoutNewDialog.SelectedReportID(), ReportLayoutNewDialog.SelectedLayoutName(), - ReportLayoutNewDialog.SelectedLayoutDescription(), SelectedReportLayoutList."Layout Format"::Excel, - ReportLayoutNewDialog.SelectedLayoutIsGlobal(), ReturnReportID, ReturnLayoutName); - end; - end; - - internal procedure SetDefaultReportLayoutSelection(SelectedReportLayoutList: Record "Report Layout List"; ShowMessage: Boolean) - var - ReportLayoutSelection: Record "Report Layout Selection"; - begin - // Add to TenantReportLayoutSelection table with an Empty Guid. - AddLayoutSelection(SelectedReportLayoutList, EmptyGuid); - - // Add to the report layout selection table - if ReportLayoutSelection.get(SelectedReportLayoutList."Report ID", SelectedCompany) then begin - ReportLayoutSelection.Type := GetReportLayoutSelectionCorrespondingEnum(SelectedReportLayoutList); - ReportLayoutSelection.Modify(true); - end else begin - ReportLayoutSelection."Report ID" := SelectedReportLayoutList."Report ID"; - ReportLayoutSelection."Company Name" := SelectedCompany; - ReportLayoutSelection."Custom Report Layout Code" := ''; - ReportLayoutSelection.Type := GetReportLayoutSelectionCorrespondingEnum(SelectedReportLayoutList); - ReportLayoutSelection.Insert(true); - end; - - if ShowMessage then - Message(DefaultLayoutSetTxt, SelectedReportLayoutList."Caption", SelectedReportLayoutList."Report Name"); - end; - - internal procedure GetDefaultReportLayoutSelection(ReportId: Integer; var DefaultReportLayoutList: Record "Report Layout List"): Boolean - var - ReportMetadata: Record "Report Metadata"; - begin - TenantReportLayoutSelection.Init(); - DefaultReportLayoutList.Init(); - - if TenantReportLayoutSelection.Get(ReportId, SelectedCompany, EmptyGuid) then begin - // Filter Default Report Layout List by the layout name and application id and report id - DefaultReportLayoutList.SetRange("Name", TenantReportLayoutSelection."Layout Name"); - DefaultReportLayoutList.SetRange("Application ID", TenantReportLayoutSelection."App ID"); - DefaultReportLayoutList.SetRange("Report ID", ReportId); - - // Retrive the record based on filters - if DefaultReportLayoutList.FindFirst() then - exit(true); - end else - if ReportMetadata.Get(ReportId) then begin - DefaultReportLayoutList.SetRange("Name", ReportMetadata."DefaultLayoutName"); - DefaultReportLayoutList.SetFilter("Application ID", '<>%1', EmptyGuid); - DefaultReportLayoutList.SetRange("Report ID", ReportId); - - if DefaultReportLayoutList.FindFirst() then - exit(true); - end; - - exit(false); - end; - - internal procedure UpdateDefaultLayoutSelectionName(SelectedReportLayoutList: Record "Report Layout List"; NewLayoutName: Text[250]): Boolean - begin - if TenantReportLayoutSelection.Get(SelectedReportLayoutList."Report ID", SelectedCompany, EmptyGuid) then - if TenantReportLayoutSelection."Layout Name" = SelectedReportLayoutList."Name" then begin - TenantReportLayoutSelection."Layout Name" := NewLayoutName; - TenantReportLayoutSelection.Modify(true); - end; - end; - - internal procedure ConfirmDeleteDefaultLayoutSelection(SelectedReportLayoutList: Record "Report Layout List"; TenantReportLayoutSelection: Record "Tenant Report Layout Selection"): Boolean - var - ReportLayoutSelection: Record "Report Layout Selection"; - begin - if Dialog.Confirm(StrSubstNo(DefaultLayoutDeleteTxt, SelectedReportLayoutList.Caption, SelectedReportLayoutList."Report Name"), false) then begin - - // Clear the selection from the Tenant Report Layout Selection table. - if (TenantReportLayoutSelection."Layout Name" = SelectedReportLayoutList."Name") then - TenantReportLayoutSelection.Delete(true); - - // Clear the selection from Report Layout Selection table and let platform set the new default layout for this report. - if ReportLayoutSelection.get(SelectedReportLayoutList."Report ID", SelectedCompany) then - ReportLayoutSelection.Delete(true); - exit(true); - end; - end; - - local procedure GetReportLayoutSelectionCorrespondingEnum(SelectedReportLayoutList: Record "Report Layout List"): Integer - begin - case SelectedReportLayoutList."Layout Format" of - - SelectedReportLayoutList."Layout Format"::RDLC: - exit(0); - SelectedReportLayoutList."Layout Format"::Word: - exit(1); - SelectedReportLayoutList."Layout Format"::Excel: - exit(3); - SelectedReportLayoutList."Layout Format"::Custom: - exit(4); - end - end; - - internal procedure InsertNewLayout(ReportID: Integer; LayoutName: Text[250]; LayoutDescription: Text[250]; LayoutFormat: Option; LayoutIsGlobal: Boolean; var ReturnReportID: Integer; var ReturnLayoutName: Text) - var - TenantReportLayout: Record "Tenant Report Layout"; - FileManagement: Codeunit "File Management"; - FileFilterTxt: Text; - DialogCaption: Text; - NVInStream: InStream; - UploadResult: Boolean; - UploadFileName: Text; - ErrorMessage: Text; - begin - if ReportID = 0 then - exit; - - if LayoutName = '' then begin - Message(EmptyLayoutNameTxt); - exit; - end; - - TenantReportLayout.Init(); - TenantReportLayout."Report ID" := ReportID; - TenantReportLayout."Name" := LayoutName; - - if LayoutIsGlobal then - TenantReportLayout."Company Name" := '' - else - TenantReportLayout."Company Name" := SelectedCompany; - - TenantReportLayout."Layout Format" := LayoutFormat; - TenantReportLayout."Description" := LayoutDescription; - - case TenantReportLayout."Layout Format" of - TenantReportLayout."Layout Format"::Word: - begin - DialogCaption := ImportWordTxt; - FileFilterTxt := FileFilterWordTxt; - end; - TenantReportLayout."Layout Format"::RDLC: - begin - DialogCaption := ImportRdlcTxt; - FileFilterTxt := FileFilterRdlcTxt; - end; - TenantReportLayout."Layout Format"::Excel: - begin - DialogCaption := ImportExcelTxt; - FileFilterTxt := FileFilterExcelTxt; - end; - TenantReportLayout."Layout Format"::Custom: - begin - DialogCaption := ImportExternalTxt; - FileFilterTxt := FileFilterExternalTxt; - end; - end; - - UploadFileName := TenantReportLayout."Name"; - OnBeforeUpload(UploadResult, UploadFileName, NVInStream); - - if (not UploadResult) then begin - ClearLastError(); - UploadResult := UploadIntoStream(DialogCaption, '', FileFilterTxt, UploadFileName, NVInStream); - end; - - if not UploadResult then begin - ErrorMessage := GetLastErrorText(); - //When upload is cancelled by user, don't emit an error. - if ErrorMessage <> '' then - Error(ErrorMessage); - exit; - end; - - // Custom layouts files are treated as unknown streams and don't need validation. - if TenantReportLayout."Layout Format" <> TenantReportLayout."Layout Format"::Custom then - FileManagement.ValidateFileExtension(UploadFileName, FileFilterTxt); - - // If the current layout is being replaced using the ReplaceLayout action - if TenantReportLayout.Get(TenantReportLayout."Report ID", TenantReportLayout."Name", TenantReportLayout."App ID") then - TenantReportLayout.Delete(true); - - TenantReportLayout."Layout".ImportStream(NVInStream, TenantReportLayout."Description"); - TenantReportLayout."MIME Type" := CreateLayoutMime(UploadFileName); - TenantReportLayout.Insert(true); - - ReturnReportID := TenantReportLayout."Report ID"; - ReturnLayoutName := TenantReportLayout."Name"; - end; - - internal procedure ReplaceLayout(ReportID: Integer; LayoutName: Text[250]; LayoutDescription: Text[250]; LayoutFormat: Option; var ReturnReportID: Integer; var ReturnLayoutName: Text) - var - TenantReportLayout: Record "Tenant Report Layout"; - begin - TenantReportLayout."Report ID" := ReportID; - TenantReportLayout."Name" := LayoutName; - - if TenantReportLayout.Get(ReportID, LayoutName, TenantReportLayout."App ID") then - InsertNewLayout(ReportID, LayoutName, LayoutDescription, LayoutFormat, TenantReportLayout."Company Name" = '', ReturnReportID, ReturnLayoutName); - end; - - local procedure CreateLayoutMime(FileNameWithExtension: Text) MimeType: Text[255] - var - FileManagement: Codeunit "File Management"; - FileExtension: Text; - begin - FileExtension := FileManagement.GetExtension(FileNameWithExtension); - MimeType := 'reportlayout/' + FileExtension; - end; - - internal procedure EditReportLayout(SelectedReportLayoutList: Record "Report Layout List"; var NewEditedLayoutName: Text) - var - TenantReportLayout: Record "Tenant Report Layout"; - TempBlob: Codeunit "Temp Blob"; - ReportLayoutEditDialog: Page "Report Layout Edit Dialog"; - NewDescription: Text[250]; - NewLayoutName: Text[250]; - CompanyName: Text[30]; - CreateCopy: Boolean; - NewLayoutInStream: InStream; - SourceLayoutOutStream: OutStream; - AllCompaniesTxt: Label ''; - AvailableInAllCompanies: Boolean; - begin - if SelectedReportLayoutList."User Defined" then begin - if TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList.Name, EmptyGuid) then - CompanyName := TenantReportLayout."Company Name"; - end else - CompanyName := SelectedCompany; - - ReportLayoutEditDialog.SetupDialog(SelectedReportLayoutList, SelectedCompany); - if ReportLayoutEditDialog.RunModal() = Action::OK then begin - - NewDescription := ReportLayoutEditDialog.SelectedLayoutDescription(); - NewLayoutName := ReportLayoutEditDialog.SelectedLayoutName(); - CreateCopy := ReportLayoutEditDialog.CopyOperationEnabled(); - AvailableInAllCompanies := ReportLayoutEditDialog.SelectedAvailableInAllCompanies(); - - // Check if a layout having NewLayoutName already exists - if TenantReportLayout.Get(SelectedReportLayoutList."Report ID", NewLayoutName, EmptyGuid) then - if CreateCopy or (SelectedReportLayoutList.Name <> NewLayoutName) then - Error(LayoutAlreadyExistsErr, NewLayoutName); - - // Check if the layout should be made available for all companies - if AvailableInAllCompanies then - CompanyName := AllCompaniesTxt; - - if CreateCopy then begin - // If create-copy is used to create a layout bound to the current company - if SelectedReportLayoutList."User Defined" and (not AvailableInAllCompanies) and (CompanyName = '') then - CompanyName := SelectedCompany; - - TenantReportLayout.Init(); - TenantReportLayout.Name := NewLayoutName; - TenantReportLayout.Description := NewDescription; - TenantReportLayout."Report ID" := SelectedReportLayoutList."Report ID"; - TenantReportLayout."Company Name" := CompanyName; - - // Copy media stream - TempBlob.CreateOutStream(SourceLayoutOutStream); - SelectedReportLayoutList."Layout".ExportStream(SourceLayoutOutStream); - TempBlob.CreateInStream(NewLayoutInStream); - TenantReportLayout."Layout".ImportStream(NewLayoutInStream, NewDescription); - - TenantReportLayout."Layout Format" := SelectedReportLayoutList."Layout Format"; - TenantReportLayout."MIME Type" := SelectedReportLayoutList."MIME Type"; - TenantReportLayout.Insert(true); - end else begin - TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid); - TenantReportLayout."Company Name" := CompanyName; - TenantReportLayout.Rename(SelectedReportLayoutList."Report ID", NewLayoutName, EmptyGuid); - TenantReportLayout.Description := NewDescription; - - TenantReportLayout.Modify(true); - end; - NewEditedLayoutName := NewLayoutName; - - // If the layout name was updated, we check if this layout is the default layout - // and update its reference in the tenant report layout selection table. - if not CreateCopy then - if (SelectedReportLayoutList.Name <> NewLayoutName) then - UpdateDefaultLayoutSelectionName(SelectedReportLayoutList, NewLayoutName); - end; - end; - - internal procedure ExportReportLayout(SelectedReportLayoutList: Record "Report Layout List"): Text - var - TempBlob: Codeunit "Temp Blob"; - FileManagement: Codeunit "File Management"; - FileName: Text; - MediaOutStream: OutStream; - begin - TempBlob.CreateOutStream(MediaOutStream); - SelectedReportLayoutList."Layout".ExportStream(MediaOutStream); - FileName := GetFileName(SelectedReportLayoutList); - exit(FileManagement.BLOBExport(TempBlob, FileName, true)); - end; - - internal procedure OpenInOneDrive(SelectedReportLayoutList: Record "Report Layout List") - var - TenantReportLayout: Record "Tenant Report Layout"; - DocumentServiceMgt: Codeunit "Document Service Management"; - TempBlob: Codeunit "Temp Blob"; - FileName: Text; - FileExtension: Text; - MediaInStream: InStream; - MediaOutStream: OutStream; - begin - if not TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid) then - exit; - - FileName := GetFileName(SelectedReportLayoutList); - FileExtension := GetFileExtension(SelectedReportLayoutList); - - TempBlob.CreateOutStream(MediaOutStream); - TenantReportLayout."Layout".ExportStream(MediaOutStream); - - MediaInStream := TempBlob.CreateInStream(); - DocumentServiceMgt.OpenInOneDrive(FileName, FileExtension, MediaInStream); - end; - - internal procedure ShareWithOneDrive(SelectedReportLayoutList: Record "Report Layout List") - var - TenantReportLayout: Record "Tenant Report Layout"; - DocumentServiceMgt: Codeunit "Document Service Management"; - TempBlob: Codeunit "Temp Blob"; - FileName: Text; - FileExtension: Text; - MediaInStream: InStream; - MediaOutStream: OutStream; - begin - if not TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid) then - exit; - - FileName := GetFileName(SelectedReportLayoutList); - FileExtension := GetFileExtension(SelectedReportLayoutList); - - TempBlob.CreateOutStream(MediaOutStream); - TenantReportLayout."Layout".ExportStream(MediaOutStream); - - MediaInStream := TempBlob.CreateInStream(); - DocumentServiceMgt.ShareWithOneDrive(FileName, FileExtension, MediaInStream); - end; - - internal procedure EditInOneDrive(SelectedReportLayoutList: Record "Report Layout List") - var - TenantReportLayout: Record "Tenant Report Layout"; - DocumentServiceMgt: Codeunit "Document Service Management"; - TempBlob: Codeunit "Temp Blob"; - FileName: Text; - FileExtension: Text; - MediaInStream: InStream; - MediaOutStream: OutStream; - begin - if not TenantReportLayout.Get(SelectedReportLayoutList."Report ID", SelectedReportLayoutList."Name", EmptyGuid) then - exit; - - FileName := GetFileName(SelectedReportLayoutList); - FileExtension := GetFileExtension(SelectedReportLayoutList); - - TempBlob.CreateOutStream(MediaOutStream); - TenantReportLayout."Layout".ExportStream(MediaOutStream); - - if DocumentServiceMgt.EditInOneDrive(FileName, FileExtension, TempBlob) then begin - - MediaInStream := TempBlob.CreateInStream(); - TenantReportLayout."Layout".ImportStream(MediaInStream, TenantReportLayout."Description"); - - if not TenantReportLayout.Insert(true) then - TenantReportLayout.Modify(true); - end; - end; - - local procedure GetFileName(SelectedReportLayoutList: Record "Report Layout List"): Text - var - CurrentExt: Text; - CurrentLayoutName: Text; - begin - CurrentLayoutName := SelectedReportLayoutList."Name"; - CurrentExt := GetFileExtension(SelectedReportLayoutList); - if StrPos(CurrentLayoutName, '.' + CurrentExt) = 0 then - exit(CurrentLayoutName + '.' + CurrentExt); - exit(CurrentLayoutName); - end; - - local procedure GetFileExtension(SelectedReportLayoutList: Record "Report Layout List") FileExt: Text - begin - // If MIME Type is present, use that to create file-extension. - if (SelectedReportLayoutList."MIME Type" <> '') and SelectedReportLayoutList."MIME Type".ToLower().Contains('reportlayout/') then begin - FileExt := SelectedReportLayoutList."MIME Type".Split('/').Get(2); - exit; - end; - - case SelectedReportLayoutList."Layout Format" of - SelectedReportLayoutList."Layout Format"::Word: - FileExt := 'docx'; - SelectedReportLayoutList."Layout Format"::RDLC: - FileExt := 'rdl'; - SelectedReportLayoutList."Layout Format"::Excel: - FileExt := 'xlsx'; - SelectedReportLayoutList."Layout Format"::Custom: - FileExt := ''; - end; - end; - - [EventSubscriber(ObjectType::Page, Page::"Report Layout Selection", 'OnSelectReportLayout', '', false, false)] - local procedure SelectReportLayout(var ReportLayoutList: Record "Report Layout List"; var Handled: Boolean) - begin - if Page.RunModal(Page::"Report Layouts", ReportLayoutList) = ACTION::LookupOK then - Handled := true; - end; - - [EventSubscriber(ObjectType::Codeunit, Codeunit::ReportManagement, 'OnSelectReportLayout', '', false, false)] - local procedure SelectReportLayoutUI(var ReportLayoutList: Record "Report Layout List"; var Handled: Boolean) - begin - if Page.RunModal(Page::"Report Layouts", ReportLayoutList) = ACTION::LookupOK then - Handled := true; - end; - - [InternalEvent(false, false)] - local procedure OnBeforeUpload(var AlreadyUploaded: Boolean; var UploadFileName: Text; var FileInStream: InStream) - begin - end; -} \ No newline at end of file diff --git a/Apps/W1/ReportLayouts/test/ExtensionLogo.png b/Apps/W1/ReportLayouts/test/ExtensionLogo.png deleted file mode 100644 index 0ebae7709090300d483403d3e7c39d247030a4e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5095 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 - - 0 - - - - SQL - - - None - - - - - - 2in -