From c8b01d767fe663a03385b2e4880d54cf83e26a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tine=20Stari=C4=8D?= <42935028+tinestaric@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:55:46 +0200 Subject: [PATCH] [Shopify] Log Skipped Records on export processes (#27539) This pull request does not have a related issue as it's part of delivery for development agreed directly with @AndreiPanko Fixes #26819 ### Created new structure to log records skipped on export Solution has new structure possible to open by typing "Shopify Skipped Records". On page user can see all the skipped records with reasons and date and time when the record was skipped. User can also open the related record using "Show record" action or clicking the "Description" field value. ### Log cases: Customer: - Customer has empty email - Customer with same email or phone exists Posted Sales Invoice: - Customer does not exist in Shopify - Payment terms do not exist in Shopify - Customer No. is Default Customer No. for Shopify Shop - Customer No. is used in Shopify Customer Template - No lines existing in sales invoice - Invalid Quantity - Empty No. value Product: - Item is blocked/sales blocked (Item Variant) - Item is blocked Shipments: - Related Shopify Order does not exist - No lines in Posted Sales Shipment applicable for fulfillment - No corresponding fulfillment found in Shopify. If the condition met log record is being created. ### Retention policy Retention policy is inserted on extension install. It can be turned on "Retention Policies" page. Fixes [AB#473306](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/473306) --------- Co-authored-by: Piotr Michalak Co-authored-by: Gediminas Gaubys --- .../Base/Codeunits/ShpfyInstaller.Codeunit.al | 3 + .../Codeunits/ShpfyCompanyExport.Codeunit.al | 11 +- .../Codeunits/ShpfyCustomerExport.Codeunit.al | 11 +- .../ShpfyPostedInvoiceExport.Codeunit.al | 37 +- .../Codeunits/ShpfySkipRecordMgt.Codeunit.al | 44 + .../Codeunits/ShpfySkippedRecord.Codeunit.al | 44 + .../Logs/Pages/ShpfySkippedRecords.Page.al | 95 ++ .../Logs/Tables/ShpfySkippedRecord.Table.al | 137 +++ .../PermissionSets/ShpfyEdit.PermissionSet.al | 1 + .../PermissionSets/ShpfyRead.PermissionSet.al | 1 + .../Codeunits/ShpfyProductExport.Codeunit.al | 35 +- .../ShpfyExportShipments.Codeunit.al | 11 +- .../Reports/ShpfySyncShipmToShopify.Report.al | 7 +- .../Logs/ShpfySkippedRecordLogSub.Codeunit.al | 104 +++ .../ShpfySkippedRecordLogTest.Codeunit.al | 828 ++++++++++++++++++ .../Shipping/ShpfyShippingHelper.Codeunit.al | 86 ++ .../Shipping/ShpfyShippingTest.Codeunit.al | 85 +- 17 files changed, 1436 insertions(+), 104 deletions(-) create mode 100644 Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkipRecordMgt.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkippedRecord.Codeunit.al create mode 100644 Apps/W1/Shopify/app/src/Logs/Pages/ShpfySkippedRecords.Page.al create mode 100644 Apps/W1/Shopify/app/src/Logs/Tables/ShpfySkippedRecord.Table.al create mode 100644 Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogSub.Codeunit.al create mode 100644 Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogTest.Codeunit.al create mode 100644 Apps/W1/Shopify/test/Shipping/ShpfyShippingHelper.Codeunit.al diff --git a/Apps/W1/Shopify/app/src/Base/Codeunits/ShpfyInstaller.Codeunit.al b/Apps/W1/Shopify/app/src/Base/Codeunits/ShpfyInstaller.Codeunit.al index 4072add050..891527bebb 100644 --- a/Apps/W1/Shopify/app/src/Base/Codeunits/ShpfyInstaller.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Base/Codeunits/ShpfyInstaller.Codeunit.al @@ -27,6 +27,7 @@ codeunit 30273 "Shpfy Installer" var LogEntry: Record "Shpfy Log Entry"; DataCapture: Record "Shpfy Data Capture"; + SkippedRecord: Record "Shpfy Skipped Record"; RetentionPolicySetup: Codeunit "Retention Policy Setup"; RetenPolAllowedTables: Codeunit "Reten. Pol. Allowed Tables"; UpgradeTag: Codeunit "Upgrade Tag"; @@ -38,12 +39,14 @@ codeunit 30273 "Shpfy Installer" RetenPolAllowedTables.AddAllowedTable(Database::"Shpfy Log Entry", LogEntry.FieldNo(SystemCreatedAt)); RetenPolAllowedTables.AddAllowedTable(Database::"Shpfy Data Capture", DataCapture.FieldNo(SystemModifiedAt)); + RetenPolAllowedTables.AddAllowedTable(Database::"Shpfy Skipped Record", SkippedRecord.FieldNo(SystemCreatedAt)); if not IsInitialSetup then exit; CreateRetentionPolicySetup(Database::"Shpfy Log Entry", RetentionPolicySetup.FindOrCreateRetentionPeriod("Retention Period Enum"::"1 Month")); CreateRetentionPolicySetup(Database::"Shpfy Data Capture", RetentionPolicySetup.FindOrCreateRetentionPeriod("Retention Period Enum"::"1 Month")); + CreateRetentionPolicySetup(Database::"Shpfy Skipped Record", RetentionPolicySetup.FindOrCreateRetentionPeriod("Retention Period Enum"::"1 Month")); UpgradeTag.SetUpgradeTag(GetShopifyLogEntryAddedToAllowedListUpgradeTag()); end; diff --git a/Apps/W1/Shopify/app/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al b/Apps/W1/Shopify/app/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al index cf65199341..1146b1366e 100644 --- a/Apps/W1/Shopify/app/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al @@ -36,6 +36,7 @@ codeunit 30284 "Shpfy Company Export" Shop: Record "Shpfy Shop"; CompanyAPI: Codeunit "Shpfy Company API"; CatalogAPI: Codeunit "Shpfy Catalog API"; + SkippedRecord: Codeunit "Shpfy Skipped Record"; CreateCustomers: Boolean; CountyCodeTooLongLbl: Label 'Can not export customer %1 %2. The length of the string is %3, but it must be less than or equal to %4 characters. Value: %5, field: %6', Comment = '%1 - Customer No., %2 - Customer Name, %3 - Length, %4 - Max Length, %5 - Value, %6 - Field Name'; @@ -44,9 +45,12 @@ codeunit 30284 "Shpfy Company Export" ShopifyCompany: Record "Shpfy Company"; ShopifyCustomer: Record "Shpfy Customer"; CompanyLocation: Record "Shpfy Company Location"; + EmptyEmailAddressLbl: Label 'Customer (Company) has no e-mail address.'; begin - if Customer."E-Mail" = '' then + if Customer."E-Mail" = '' then begin + SkippedRecord.LogSkippedRecord(Customer.RecordId, EmptyEmailAddressLbl, Shop); exit; + end; if CreateCompanyMainContact(Customer, ShopifyCustomer) then if FillInShopifyCompany(Customer, ShopifyCompany, CompanyLocation) then @@ -180,10 +184,13 @@ codeunit 30284 "Shpfy Company Export" var ShopifyCompany: Record "Shpfy Company"; CompanyLocation: Record "Shpfy Company Location"; + CompanyWithPhoneNoOrEmailExistsLbl: Label 'Company already exists with the same e-mail or phone.'; begin ShopifyCompany.Get(CompanyId); - if ShopifyCompany."Customer SystemId" <> Customer.SystemId then + if ShopifyCompany."Customer SystemId" <> Customer.SystemId then begin + SkippedRecord.LogSkippedRecord(ShopifyCompany.Id, Customer.RecordId, CompanyWithPhoneNoOrEmailExistsLbl, Shop); exit; + end; CompanyLocation.SetRange("Company SystemId", ShopifyCompany.SystemId); CompanyLocation.FindFirst(); diff --git a/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al b/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al index d11fea569e..803beab9cd 100644 --- a/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al @@ -39,6 +39,7 @@ codeunit 30116 "Shpfy Customer Export" var Shop: Record "Shpfy Shop"; CustomerApi: Codeunit "Shpfy Customer API"; + SkippedRecord: Codeunit "Shpfy Skipped Record"; CreateCustomers: Boolean; CountyCodeTooLongLbl: Label 'Can not export customer %1 %2. The length of the string is %3, but it must be less than or equal to %4 characters. Value: %5, field: %6', Comment = '%1 - Customer No., %2 - Customer Name, %3 - Length, %4 - Max Length, %5 - Value, %6 - Field Name'; @@ -87,9 +88,12 @@ codeunit 30116 "Shpfy Customer Export" var ShopifyCustomer: Record "Shpfy Customer"; CustomerAddress: Record "Shpfy Customer Address"; + EmptyEmailAddressLbl: Label 'Customer has no e-mail address.'; begin - if Customer."E-Mail" = '' then + if Customer."E-Mail" = '' then begin + SkippedRecord.LogSkippedRecord(Customer.RecordId, EmptyEmailAddressLbl, Shop); exit; + end; Clear(ShopifyCustomer); Clear(CustomerAddress); @@ -296,10 +300,13 @@ codeunit 30116 "Shpfy Customer Export" var ShopifyCustomer: Record "Shpfy Customer"; CustomerAddress: Record "Shpfy Customer Address"; + CustomerWithPhoneNoOrEmailExistsLbl: Label 'Customer already exists with the same e-mail or phone.'; begin ShopifyCustomer.Get(CustomerID); - if ShopifyCustomer."Customer SystemId" <> Customer.SystemId then + if ShopifyCustomer."Customer SystemId" <> Customer.SystemId then begin + SkippedRecord.LogSkippedRecord(ShopifyCustomer.Id, Customer.RecordId, CustomerWithPhoneNoOrEmailExistsLbl, Shop); exit; // An other customer with the same e-mail or phone is the source of it. + end; CustomerAddress.SetRange("Customer Id", CustomerId); CustomerAddress.SetRange(Default, true); diff --git a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al index 60e9773d1c..ec298d736b 100644 --- a/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Invoicing/Codeunits/ShpfyPostedInvoiceExport.Codeunit.al @@ -17,6 +17,7 @@ codeunit 30362 "Shpfy Posted Invoice Export" DraftOrdersAPI: Codeunit "Shpfy Draft Orders API"; FulfillmentAPI: Codeunit "Shpfy Fulfillment API"; JsonHelper: Codeunit "Shpfy Json Helper"; + SkippedRecord: Codeunit "Shpfy Skipped Record"; trigger OnRun() begin @@ -92,22 +93,34 @@ codeunit 30362 "Shpfy Posted Invoice Export" var ShopifyCompany: Record "Shpfy Company"; ShopifyCustomer: Record "Shpfy Customer"; + CustomerNotExistInShopifyLbl: Label 'Customer does not exists as Shopify company or customer.'; + PaymentTermsNotExistLbl: Label 'Payment terms %1 do not exist in Shopify.', Comment = '%1 = Payment Terms Code.'; + CustomerNoIsDefaultCustomerNoLbl: Label 'Bill-to customer no. is the default customer no. for Shopify shop.'; + CustomerTemplateExistsLbl: Label 'Shopify customer template exists for customer no. %1 shop %2.', Comment = '%1 = Customer No., %2 = Shop Code'; begin ShopifyCompany.SetRange("Customer No.", SalesInvoiceHeader."Bill-to Customer No."); if ShopifyCompany.IsEmpty() then begin ShopifyCustomer.SetRange("Customer No.", SalesInvoiceHeader."Bill-to Customer No."); - if ShopifyCustomer.IsEmpty() then + if ShopifyCustomer.IsEmpty() then begin + SkippedRecord.LogSkippedRecord(SalesInvoiceHeader.RecordId, CustomerNotExistInShopifyLbl, Shop); exit(false); + end; end; - if not ShopifyPaymentTermsExists(SalesInvoiceHeader."Payment Terms Code") then + if not ShopifyPaymentTermsExists(SalesInvoiceHeader."Payment Terms Code") then begin + SkippedRecord.LogSkippedRecord(SalesInvoiceHeader.RecordId, StrSubstNo(PaymentTermsNotExistLbl, SalesInvoiceHeader."Payment Terms Code"), Shop); exit(false); + end; - if Shop."Default Customer No." = SalesInvoiceHeader."Bill-to Customer No." then + if Shop."Default Customer No." = SalesInvoiceHeader."Bill-to Customer No." then begin + SkippedRecord.LogSkippedRecord(SalesInvoiceHeader.RecordId, CustomerNoIsDefaultCustomerNoLbl, Shop); exit(false); + end; - if CheckCustomerTemplates(SalesInvoiceHeader."Bill-to Customer No.") then + if CheckCustomerTemplates(SalesInvoiceHeader."Bill-to Customer No.") then begin + SkippedRecord.LogSkippedRecord(SalesInvoiceHeader.RecordId, StrSubstNo(CustomerTemplateExistsLbl, SalesInvoiceHeader."Bill-to Customer No.", Shop.Code), Shop); exit(false); + end; if not CheckSalesInvoiceHeaderLines(SalesInvoiceHeader) then exit(false); @@ -145,21 +158,31 @@ codeunit 30362 "Shpfy Posted Invoice Export" local procedure CheckSalesInvoiceHeaderLines(SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean var SalesInvoiceLine: Record "Sales Invoice Line"; + NoLinesInSalesInvoiceLbl: Label 'No relevant sales invoice lines exist.'; + InvalidQuantityLbl: Label 'Invalid quantity in sales invoice line.'; + EmptyNoInLineLbl: Label 'No. field is empty in Sales Invoice Line.'; begin + SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No."); SalesInvoiceLine.SetFilter(Type, '<>%1', SalesInvoiceLine.Type::" "); - if SalesInvoiceLine.IsEmpty() then + if SalesInvoiceLine.IsEmpty() then begin + SkippedRecord.LogSkippedRecord(SalesInvoiceHeader.RecordId, NoLinesInSalesInvoiceLbl, Shop); exit(false); + end; SalesInvoiceLine.Reset(); SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No."); if SalesInvoiceLine.FindSet() then repeat - if (SalesInvoiceLine.Quantity <> 0) and (SalesInvoiceLine.Quantity <> Round(SalesInvoiceLine.Quantity, 1)) then + if (SalesInvoiceLine.Quantity <> 0) and (SalesInvoiceLine.Quantity <> Round(SalesInvoiceLine.Quantity, 1)) then begin + SkippedRecord.LogSkippedRecord(SalesInvoiceLine.RecordId, InvalidQuantityLbl, Shop); exit(false); + end; - if (SalesInvoiceLine.Type <> SalesInvoiceLine.Type::" ") and (SalesInvoiceLine."No." = '') then + if (SalesInvoiceLine.Type <> SalesInvoiceLine.Type::" ") and (SalesInvoiceLine."No." = '') then begin + SkippedRecord.LogSkippedRecord(SalesInvoiceLine.RecordId, EmptyNoInLineLbl, Shop); exit(false); + end; until SalesInvoiceLine.Next() = 0; exit(true); diff --git a/Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkipRecordMgt.Codeunit.al b/Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkipRecordMgt.Codeunit.al new file mode 100644 index 0000000000..9c0f4a5eaf --- /dev/null +++ b/Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkipRecordMgt.Codeunit.al @@ -0,0 +1,44 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy Skip Record (ID 30313). +/// +codeunit 30313 "Shpfy Skipped Record" +{ + Access = Internal; + Permissions = tabledata "Shpfy Skipped Record" = rimd; + + /// + /// Creates log entry for skipped record. + /// + /// Related Shopify Id of the record. + /// Table Id of the record. + /// Record Id of the record. + /// Reason for skipping the record. + /// Shop record. + internal procedure LogSkippedRecord(ShopifyId: BigInteger; RecordId: RecordID; SkippedReason: Text[250]; Shop: Record "Shpfy Shop") + var + SkippedRecord: Record "Shpfy Skipped Record"; + begin + if Shop."Logging Mode" = Enum::"Shpfy Logging Mode"::Disabled then + exit; + SkippedRecord.Init(); + SkippedRecord.Validate("Shopify Id", ShopifyId); + SkippedRecord.Validate("Table ID", RecordId.TableNo()); + SkippedRecord.Validate("Record ID", RecordId); + SkippedRecord.Validate("Skipped Reason", SkippedReason); + SkippedRecord.Insert(true); + end; + + /// + /// Creates log entry for skipped recordwith empty Shopify Id. + /// + /// Record Id of the record. + /// Reason for skipping the record. + /// Shop record. + internal procedure LogSkippedRecord(RecordId: RecordID; SkippedReason: Text[250]; Shop: Record "Shpfy Shop") + begin + LogSkippedRecord(0, RecordId, SkippedReason, Shop); + end; + +} diff --git a/Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkippedRecord.Codeunit.al b/Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkippedRecord.Codeunit.al new file mode 100644 index 0000000000..9c0f4a5eaf --- /dev/null +++ b/Apps/W1/Shopify/app/src/Logs/Codeunits/ShpfySkippedRecord.Codeunit.al @@ -0,0 +1,44 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Codeunit Shpfy Skip Record (ID 30313). +/// +codeunit 30313 "Shpfy Skipped Record" +{ + Access = Internal; + Permissions = tabledata "Shpfy Skipped Record" = rimd; + + /// + /// Creates log entry for skipped record. + /// + /// Related Shopify Id of the record. + /// Table Id of the record. + /// Record Id of the record. + /// Reason for skipping the record. + /// Shop record. + internal procedure LogSkippedRecord(ShopifyId: BigInteger; RecordId: RecordID; SkippedReason: Text[250]; Shop: Record "Shpfy Shop") + var + SkippedRecord: Record "Shpfy Skipped Record"; + begin + if Shop."Logging Mode" = Enum::"Shpfy Logging Mode"::Disabled then + exit; + SkippedRecord.Init(); + SkippedRecord.Validate("Shopify Id", ShopifyId); + SkippedRecord.Validate("Table ID", RecordId.TableNo()); + SkippedRecord.Validate("Record ID", RecordId); + SkippedRecord.Validate("Skipped Reason", SkippedReason); + SkippedRecord.Insert(true); + end; + + /// + /// Creates log entry for skipped recordwith empty Shopify Id. + /// + /// Record Id of the record. + /// Reason for skipping the record. + /// Shop record. + internal procedure LogSkippedRecord(RecordId: RecordID; SkippedReason: Text[250]; Shop: Record "Shpfy Shop") + begin + LogSkippedRecord(0, RecordId, SkippedReason, Shop); + end; + +} diff --git a/Apps/W1/Shopify/app/src/Logs/Pages/ShpfySkippedRecords.Page.al b/Apps/W1/Shopify/app/src/Logs/Pages/ShpfySkippedRecords.Page.al new file mode 100644 index 0000000000..ceeda2df49 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Logs/Pages/ShpfySkippedRecords.Page.al @@ -0,0 +1,95 @@ +namespace Microsoft.Integration.Shopify; + +/// +/// Page Shpfy Skipped Records (ID 30166). +/// +page 30166 "Shpfy Skipped Records" +{ + ApplicationArea = All; + Caption = 'Shopify Skipped Records'; + PageType = List; + SourceTable = "Shpfy Skipped Record"; + UsageCategory = Lists; + Editable = false; + InsertAllowed = false; + SourceTableView = sorting("Entry No.") order(descending); + + layout + { + area(Content) + { + repeater(General) + { + field("Shopify Id"; Rec."Shopify Id") { } + field("Table ID"; Rec."Table ID") { } + field("Table Name"; Rec."Table Name") { } + field(Description; Rec.Description) + { + trigger OnDrillDown() + begin + Rec.ShowPage(); + end; + } + field("Skipped Reason"; Rec."Skipped Reason") { } + } + } + } + + actions + { + area(Promoted) + { + group(Category_Process) + { + actionref(Show_Promoted; Show) { } + } + + group(Category_Category4) + { + Caption = 'Log Entries'; + + actionref(Delete7days_Promoted; Delete7days) { } + actionref(Delete0days_Promoted; Delete0days) { } + } + } + area(Processing) + { + action(Show) + { + ApplicationArea = All; + Caption = 'Show record'; + Image = View; + ToolTip = 'Show the details of the selected record.'; + + trigger OnAction() + begin + Rec.ShowPage(); + end; + } + action(Delete7days) + { + ApplicationArea = All; + Caption = 'Delete Entries Older Than 7 Days'; + Image = ClearLog; + ToolTip = 'Clear the list of skipped records that are older than 7 days.'; + + trigger OnAction(); + begin + Rec.DeleteEntries(7); + end; + } + action(Delete0days) + { + ApplicationArea = All; + Caption = 'Delete All Entries'; + Image = Delete; + ToolTip = 'Clear the list of all skipped records.'; + + trigger OnAction(); + begin + Rec.DeleteEntries(0); + end; + } + } + } +} diff --git a/Apps/W1/Shopify/app/src/Logs/Tables/ShpfySkippedRecord.Table.al b/Apps/W1/Shopify/app/src/Logs/Tables/ShpfySkippedRecord.Table.al new file mode 100644 index 0000000000..90734c6c31 --- /dev/null +++ b/Apps/W1/Shopify/app/src/Logs/Tables/ShpfySkippedRecord.Table.al @@ -0,0 +1,137 @@ +namespace Microsoft.Integration.Shopify; + +using System.Reflection; +using Microsoft.Utilities; + +/// +/// Table Shpfy Skipped Record (ID 30159). +/// +table 30159 "Shpfy Skipped Record" +{ + Caption = 'Shpfy Skipped Record'; + DataClassification = CustomerContent; + Access = Internal; + + fields + { + field(1; "Entry No."; Integer) + { + AutoIncrement = true; + Caption = 'Entry No.'; + ToolTip = 'Specifies the number of the entry, as assigned from the specific number series when the entry was created.'; + + } + field(2; "Shopify Id"; BigInteger) + { + Caption = 'Shopify Id'; + ToolTip = 'Specifies the Shopify Id of the skipped record.'; + } + field(3; "Table Id"; Integer) + { + Caption = 'Table Id'; + ToolTip = 'Specifies the Table Id of the skipped record.'; + DataClassification = SystemMetadata; + + trigger OnValidate() + begin + "Table Name" := GetTableCaption(); + end; + } + field(4; "Table Name"; Text[250]) + { + Caption = 'Table Name'; + ToolTip = 'Specifies the table name of the skipped record.'; + DataClassification = SystemMetadata; + } + field(5; "Record ID"; RecordID) + { + Caption = 'Record Id'; + ToolTip = 'Specifies the record Id of the skipped record.'; + + trigger OnValidate() + begin + Description := GetRecDescription(); + end; + } + field(6; Description; Text[250]) + { + Caption = 'Description'; + ToolTip = 'Specifies the description of the skipped record.'; + } + field(7; "Skipped Reason"; Text[250]) + { + Caption = 'Skipped Reason'; + ToolTip = 'Specifies the reason why the record was skipped.'; + } + } + keys + { + key(Key1; "Entry No.") + { + Clustered = true; + } + } + + var + DeleteLogEntriesLbl: Label 'Are you sure that you want to delete Shopify log entries?'; + + local procedure GetTableCaption(): Text[250] + var + AllObjWithCaption: Record AllObjWithCaption; + begin + if "Table ID" <> 0 then + if AllObjWithCaption.Get(AllObjWithCaption."Object Type"::Table, "Table ID") then + exit(AllObjWithCaption."Object Caption"); + end; + + local procedure GetRecDescription() Result: Text + var + RecRef: RecordRef; + PKFilter: Text; + Delimiter: Text; + Pos: Integer; + begin + if RecRef.Get("Record ID") then begin + RecRef.SetRecFilter(); + PKFilter := RecRef.GetView(); + repeat + Pos := StrPos(PKFilter, '=FILTER('); + if Pos <> 0 then begin + PKFilter := CopyStr(PKFilter, Pos + 8); + Result += Delimiter + CopyStr(PKFilter, 1, StrPos(PKFilter, ')') - 1); + Delimiter := ','; + end; + until Pos = 0; + end; + end; + + /// + /// Show related record from Record ID field. + /// + internal procedure ShowPage() + var + PageManagement: Codeunit "Page Management"; + begin + if "Record ID".TableNo() <> 0 then + PageManagement.PageRun("Record ID"); + end; + + /// + /// Delete Entries. + /// + /// Parameter of type Integer. + internal procedure DeleteEntries(DaysOld: Integer); + begin + if not Confirm(DeleteLogEntriesLbl) then + exit; + + if DaysOld > 0 then begin + Rec.SetFilter(SystemCreatedAt, '<=%1', CreateDateTime(Today - DaysOld, Time)); + if not Rec.IsEmpty() then + Rec.DeleteAll(false); + Rec.SetRange(SystemCreatedAt); + end else + if not Rec.IsEmpty() then + Rec.DeleteAll(false); + end; +} \ No newline at end of file diff --git a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al index d8296da1f9..b5b9defb15 100644 --- a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al +++ b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyEdit.PermissionSet.al @@ -63,6 +63,7 @@ permissionset 30102 "Shpfy - Edit" tabledata "Shpfy Shop Collection Map" = IMD, tabledata "Shpfy Shop Inventory" = IMD, tabledata "Shpfy Shop Location" = IMD, + tabledata "Shpfy Skipped Record" = IMD, tabledata "Shpfy Synchronization Info" = IMD, tabledata "Shpfy Tag" = IMD, tabledata "Shpfy Tax Area" = IMD, diff --git a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al index 2fab53a2b8..a41a5b602e 100644 --- a/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al +++ b/Apps/W1/Shopify/app/src/PermissionSets/ShpfyRead.PermissionSet.al @@ -63,6 +63,7 @@ permissionset 30100 "Shpfy - Read" tabledata "Shpfy Shop Collection Map" = R, tabledata "Shpfy Shop Inventory" = R, tabledata "Shpfy Shop Location" = R, + tabledata "Shpfy Skipped Record" = R, tabledata "Shpfy Synchronization Info" = R, tabledata "Shpfy Tag" = R, tabledata "Shpfy Tax Area" = R, diff --git a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al index ec7c7fbbd5..8214a617c9 100644 --- a/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Products/Codeunits/ShpfyProductExport.Codeunit.al @@ -59,11 +59,13 @@ codeunit 30178 "Shpfy Product Export" ProductEvents: Codeunit "Shpfy Product Events"; ProductPriceCalc: Codeunit "Shpfy Product Price Calc."; VariantApi: Codeunit "Shpfy Variant API"; + SkippedRecord: Codeunit "Shpfy Skipped Record"; OnlyUpdatePrice: Boolean; RecordCount: Integer; NullGuid: Guid; BulkOperationInput: TextBuilder; GraphQueryList: List of [TextBuilder]; + VariantPriceCalcSkippedLbl: Label 'Variant price is not synchronized because the item is blocked or sales blocked.'; /// /// Creates html body for a product from extended text, marketing text and attributes. @@ -313,11 +315,13 @@ codeunit 30178 "Shpfy Product Export" /// Parameter of type Record "Shopify Variant". /// Parameter of type Record Item. /// Parameter of type Record "Item Unit of Measure". - local procedure FillInProductVariantData(var ShopifyVariant: Record "Shpfy Variant"; Item: Record Item; ItemUnitofMeasure: Record "Item Unit of Measure") + internal procedure FillInProductVariantData(var ShopifyVariant: Record "Shpfy Variant"; Item: Record Item; ItemUnitofMeasure: Record "Item Unit of Measure") begin if Shop."Sync Prices" or OnlyUpdatePrice then if (not Item.Blocked) and (not Item."Sales Blocked") then - ProductPriceCalc.CalcPrice(Item, '', ItemUnitofMeasure.Code, ShopifyVariant."Unit Cost", ShopifyVariant.Price, ShopifyVariant."Compare at Price"); + ProductPriceCalc.CalcPrice(Item, '', ItemUnitofMeasure.Code, ShopifyVariant."Unit Cost", ShopifyVariant.Price, ShopifyVariant."Compare at Price") + else + SkippedRecord.LogSkippedRecord(ShopifyVariant.Id, Item.RecordId, VariantPriceCalcSkippedLbl, Shop); if not OnlyUpdatePrice then begin ShopifyVariant."Available For Sales" := (not Item.Blocked) and (not Item."Sales Blocked"); ShopifyVariant.Barcode := CopyStr(GetBarcode(Item."No.", '', ItemUnitofMeasure.Code), 1, MaxStrLen(ShopifyVariant.Barcode)); @@ -348,11 +352,13 @@ codeunit 30178 "Shpfy Product Export" /// Parameter of type Record "Shopify Variant". /// Parameter of type Record Item. /// Parameter of type Record "Item Variant". - local procedure FillInProductVariantData(var ShopifyVariant: Record "Shpfy Variant"; Item: Record Item; ItemVariant: Record "Item Variant") + internal procedure FillInProductVariantData(var ShopifyVariant: Record "Shpfy Variant"; Item: Record Item; ItemVariant: Record "Item Variant") begin if Shop."Sync Prices" or OnlyUpdatePrice then if (not Item.Blocked) and (not Item."Sales Blocked") then - ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, Item."Sales Unit of Measure", ShopifyVariant."Unit Cost", ShopifyVariant.Price, ShopifyVariant."Compare at Price"); + ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, Item."Sales Unit of Measure", ShopifyVariant."Unit Cost", ShopifyVariant.Price, ShopifyVariant."Compare at Price") + else + SkippedRecord.LogSkippedRecord(ShopifyVariant.Id, Item.RecordId, VariantPriceCalcSkippedLbl, Shop); if not OnlyUpdatePrice then begin ShopifyVariant."Available For Sales" := (not Item.Blocked) and (not Item."Sales Blocked"); ShopifyVariant.Barcode := CopyStr(GetBarcode(Item."No.", ItemVariant.Code, Item."Sales Unit of Measure"), 1, MaxStrLen(ShopifyVariant.Barcode)); @@ -393,11 +399,13 @@ codeunit 30178 "Shpfy Product Export" /// Parameter of type Record Item. /// Parameter of type Record "Item Variant". /// Parameter of type Record "Item Unit of Measure". - local procedure FillInProductVariantData(var ShopifyVariant: Record "Shpfy Variant"; Item: Record Item; ItemVariant: Record "Item Variant"; ItemUnitofMeasure: Record "Item Unit of Measure") + internal procedure FillInProductVariantData(var ShopifyVariant: Record "Shpfy Variant"; Item: Record Item; ItemVariant: Record "Item Variant"; ItemUnitofMeasure: Record "Item Unit of Measure") begin if Shop."Sync Prices" or OnlyUpdatePrice then if (not Item.Blocked) and (not Item."Sales Blocked") then - ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, ItemUnitofMeasure.Code, ShopifyVariant."Unit Cost", ShopifyVariant.Price, ShopifyVariant."Compare at Price"); + ProductPriceCalc.CalcPrice(Item, ItemVariant.Code, ItemUnitofMeasure.Code, ShopifyVariant."Unit Cost", ShopifyVariant.Price, ShopifyVariant."Compare at Price") + else + SkippedRecord.LogSkippedRecord(ShopifyVariant.Id, Item.RecordId, VariantPriceCalcSkippedLbl, Shop); if not OnlyUpdatePrice then begin ShopifyVariant."Available For Sales" := (not Item.Blocked) and (not Item."Sales Blocked"); ShopifyVariant.Barcode := CopyStr(GetBarcode(Item."No.", ItemVariant.Code, ItemUnitofMeasure.Code), 1, MaxStrLen(ShopifyVariant.Barcode)); @@ -544,18 +552,27 @@ codeunit 30178 "Shpfy Product Export" RecordRef1: RecordRef; RecordRef2: RecordRef; VariantAction: Option " ",Create,Update; + ItemIsBlockedLbl: Label 'Item is blocked.'; + ItemIsDraftLbl: Label 'Shopify product is in draft status.'; + ItemIsArchivedLbl: Label 'Shopify product is archived.'; begin if ShopifyProduct.Get(ProductId) and Item.GetBySystemId(ShopifyProduct."Item SystemId") then begin case Shop."Action for Removed Products" of Shop."Action for Removed Products"::StatusToArchived: - if Item.Blocked and (ShopifyProduct.Status = ShopifyProduct.Status::Archived) then + if Item.Blocked and (ShopifyProduct.Status = ShopifyProduct.Status::Archived) then begin + SkippedRecord.LogSkippedRecord(ShopifyProduct.Id, Item.RecordId, ItemIsArchivedLbl, Shop); exit; + end; Shop."Action for Removed Products"::StatusToDraft: - if Item.Blocked and (ShopifyProduct.Status = ShopifyProduct.Status::Draft) then + if Item.Blocked and (ShopifyProduct.Status = ShopifyProduct.Status::Draft) then begin + SkippedRecord.LogSkippedRecord(ShopifyProduct.Id, Item.RecordId, ItemIsDraftLbl, Shop); exit; + end; Shop."Action for Removed Products"::DoNothing: - if Item.Blocked then + if Item.Blocked then begin + SkippedRecord.LogSkippedRecord(ShopifyProduct.Id, Item.RecordId, ItemIsBlockedLbl, Shop); exit; + end; end; TempShopifyProduct := ShopifyProduct; FillInProductFields(Item, ShopifyProduct); diff --git a/Apps/W1/Shopify/app/src/Shipping/Codeunits/ShpfyExportShipments.Codeunit.al b/Apps/W1/Shopify/app/src/Shipping/Codeunits/ShpfyExportShipments.Codeunit.al index d3e7ea1fff..d81e737815 100644 --- a/Apps/W1/Shopify/app/src/Shipping/Codeunits/ShpfyExportShipments.Codeunit.al +++ b/Apps/W1/Shopify/app/src/Shipping/Codeunits/ShpfyExportShipments.Codeunit.al @@ -40,9 +40,12 @@ codeunit 30190 "Shpfy Export Shipments" ShopifyOrderHeader: Record "Shpfy Order Header"; OrderFulfillments: Codeunit "Shpfy Order Fulfillments"; JsonHelper: Codeunit "Shpfy Json Helper"; + SkippedRecord: Codeunit "Shpfy Skipped Record"; JFulfillment: JsonToken; JResponse: JsonToken; FulfillmentOrderRequest: Text; + NoCorrespondingFulfillmentLinesLbl: Label 'No corresponding fulfillment lines found.'; + NoFulfillmentCreatedInShopifyLbl: Label 'Fulfillment was not created in Shopify.'; begin if ShopifyOrderHeader.Get(SalesShipmentHeader."Shpfy Order Id") then begin ShopifyCommunicationMgt.SetShop(ShopifyOrderHeader."Shop Code"); @@ -53,10 +56,14 @@ codeunit 30190 "Shpfy Export Shipments" JFulfillment := JsonHelper.GetJsonToken(JResponse, 'data.fulfillmentCreateV2.fulfillment'); if (JFulfillment.IsObject) then SalesShipmentHeader."Shpfy Fulfillment Id" := OrderFulfillments.ImportFulfillment(SalesShipmentHeader."Shpfy Order Id", JFulfillment) - else + else begin + SkippedRecord.LogSkippedRecord(SalesShipmentHeader."Shpfy Order Id", SalesShipmentHeader.RecordId, NoFulfillmentCreatedInShopifyLbl, Shop); SalesShipmentHeader."Shpfy Fulfillment Id" := -1; - end else + end; + end else begin + SkippedRecord.LogSkippedRecord(SalesShipmentHeader."Shpfy Order Id", SalesShipmentHeader.RecordId, NoCorrespondingFulfillmentLinesLbl, Shop); SalesShipmentHeader."Shpfy Fulfillment Id" := -1; + end; SalesShipmentHeader.Modify(true); end; end; diff --git a/Apps/W1/Shopify/app/src/Shipping/Reports/ShpfySyncShipmToShopify.Report.al b/Apps/W1/Shopify/app/src/Shipping/Reports/ShpfySyncShipmToShopify.Report.al index 436a966454..13764c784b 100644 --- a/Apps/W1/Shopify/app/src/Shipping/Reports/ShpfySyncShipmToShopify.Report.al +++ b/Apps/W1/Shopify/app/src/Shipping/Reports/ShpfySyncShipmToShopify.Report.al @@ -31,11 +31,15 @@ report 30109 "Shpfy Sync Shipm. to Shopify" ShopifyOrderHeader: Record "Shpfy Order Header"; ShipmentLine: Record "Sales Shipment Line"; Shop: Record "Shpfy Shop"; + SkippedRecord: Codeunit "Shpfy Skipped Record"; + NoLinesApplicableLbl: Label 'No lines applicable for fulfillment.'; + ShopifyOrderNotExistsLbl: Label 'Shopify order %1 does not exist.', Comment = '%1 = Shopify Order Id'; begin ShipmentLine.SetRange("Document No.", "No."); ShipmentLine.SetRange(Type, ShipmentLine.Type::"Item"); ShipmentLine.SetFilter(Quantity, '>0'); if ShipmentLine.IsEmpty() then begin + SkippedRecord.LogSkippedRecord("Sales Shipment Header"."Shpfy Order Id", "Sales Shipment Header".RecordId, NoLinesApplicableLbl, Shop); "Shpfy Fulfillment Id" := -2; Modify(); end else @@ -43,7 +47,8 @@ report 30109 "Shpfy Sync Shipm. to Shopify" Shop.Get(ShopifyOrderHeader."Shop Code"); FulfillmentOrdersAPI.GetShopifyFulfillmentOrdersFromShopifyOrder(Shop, "Sales Shipment Header"."Shpfy Order Id"); ExportShipments.CreateShopifyFulfillment("Sales Shipment Header"); - end; + end else + SkippedRecord.LogSkippedRecord("Sales Shipment Header"."Shpfy Order Id", "Sales Shipment Header".RecordId, StrSubstNo(ShopifyOrderNotExistsLbl, "Sales Shipment Header"."Shpfy Order Id"), Shop); end; } } diff --git a/Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogSub.Codeunit.al b/Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogSub.Codeunit.al new file mode 100644 index 0000000000..36fe59f879 --- /dev/null +++ b/Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogSub.Codeunit.al @@ -0,0 +1,104 @@ +codeunit 139583 "Shpfy Skipped Record Log Sub." +{ + EventSubscriberInstance = Manual; + + var + ShopifyCustomerId: BigInteger; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Shpfy Communication Events", 'OnClientSend', '', true, false)] + local procedure OnClientSend(HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage) + begin + MakeResponse(HttpRequestMessage, HttpResponseMessage); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Shpfy Communication Events", 'OnGetContent', '', true, false)] + local procedure OnGetContent(HttpResponseMessage: HttpResponseMessage; var Response: Text) + begin + HttpResponseMessage.Content.ReadAs(Response); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Shpfy Customer Events", OnBeforeFindMapping, '', true, false)] + local procedure OnBeforeFindMapping(var Handled: Boolean; var ShopifyCustomer: Record "Shpfy Customer") + begin + ShopifyCustomer.Id := ShopifyCustomerId; + Handled := true; + end; + + local procedure MakeResponse(HttpRequestMessage: HttpRequestMessage; var HttpResponseMessage: HttpResponseMessage) + var + Uri: Text; + GraphQlQuery: Text; + GetCustomersGQLMsg: Label '{"query":"{customers(first:100){pageInfo{endCursor hasNextPage} nodes{ legacyResourceId }}}"}', Locked = true; + GetProductMetafieldsGQLStartMsg: Label '{"query":"{product(id: \"gid://shopify/Product/', Locked = true; + GetProductMetafieldsGQLEndMsg: Label '\") { metafields(first: 50) {edges{node{legacyResourceId updatedAt}}}}}"}', Locked = true; + GetVariantMetafieldsGQLStartMsg: Label '{"query":"{productVariant(id: \"gid://shopify/ProductVariant/', Locked = true; + GetVariantMetafieldGQLEndMsg: Label '\") { metafields(first: 50) {edges{ node{legacyResourceId updatedAt}}}}}"}', Locked = true; + CreateFulfimentGQLStartMsg: Label '{"query": "mutation {fulfillmentCreateV2( fulfillment: {notifyCustomer: true, trackingInfo: {number: ', Locked = true; + GraphQLCmdTxt: Label '/graphql.json', Locked = true; + begin + case HttpRequestMessage.Method of + 'POST': + begin + Uri := HttpRequestMessage.GetRequestUri(); + if Uri.EndsWith(GraphQLCmdTxt) then + if HttpRequestMessage.Content.ReadAs(GraphQlQuery) then + case true of + GraphQlQuery.Contains(GetCustomersGQLMsg): + HttpResponseMessage := GetCustomersResult(); + GraphQlQuery.StartsWith(GetProductMetafieldsGQLStartMsg) and GraphQlQuery.EndsWith(GetProductMetafieldsGQLEndMsg): + HttpResponseMessage := GetProductMetafieldsEmptyResult(); + GraphQlQuery.StartsWith(GetVariantMetafieldsGQLStartMsg) and GraphQlQuery.EndsWith(GetVariantMetafieldGQLEndMsg): + HttpResponseMessage := GetVariantMetafieldsEmptyResult(); + GraphQlQuery.StartsWith(CreateFulfimentGQLStartMsg): + HttpResponseMessage := GetCreateFulfilmentFailedResult(); + end; + end; + end; + end; + + local procedure GetCustomersResult(): HttpResponseMessage + var + HttpResponseMessage: HttpResponseMessage; + Body: Text; + begin + Body := '{ "data": { "customers": { "pageInfo": { "hasNextPage": false }, "edges": [] } }, "extensions": { "cost": { "requestedQueryCost": 12, "actualQueryCost": 2, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1998, "restoreRate": 100 } } } }'; + HttpResponseMessage.Content.WriteFrom(Body); + exit(HttpResponseMessage); + end; + + local procedure GetProductMetafieldsEmptyResult(): HttpResponseMessage + var + HttpResponseMessage: HttpResponseMessage; + Body: Text; + begin + Body := '{ "data": { "customers": { "pageInfo": { "hasNextPage": false }, "edges": [] } }, "extensions": { "cost": { "requestedQueryCost": 12, "actualQueryCost": 2, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1998, "restoreRate": 100 } } } }'; + HttpResponseMessage.Content.WriteFrom(Body); + exit(HttpResponseMessage); + end; + + local procedure GetVariantMetafieldsEmptyResult(): HttpResponseMessage + var + HttpResponseMessage: HttpResponseMessage; + Body: Text; + begin + Body := '{ "data": { "productVariant": { "metafields": { "edges": [] } } }, "extensions": { "cost": { "requestedQueryCost": 10, "actualQueryCost": 3, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1997, "restoreRate": 100 } } } }'; + HttpResponseMessage.Content.WriteFrom(Body); + exit(HttpResponseMessage); + end; + + local procedure GetCreateFulfilmentFailedResult(): HttpResponseMessage + var + HttpResponseMessage: HttpResponseMessage; + Body: Text; + begin + Body := '{ "data": { "fulfillmentCreateV2": { "fulfillment": null, "userErrors": [ { "field": [ "fulfillment" ], "message": "Fulfillment order does not exist." } ] } }, "extensions": { "cost": { "requestedQueryCost": 24, "actualQueryCost": 10, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1990, "restoreRate": 100 } } } }'; + HttpResponseMessage.Content.WriteFrom(Body); + exit(HttpResponseMessage); + end; + + internal procedure SetShopifyCustomerId(Id: BigInteger) + begin + ShopifyCustomerId := Id; + end; + +} diff --git a/Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogTest.Codeunit.al b/Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogTest.Codeunit.al new file mode 100644 index 0000000000..783d41bb29 --- /dev/null +++ b/Apps/W1/Shopify/test/Logs/ShpfySkippedRecordLogTest.Codeunit.al @@ -0,0 +1,828 @@ +codeunit 139581 "Shpfy Skipped Record Log Test" +{ + Subtype = Test; + TestPermissions = Disabled; + + var + Shop: Record "Shpfy Shop"; + ShpfyInitializeTest: Codeunit "Shpfy Initialize Test"; + LibraryAssert: Codeunit "Library Assert"; + Any: Codeunit Any; + SalesShipmentNo: Code[20]; + IsInitialized: Boolean; + + trigger OnRun() + begin + IsInitialized := false; + end; + + [Test] + procedure UnitTestLogEmptyCustomerEmail() + var + Customer: Record Customer; + SkippedRecord: Record "Shpfy Skipped Record"; + begin + // [SCENARIO] Log skipped record when customer email is empty on customer export to shopify. + Initialize(); + + // [GIVEN] A customer record with empty email. + CreateCustomerWithEmail(Customer, ''); + + // [WHEN] Invoke Shopify Customer Export + InvokeShopifyCustomerExport(Customer, 0); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Customer.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Customer has no e-mail address.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogCustomerForSameEmailExist() + var + Customer: Record Customer; + ShpfyCustomer: Record "Shpfy Customer"; + SkippedRecord: Record "Shpfy Skipped Record"; + begin + // [SCENARIO] Log skipped record when customer with same email already exist on customer export to shopify. + Initialize(); + + // [GIVEN] A customer record with email that already exist in shopify. + CreateCustomerWithEmail(Customer, 'dummy@cust.com'); + // [GIVEN] Shopify customer with random guid. + CreateShopifyCustomerWithRandomGuid(ShpfyCustomer); + + // [WHEN] Invoke Shopify Customer Export + InvokeShopifyCustomerExport(Customer, ShpfyCustomer.Id); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Customer.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Customer already exists with the same e-mail or phone.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogProductItemBlocked() + var + Item: Record Item; + ShpfyItem: Record "Shpfy Product"; + SkippedRecord: Record "Shpfy Skipped Record"; + ProductExport: Codeunit "Shpfy Product Export"; + begin + // [SCENARIO] Log skipped record when product item is blocked + Initialize(); + + // [GIVEN] A product item record that is blocked. + CreateBlockedItem(Item); + // [GIVEN] Shopify Product + CreateShpfyProduct(ShpfyItem, Item.SystemId, Shop.Code); + + // [WHEN] Invoke Shopify Product Export + ProductExport.SetShop(Shop); + Shop.SetRange("Code", Shop.Code); + ProductExport.Run(Shop); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Item.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Item is blocked.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogProductItemBlockedAndProductArchived() + var + Item: Record Item; + ShpfyProduct: Record "Shpfy Product"; + SkippedRecord: Record "Shpfy Skipped Record"; + ProductExport: Codeunit "Shpfy Product Export"; + begin + // [SCENARIO] Log skipped record when product item is blocked and product is archived + Initialize(); + + // [GIVEN] A product item record that is blocked and archived. Shop with action for removed products set to status to archived. + SetActionForRemovedProducts(Shop, Enum::"Shpfy Remove Product Action"::StatusToArchived); + // [GIVEN] Item that is blocked. + CreateBlockedItem(Item); + // [GIVEN] Shpify Product with status archived. + CreateShopifyProductWithStatus(Item, ShpfyProduct, Enum::"Shpfy Product Status"::Archived); + + // [WHEN] Invoke Shopify Product Export + ProductExport.SetShop(Shop); + Shop.SetRange("Code", Shop.Code); + ProductExport.Run(Shop); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Item.RecordId); + SkippedRecord.SetRange("Shopify Id", ShpfyProduct.Id); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Shopify product is archived.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogProductItemBlockedAndProductIsDraft() + var + Item: Record Item; + ShpfyProduct: Record "Shpfy Product"; + SkippedRecord: Record "Shpfy Skipped Record"; + ProductExport: Codeunit "Shpfy Product Export"; + SkippedRecordLogSub: Codeunit "Shpfy Skipped Record Log Sub."; + begin + // [SCENARIO] Log skipped record when product item is blocked and product is draft + Initialize(); + + // [GIVEN] Shop with action for removed products set to status to draft. + SetActionForRemovedProducts(Shop, Enum::"Shpfy Remove Product Action"::StatusToDraft); + + // [GIVEN] Item that is blocked. + CreateBlockedItem(Item); + + // [GIVEN] Shpify Product with status draft. + CreateShopifyProductWithStatus(Item, ShpfyProduct, Enum::"Shpfy Product Status"::Draft); + + // [WHEN] Invoke Shopify Product Export + BindSubscription(SkippedRecordLogSub); + ProductExport.SetShop(Shop); + Shop.SetRange("Code", Shop.Code); + ProductExport.Run(Shop); + UnbindSubscription(SkippedRecordLogSub); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Item.RecordId); + SkippedRecord.SetRange("Shopify Id", ShpfyProduct.Id); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Shopify product is in draft status.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestSkipShopifyVariantPriceCalcWithItemUnitOfMeasureForVariantWithBlockedItem() + var + Item: Record Item; + ShopifyProduct: Record "Shpfy Product"; + ShopifyVariant: Record "Shpfy Variant"; + ItemunitOfMeasure: Record "Item Unit of Measure"; + SkippedRecord: Record "Shpfy Skipped Record"; + ProductExport: Codeunit "Shpfy Product Export"; + begin + // [SCENARIO] Skip shopify variant price calculation using item unit of measure for variant with blocked item. + Initialize(); + + // [GIVEN] Blocked or sales blokced item + CreateBlockedItem(Item); + // [GIVEN] Shopify Product + CreateShpfyProduct(ShopifyProduct, Item.SystemId, Shop.Code, ShopifyVariant); + + // [WHEN] Invoke FillInProductVariantData + ProductExport.SetShop(Shop); + ProductExport.SetOnlyUpdatePriceOn(); + ProductExport.FillInProductVariantData(ShopifyVariant, Item, ItemUnitOfMeasure); + + // [THEN] Related log record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Item.RecordId); + SkippedRecord.SetRange("Shopify Id", ShopifyVariant.Id); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Variant price is not synchronized because the item is blocked or sales blocked.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestSkipShopifyVariantPriceCalcWithItemVariantForVariantWithBlockedItem() + var + Item: Record Item; + ShopifyProduct: Record "Shpfy Product"; + ShopifyVariant: Record "Shpfy Variant"; + ItemVariant: Record "Item Variant"; + SkippedRecord: Record "Shpfy Skipped Record"; + ProductExport: Codeunit "Shpfy Product Export"; + begin + // [SCENARIO] Skip shopify variant price calculation using item variant for variant with blocked item. + Initialize(); + + // [GIVEN] Blocked or sales blokced item + CreateBlockedItem(Item); + // [GIVEN] Shopify Product + CreateShpfyProduct(ShopifyProduct, Item.SystemId, Shop.Code, ShopifyVariant); + + // [WHEN] Invoke FillInProductVariantData + ProductExport.SetShop(Shop); + ProductExport.SetOnlyUpdatePriceOn(); + ProductExport.FillInProductVariantData(ShopifyVariant, Item, ItemVariant); + + // [THEN] Related log record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Item.RecordId); + SkippedRecord.SetRange("Shopify Id", ShopifyVariant.Id); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Variant price is not synchronized because the item is blocked or sales blocked.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestSkipShopifyVariantPriceCalcWithItemUnitOfMeasureAndItemVariantForVariantWithBlockedItem() + var + Item: Record Item; + ShopifyProduct: Record "Shpfy Product"; + ShopifyVariant: Record "Shpfy Variant"; + ItemUnitOfMeasure: Record "Item Unit of Measure"; + ItemVariant: Record "Item Variant"; + SkippedRecord: Record "Shpfy Skipped Record"; + ProductExport: Codeunit "Shpfy Product Export"; + begin + // [SCENARIO] Skip shopify variant price calculation using item unit of measure and item variant for variant with blocked item. + Initialize(); + + // [GIVEN] Blocked or sales blokced item + CreateBlockedItem(Item); + // [GIVEN] Shopify Product + CreateShpfyProduct(ShopifyProduct, Item.SystemId, Shop.Code, ShopifyVariant); + + // [WHEN] Invoke FillInProductVariantData + ProductExport.SetShop(Shop); + ProductExport.SetOnlyUpdatePriceOn(); + ProductExport.FillInProductVariantData(ShopifyVariant, Item, ItemVariant, ItemUnitOfMeasure); + + // [THEN] Related log record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", Item.RecordId); + SkippedRecord.SetRange("Shopify Id", ShopifyVariant.Id); + LibraryAssert.IsTrue(SkippedRecord.FindFirst(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Variant price is not synchronized because the item is blocked or sales blocked.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogSalesInvoiceWithNotExistingShopifyCustomer() + var + Customer: Record Customer; + SalesInvoiceHeader: Record "Sales Invoice Header"; + SkippedRecord: Record "Shpfy Skipped Record"; + PostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; + LibrarySales: Codeunit "Library - Sales"; + begin + // [SCENARIO] Log skipped record when sales invoice export is skipped because not existing shopify customer. + Initialize(); + + // [GIVEN] Customer + LibrarySales.CreateCustomer(Customer); + // [GIVEN] Sales Invoice + CreateSalesInvoiceHeader(SalesInvoiceHeader, Customer."No.", ''); + + // [WHEN] Invoke Shopify Posted Invoice Export + PostedInvoiceExport.ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesInvoiceHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Customer does not exists as Shopify company or customer.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogSalesInvoiceWithNotExistingShopifyPaymentTerms() + var + Customer: Record Customer; + SalesInvoiceHeader: Record "Sales Invoice Header"; + SkippedRecord: Record "Shpfy Skipped Record"; + PostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; + PaymentTermsCode: Code[10]; + begin + // [SCENARIO] Log skipped record when sales invoice export is skipped because of not existing shopify payment terms. + Initialize(); + + // [GIVEN] Customer + Customer := ShpfyInitializeTest.GetDummyCustomer(); + // [GIVEN] Shopify Customer + CreateShopifyCustomer(Customer); + // [GIVEN] Payment Terms Code + PaymentTermsCode := Any.AlphanumericText(10); + // [GIVEN] Sales Invoice + CreateSalesInvoiceHeader(SalesInvoiceHeader, Customer."No.", PaymentTermsCode); + + // [WHEN] Invoke Shopify Posted Invoice Export + PostedInvoiceExport.ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesInvoiceHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual(StrSubstNo('Payment terms %1 do not exist in Shopify.', PaymentTermsCode), SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogSalesInvoiceWithCustomerNoIsDefaultCustomerNo() + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + ShopWithDefaultCustomerNo: Record "Shpfy Shop"; + Customer: Record Customer; + SkippedRecord: Record "Shpfy Skipped Record"; + PostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; + PaymentTermsCode: Code[10]; + begin + // [SCENARIO] Log skipped record when sales invoice export is skipped because bill to customer no which is default shopify shop customer no. + Initialize(); + + // [GIVEN] Customer + CreateRandomCustomer(Customer); + // [GIVEN] Shopify Customer + CreateShopifyCustomer(Customer); + // [GIVEN] Shop with default customer no set. + CreateShopWithDefCustomerNo(ShopWithDefaultCustomerNo, Customer."No."); + // [GIVEN] Payment Terms Code + PaymentTermsCode := CreatePaymentTerms(ShopWithDefaultCustomerNo.Code); + // [GIVEN] Sales Invoice for default customer no. + CreateSalesInvoiceHeader(SalesInvoiceHeader, ShopWithDefaultCustomerNo."Default Customer No.", PaymentTermsCode); + + // [WHEN] Invoke Shopify Posted Invoice Export + PostedInvoiceExport.SetShop(ShopWithDefaultCustomerNo.Code); + PostedInvoiceExport.ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesInvoiceHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Bill-to customer no. is the default customer no. for Shopify shop.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogSalesInvoiceWithCustomerNoUsedInShopifyCustomerTemplates() + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + Customer: Record Customer; + SkippedRecord: Record "Shpfy Skipped Record"; + ShopWithCustTemplates: Record "Shpfy Shop"; + ShopifyCustomerTemplate: Record "Shpfy Customer Template"; + PostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; + PaymentTermsCode: Code[10]; + begin + // [SCENARIO] Log skipped record when sales invoice export is skipped because customer no which is used in shopify customer templates. + Initialize(); + + // [GIVEN] Customer + CreateRandomCustomer(Customer); + // [GIVEN] Shopify Customer + CreateShopifyCustomer(Customer); + // [GIVEN] Shop with Shopify Customer Template for customer no. + CreateShopWithCustomerTemplate(ShopWithCustTemplates, ShopifyCustomerTemplate, Customer."No."); + // [GIVEN] Payment Terms Code + PaymentTermsCode := CreatePaymentTerms(ShopWithCustTemplates.Code); + // [GIVEN] Sales Invoice for default customer no. + CreateSalesInvoiceHeader(SalesInvoiceHeader, ShopifyCustomerTemplate."Default Customer No.", PaymentTermsCode); + + // [WHEN] Invoke Shopify Posted Invoice Export + PostedInvoiceExport.SetShop(ShopWithCustTemplates.Code); + PostedInvoiceExport.ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesInvoiceHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual(StrSubstNo('Shopify customer template exists for customer no. %1 shop %2.', Customer."No.", ShopWithCustTemplates.Code), SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogSalesInvoiceWithoutSalesLine() + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + Customer: Record Customer; + SkippedRecord: Record "Shpfy Skipped Record"; + PostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; + PaymentTermsCode: Code[10]; + begin + // [SCENARIO] Log skipped record when sales invoice export is skipped because it has no sales lines. + Initialize(); + + // [GIVEN] Customer + Customer := ShpfyInitializeTest.GetDummyCustomer(); + // [GIVEN] Shopify Customer + CreateShopifyCustomer(Customer); + // [GIVEN] Payment Terms Code + PaymentTermsCode := CreatePaymentTerms(Shop.Code); + // [GIVEN] Sales Invoice without sales lines. + CreateSalesInvoiceHeader(SalesInvoiceHeader, Customer."No.", PaymentTermsCode); + + // [WHEN] Invoke Shopify Posted Invoice Export + PostedInvoiceExport.SetShop(Shop.Code); + PostedInvoiceExport.ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesInvoiceHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('No relevant sales invoice lines exist.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogSalesInvoiceWithSalesLineWithDecimalQuantity() + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + SalesInvoiceLine: Record "Sales Invoice Line"; + Customer: Record Customer; + SkippedRecord: Record "Shpfy Skipped Record"; + PostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; + LibraryRandom: Codeunit "Library - Random"; + PaymentTermsCode: Code[10]; + begin + // [SCENARIO] Log skipped record when sales invoice export is skipped sales line with decimal quantity. + Initialize(); + + // [GIVEN] Customer + Customer := ShpfyInitializeTest.GetDummyCustomer(); + // [GIVEN] Shopify Customer + CreateShopifyCustomer(Customer); + // [GIVEN] Payment Terms Code + PaymentTermsCode := CreatePaymentTerms(Shop.Code); + // [GIVEN] Sales Invoice with sales line with decimal quantity. + CreateSalesInvoiceHeader(SalesInvoiceHeader, Customer."No.", PaymentTermsCode); + CreateSalesInvoiceLine(SalesInvoiceLine, SalesInvoiceHeader."No.", LibraryRandom.RandDecInDecimalRange(0.01, 0.99, 2), Any.AlphanumericText(20)); + + // [WHEN] Invoke Shopify Posted Invoice Export + PostedInvoiceExport.SetShop(Shop.Code); + PostedInvoiceExport.ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesInvoiceLine.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Invalid quantity in sales invoice line.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestLogSalesInvoiceWithSalesLineWithEmptyNoField() + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + SalesInvoiceLine: Record "Sales Invoice Line"; + Customer: Record Customer; + SkippedRecord: Record "Shpfy Skipped Record"; + PostedInvoiceExport: Codeunit "Shpfy Posted Invoice Export"; + PaymentTermsCode: Code[10]; + begin + // [SCENARIO] Log skipped record when sales invoice export is skipped when sales invoice line has empty No field. + Initialize(); + + // [GIVEN] Customer + Customer := ShpfyInitializeTest.GetDummyCustomer(); + // [GIVEN] Shopify Customer + CreateShopifyCustomer(Customer); + // [GIVEN] Payment Terms Code + PaymentTermsCode := CreatePaymentTerms(Shop.Code); + // [GIVEN] Sales Invoice with sales line with empty No field. + CreateSalesInvoiceHeader(SalesInvoiceHeader, Customer."No.", PaymentTermsCode); + CreateSalesInvoiceLine(SalesInvoiceLine, SalesInvoiceHeader."No.", Any.IntegerInRange(100), ''); + + // [WHEN] Invoke Shopify Posted Invoice Export + PostedInvoiceExport.SetShop(Shop.Code); + PostedInvoiceExport.ExportPostedSalesInvoiceToShopify(SalesInvoiceHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesInvoiceLine.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('No. field is empty in Sales Invoice Line.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + + end; + + [Test] + [HandlerFunctions('SyncPostedShipmentsToShopify')] + procedure UnitTestLogSalesShipmentWithoutShipmentLines() + var + SalesShipmentHeader: Record "Sales Shipment Header"; + SkippedRecord: Record "Shpfy Skipped Record"; + begin + // [SCENARIO] Log skipped record when sales shipment export is skipped because not existing shipment lines. + Initialize(); + + // [GIVEN] Posted shipment without lines. + CreateSalesShipmentHeader(SalesShipmentHeader, Any.IntegerInRange(10000, 999999)); + Commit(); + + // [WHEN] Invoke Shopify Sync Shipment to Shopify + Report.Run(Report::"Shpfy Sync Shipm. to Shopify"); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesShipmentHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('No lines applicable for fulfillment.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + [HandlerFunctions('SyncPostedShipmentsToShopify')] + procedure UnitTestLogSalesShipmentWithNotExistingShopifyOrder() + var + SalesShipmentHeader: Record "Sales Shipment Header"; + SkippedRecord: Record "Shpfy Skipped Record"; + ShopifyOrderId: BigInteger; + begin + // [SCENARIO] Log skipped record when sales shipment export is skipped because not existing related shopify order. + Initialize(); + + // [GIVEN] Random shopify order id + ShopifyOrderId := Any.IntegerInRange(10000, 999999); + + // [GIVEN] Posted shipment with line. + CreateSalesShipmentHeader(SalesShipmentHeader, ShopifyOrderId); + CreateSalesShipmentLine(SalesShipmentHeader."No."); + Commit(); + + // [WHEN] Invoke Shopify Sync Shipment to Shopify + SalesShipmentNo := SalesShipmentHeader."No."; + Report.Run(Report::"Shpfy Sync Shipm. to Shopify"); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesShipmentHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual(StrSubstNo('Shopify order %1 does not exist.', ShopifyOrderId), SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure LogSalesShipmentNoCorrespondingFulfillmentWithFailedResponse() + var + SalesShipmentHeader: Record "Sales Shipment Header"; + SkippedRecord: Record "Shpfy Skipped Record"; + ExportShipments: Codeunit "Shpfy Export Shipments"; + ShippingHelper: Codeunit "Shpfy Shipping Helper"; + ShopifyOrderId: BigInteger; + begin + // [SCENARIO] Log skipped record when sales shipment is export is skip because theres no fulfillment lines shopify. + Initialize(); + + // [GIVEN] Shopify order with line + ShopifyOrderId := CreateshopifyOrder(Shop, Enum::"Shpfy Delivery Method Type"::" "); + // [GIVEN] Posted shipment with line. + ShippingHelper.CreateRandomSalesShipment(SalesShipmentHeader, ShopifyOrderId); + + // [WHEN] Invoke Shopify Sync Shipment to Shopify + ExportShipments.CreateShopifyFulfillment(SalesShipmentHeader); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesShipmentHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('No corresponding fulfillment lines found.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure LogSalesShipmentNoFulfilmentCreatedInShopify() + var + SalesShipmentHeader: Record "Sales Shipment Header"; + SkippedRecord: Record "Shpfy Skipped Record"; + ExportShipments: Codeunit "Shpfy Export Shipments"; + ShippingHelper: Codeunit "Shpfy Shipping Helper"; + SkippedRecordLogSub: Codeunit "Shpfy Skipped Record Log Sub."; + ShopifyOrderId: BigInteger; + DeliveryMethodType: Enum "Shpfy Delivery Method Type"; + begin + // [SCENARIO] Log skipped record when sales shipment is exported with no fulfillment created in shopify. + Initialize(); + + // [GIVEN] Shopify order with line + DeliveryMethodType := DeliveryMethodType::" "; + ShopifyOrderId := CreateShopifyOrder(Shop, DeliveryMethodType); + + // [GIVEN] Shopify fulfilment related to shopify order + ShippingHelper.CreateShopifyFulfillmentOrder(ShopifyOrderId, DeliveryMethodType); + + // [GIVEN] Sales shipment related to shopify order + ShippingHelper.CreateRandomSalesShipment(SalesShipmentHeader, ShopifyOrderId); + + // [WHEN] Invoke Shopify Sync Shipment to Shopify + BindSubscription(SkippedRecordLogSub); + ExportShipments.CreateShopifyFulfillment(SalesShipmentHeader); + UnbindSubscription(SkippedRecordLogSub); + + // [THEN] Related record is created in shopify skipped record table. + SkippedRecord.SetRange("Record ID", SalesShipmentHeader.RecordId); + LibraryAssert.IsTrue(SkippedRecord.FindLast(), 'Skipped record is not created'); + LibraryAssert.AreEqual('Fulfillment was not created in Shopify.', SkippedRecord."Skipped Reason", 'Skipped reason is not as expected'); + end; + + [Test] + procedure UnitTestSkipLoggingWhenShopHasLoggingModeDisabled() + var + ShopWithDisabledLogging: Record "Shpfy Shop"; + SkippedRecord: Record "Shpfy Skipped Record"; + SkippedRecordCodeunit: Codeunit "Shpfy Skipped Record"; + RecordID: RecordID; + ShopifyId: BigInteger; + TableId: Integer; + begin + // [SCENARIO] Skip logging when setup in shop for logging is Disabled. + Initialize(); + + // [GIVEN] Shop with logging mode = Disabled. + CreateShopWithDisabledLogging(ShopWithDisabledLogging); + // [GIVEN] Random Shopify Id + ShopifyId := Any.IntegerInRange(10000, 999999); + + // [WHEN] Invoke Skip Record Management + SkippedRecordCodeunit.LogSkippedRecord(ShopifyId, RecordID, Any.AlphabeticText(250), ShopWithDisabledLogging); + + // [THEN] No record is created in shopify skipped record table. + SkippedRecord.SetRange("Shopify Id", ShopifyId); + SkippedRecord.SetRange("Table Id", TableId); + LibraryAssert.IsTrue(SkippedRecord.IsEmpty(), 'Skipped record is created'); + end; + + local procedure Initialize() + begin + if IsInitialized then + exit; + Shop := ShpfyInitializeTest.CreateShop(); + Shop."Can Update Shopify Customer" := true; + Shop."Can Update Shopify Products" := true; + Shop.Modify(false); + + Commit(); + + IsInitialized := true; + end; + + local procedure CreateShpfyProduct(var ShopifyProduct: Record "Shpfy Product"; ItemSystemId: Guid; ShopCode: Code[20]; var ShopifyVariant: Record "Shpfy Variant") + begin + Randomize(); + ShopifyProduct.DeleteAll(false); + ShopifyProduct.Init(); + ShopifyProduct.Id := Random(999999); + ShopifyProduct."Item SystemId" := ItemSystemId; + ShopifyProduct."Shop Code" := ShopCode; + ShopifyProduct.Insert(false); + ShopifyVariant.DeleteAll(false); + ShopifyVariant.Init(); + ShopifyVariant.Id := Random(999999); + ShopifyVariant."Product Id" := ShopifyProduct.Id; + ShopifyVariant."Item SystemId" := ItemSystemId; + ShopifyVariant."Shop Code" := ShopCode; + ShopifyVariant.Insert(false); + end; + + local procedure CreateShpfyProduct(var ShopifyProduct: Record "Shpfy Product"; ItemSystemId: Guid; ShopCode: Code[20]) + var + ShopifyVariant: Record "Shpfy Variant"; + begin + CreateShpfyProduct(ShopifyProduct, ItemSystemId, ShopCode, ShopifyVariant); + end; + + local procedure CreateSalesInvoiceHeader(var SalesInvoiceHeader: Record "Sales Invoice Header"; CustomerNo: Code[20]; PaymentTermsCode: Code[10]) + begin + SalesInvoiceHeader.Init(); + SalesInvoiceHeader."No." := Any.AlphanumericText(20); + SalesInvoiceHeader."Bill-to Customer No." := CustomerNo; + SalesInvoiceHeader."Payment Terms Code" := PaymentTermsCode; + SalesInvoiceHeader.Insert(false); + end; + + local procedure CreatePaymentTerms(ShopCode: Code[20]): Code[10] + var + PaymentTerms: Record "Payment Terms"; + ShopifyPaymentTerms: Record "Shpfy Payment Terms"; + begin + PaymentTerms.DeleteAll(false); + ShopifyPaymentTerms.DeleteAll(false); + PaymentTerms.Init(); + PaymentTerms.Code := Any.AlphanumericText(10); + PaymentTerms.Insert(false); + ShopifyPaymentTerms.Init(); + ShopifyPaymentTerms."Shop Code" := ShopCode; + ShopifyPaymentTerms."Payment Terms Code" := PaymentTerms.Code; + ShopifyPaymentTerms."Is Primary" := true; + ShopifyPaymentTerms.Insert(false); + exit(PaymentTerms.Code); + end; + + local procedure CreateSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; DocumentNo: Code[20]; Quantity: Decimal; No: Text) + begin + SalesInvoiceLine.Init(); + SalesInvoiceLine."Document No." := DocumentNo; + SalesInvoiceLine.Type := SalesInvoiceLine.Type::Item; + SalesInvoiceLine."No." := No; + SalesInvoiceLine.Quantity := Quantity; + SalesInvoiceLine.Insert(false); + end; + + local procedure CreateSalesShipmentHeader(var SalesShipmentHeader: Record "Sales Shipment Header"; ShpfyOrderId: BigInteger) + begin + SalesShipmentHeader.Init(); + SalesShipmentHeader."No." := Any.AlphanumericText(20); + SalesShipmentHeader."Shpfy Order Id" := ShpfyOrderId; + SalesShipmentHeader.Insert(false); + end; + + local procedure CreateShopWithDisabledLogging(var Shop: Record "Shpfy Shop") + begin + CreateShopWithDefCustomerNo(Shop, ''); + Shop."Logging Mode" := Enum::"Shpfy Logging Mode"::Disabled; + Shop.Modify(false); + end; + + local procedure CreateShopifyOrder(Shop: Record "Shpfy Shop"; DeliveryMethodType: Enum "Shpfy Delivery Method Type"): BigInteger + var + ShopifyOrderHeader: Record "Shpfy Order Header"; + ShippingHelper: Codeunit "Shpfy Shipping Helper"; + LocationId: BigInteger; + ShopifyOrderId: BigInteger; + begin + ShopifyOrderId := ShippingHelper.CreateRandomShopifyOrder(LocationId, DeliveryMethodType); + ShopifyOrderHeader.Get(ShopifyOrderId); + ShopifyOrderHeader."Shop Code" := Shop.Code; + ShopifyOrderHeader.Modify(false); + exit(ShopifyOrderId); + end; + + local procedure CreateShopifyCustomer(Customer: Record Customer) + var + ShopifyCustomer: Record "Shpfy Customer"; + CustomerInitTest: Codeunit "Shpfy Customer Init Test"; + begin + CustomerInitTest.CreateShopifyCustomer(ShopifyCustomer); + ShopifyCustomer."Customer SystemId" := Customer.SystemId; + ShopifyCustomer.Modify(false); + end; + + local procedure CreateRandomCustomer(var Customer: Record Customer) + begin + Customer.Init(); + Customer."No." := Any.AlphanumericText(20); + Customer.Insert(false); + end; + + + local procedure CreateShopifyCustomerTemplate(var ShopifyCustomerTemplate: Record "Shpfy Customer Template"; Shop: Record "Shpfy Shop"; CustomerNo: Code[20]) + begin + ShopifyCustomerTemplate.Init(); + ShopifyCustomerTemplate."Shop Code" := Shop.Code; + ShopifyCustomerTemplate."Default Customer No." := CustomerNo; + ShopifyCustomerTemplate.Insert(false); + end; + + local procedure CreateSalesShipmentLine(SalesShipmentNo: Code[20]) + var + SalesShipmentLine: Record "Sales Shipment Line"; + begin + SalesShipmentLine.Init(); + SalesShipmentLine."Document No." := SalesShipmentNo; + SalesShipmentLine."Line No." := 10000; + SalesShipmentLine."No." := Any.AlphanumericText(20); + SalesShipmentLine.Type := SalesShipmentLine.Type::Item; + SalesShipmentLine.Quantity := Any.IntegerInRange(1, 100); + SalesShipmentLine.Insert(false); + end; + + local procedure CreateBlockedItem(var Item: Record Item) + begin + Item.Init(); + Item."No." := Any.AlphanumericText(20); + Item.Blocked := true; + Item."Sales Blocked" := true; + Item.Insert(false); + end; + + local procedure CreateShopifyCustomerWithRandomGuid(var ShopifyCustomer: Record "Shpfy Customer") + var + CustomerInitTest: Codeunit "Shpfy Customer Init Test"; + begin + CustomerInitTest.CreateShopifyCustomer(ShopifyCustomer); + ShopifyCustomer."Customer SystemId" := CreateGuid(); + end; + + local procedure SetActionForRemovedProducts(var Shop: Record "Shpfy Shop"; ShpfyRemoveProductAction: Enum Microsoft.Integration.Shopify."Shpfy Remove Product Action") + begin + Shop."Action for Removed Products" := ShpfyRemoveProductAction; + Shop.Modify(false); + end; + + local procedure CreateShopifyProductWithStatus(var Item: Record Item; var ShpfyProduct: Record "Shpfy Product"; ShpfyProductStatus: Enum Microsoft.Integration.Shopify."Shpfy Product Status") + begin + CreateShpfyProduct(ShpfyProduct, Item.SystemId, Shop.Code); + ShpfyProduct.Status := ShpfyProductStatus; + ShpfyProduct.Modify(false); + end; + + local procedure CreateCustomerWithEmail(var Customer: Record Customer; EmailAdress: Text) + begin + Customer.Init(); + Customer."No." := Any.AlphanumericText(20); + Customer."E-Mail" := EmailAdress; + Customer.Insert(true); + end; + + local procedure CreateShopWithDefCustomerNo(var Shop: Record "Shpfy Shop"; DefaultCustomer: Code[20]) + begin + Shop.Init(); + Shop.Code := Any.AlphanumericText(20); + Shop."Default Customer No." := DefaultCustomer; + Shop.Insert(false); + end; + + local procedure InvokeShopifyCustomerExport(var Customer: Record Customer; ShpfyCustomerId: BigInteger) + var + CustomerExport: Codeunit "Shpfy Customer Export"; + SkippedRecordLogSub: Codeunit "Shpfy Skipped Record Log Sub."; + begin + BindSubscription(SkippedRecordLogSub); + if ShpfyCustomerId <> 0 then + SkippedRecordLogSub.SetShopifyCustomerId(ShpfyCustomerId); + CustomerExport.SetShop(Shop); + CustomerExport.SetCreateCustomers(true); + Customer.SetRange("No.", Customer."No."); + CustomerExport.Run(Customer); + UnbindSubscription(SkippedRecordLogSub); + end; + + local procedure CreateShopWithCustomerTemplate(var ShopWithCustTemplates: Record "Shpfy Shop"; var ShopifyCustomerTemplate: Record "Shpfy Customer Template"; CustomerNo: Code[20]) + begin + CreateShopWithDefCustomerNo(ShopWithCustTemplates, ''); + CreateShopifyCustomerTemplate(ShopifyCustomerTemplate, ShopWithCustTemplates, CustomerNo); + end; + + [RequestPageHandler] + procedure SyncPostedShipmentsToShopify(var SyncShipmToShopify: TestRequestPage "Shpfy Sync Shipm. to Shopify") + begin + SyncShipmToShopify."Sales Shipment Header".SetFilter("No.", SalesShipmentNo); + SyncShipmToShopify.OK().Invoke(); + end; +} diff --git a/Apps/W1/Shopify/test/Shipping/ShpfyShippingHelper.Codeunit.al b/Apps/W1/Shopify/test/Shipping/ShpfyShippingHelper.Codeunit.al new file mode 100644 index 0000000000..e10cfceb43 --- /dev/null +++ b/Apps/W1/Shopify/test/Shipping/ShpfyShippingHelper.Codeunit.al @@ -0,0 +1,86 @@ +codeunit 139614 "Shpfy Shipping Helper" +{ + internal procedure CreateRandomShopifyOrder(LocationId: BigInteger; DeliveryMethodType: Enum "Shpfy Delivery Method Type"): BigInteger + var + OrderHeader: Record "Shpfy Order Header"; + OrderLine: Record "Shpfy Order Line"; + Any: Codeunit Any; + begin + Any.SetDefaultSeed(); + Clear(OrderHeader); + OrderHeader."Shopify Order Id" := Any.IntegerInRange(10000, 99999); + OrderHeader.Insert(); + + Clear(OrderLine); + OrderLine."Shopify Order Id" := OrderHeader."Shopify Order Id"; + OrderLine."Shopify Product Id" := Any.IntegerInRange(10000, 99999); + OrderLine."Shopify Variant Id" := Any.IntegerInRange(10000, 99999); + OrderLine."Line Id" := Any.IntegerInRange(10000, 99999); + OrderLine.Quantity := Any.IntegerInRange(1, 10); + OrderLine."Location Id" := LocationId; + OrderLine."Delivery Method Type" := DeliveryMethodType; + OrderLine.Insert(); + + exit(OrderHeader."Shopify Order Id"); + end; + + internal procedure CreateShopifyFulfillmentOrder(ShopifyOrderId: BigInteger; DeliveryMethodType: Enum "Shpfy Delivery Method Type"): BigInteger + var + OrderLine: Record "Shpfy Order Line"; + FulfillmentOrderHeader: Record "Shpfy FulFillment Order Header"; + FulfillmentOrderLine: Record "Shpfy FulFillment Order Line"; + Any: Codeunit Any; + begin + Any.SetDefaultSeed(); + Clear(FulfillmentOrderHeader); + FulfillmentOrderHeader."Shopify Fulfillment Order Id" := Any.IntegerInRange(10000, 99999); + FulfillmentOrderHeader."Shopify Order Id" := ShopifyOrderId; + FulfillmentOrderHeader."Delivery Method Type" := FulfillmentOrderHeader."Delivery Method Type"::Shipping; + FulfillmentOrderHeader.Insert(); + + OrderLine.Reset(); + OrderLine.SetRange("Shopify Order Id", ShopifyOrderId); + if OrderLine.FindSet() then + repeat + Clear(FulfillmentOrderLine); + FulfillmentOrderLine."Shopify Fulfillment Order Id" := FulfillmentOrderHeader."Shopify Fulfillment Order Id"; + FulfillmentOrderLine."Shopify Fulfillm. Ord. Line Id" := Any.IntegerInRange(10000, 99999); + FulfillmentOrderLine."Shopify Order Id" := FulfillmentOrderHeader."Shopify Order Id"; + FulfillmentOrderLine."Shopify Product Id" := OrderLine."Shopify Product Id"; + FulfillmentOrderLine."Shopify Variant Id" := OrderLine."Shopify Variant Id"; + FulfillmentOrderLine."Remaining Quantity" := OrderLine.Quantity; + FulfillmentOrderLine."Shopify Location Id" := OrderLine."Location Id"; + FulfillmentOrderLine."Delivery Method Type" := DeliveryMethodType; + FulfillmentOrderLine.Insert(); + until OrderLine.Next() = 0; + + exit(FulfillmentOrderHeader."Shopify Fulfillment Order Id"); + end; + + internal procedure CreateRandomSalesShipment(var SalesShipmentHeader: Record "Sales Shipment Header"; ShopifyOrderId: BigInteger) + var + SalesShipmentLine: Record "Sales Shipment Line"; + OrderLine: Record "Shpfy Order Line"; + Any: Codeunit Any; + begin + Any.SetDefaultSeed(); + Clear(SalesShipmentHeader); + SalesShipmentHeader."No." := Any.AlphanumericText(MaxStrLen(SalesShipmentHeader."No.")); + SalesShipmentHeader."Shpfy Order Id" := ShopifyOrderId; + SalesShipmentHeader."Package Tracking No." := Any.AlphanumericText(MaxStrLen(SalesShipmentHeader."Package Tracking No.")); + SalesShipmentHeader.Insert(); + + OrderLine.Reset(); + OrderLine.SetRange("Shopify Order Id", ShopifyOrderId); + if OrderLine.FindSet() then + repeat + Clear(SalesShipmentLine); + SalesShipmentLine."Document No." := SalesShipmentHeader."No."; + SalesShipmentLine.Type := SalesShipmentLine.type::Item; + SalesShipmentLine."No." := Any.AlphanumericText(MaxStrLen(SalesShipmentLine."No.")); + SalesShipmentLine."Shpfy Order Line Id" := OrderLine."Line Id"; + SalesShipmentLine.Quantity := OrderLine.Quantity; + SalesShipmentLine.Insert(); + until OrderLine.Next() = 0; + end; +} diff --git a/Apps/W1/Shopify/test/Shipping/ShpfyShippingTest.Codeunit.al b/Apps/W1/Shopify/test/Shipping/ShpfyShippingTest.Codeunit.al index 578e420c0a..2a412294f6 100644 --- a/Apps/W1/Shopify/test/Shipping/ShpfyShippingTest.Codeunit.al +++ b/Apps/W1/Shopify/test/Shipping/ShpfyShippingTest.Codeunit.al @@ -15,6 +15,7 @@ codeunit 139606 "Shpfy Shipping Test" Shop: Record "Shpfy Shop"; ExportShipments: Codeunit "Shpfy Export Shipments"; JsonHelper: Codeunit "Shpfy Json Helper"; + ShippingHelper: Codeunit "Shpfy Shipping Helper"; DeliveryMethodType: Enum "Shpfy Delivery Method Type"; FulfillmentRequest: Text; JFulfillment: JsonObject; @@ -29,9 +30,9 @@ codeunit 139606 "Shpfy Shipping Test" Shop.Init(); LocationId := Any.IntegerInRange(10000, 99999); DeliveryMethodType := DeliveryMethodType::Shipping; - ShopifyOrderId := CreateRandomShopifyOrder(LocationId, DeliveryMethodType); - ShopifyFulfillmentOrderId := CreateShopifyFulfillmentOrder(ShopifyOrderId, DeliveryMethodType); - CreateRandomSalesShipment(SalesShipmentHeader, ShopifyOrderId); + ShopifyOrderId := ShippingHelper.CreateRandomShopifyOrder(LocationId, DeliveryMethodType); + ShopifyFulfillmentOrderId := ShippingHelper.CreateShopifyFulfillmentOrder(ShopifyOrderId, DeliveryMethodType); + ShippingHelper.CreateRandomSalesShipment(SalesShipmentHeader, ShopifyOrderId); // [WHEN] Invoke the function CreateFulfillmentRequest() FulfillmentRequest := ExportShipments.CreateFulfillmentOrderRequest(SalesShipmentHeader, Shop, LocationId, DeliveryMethodType); @@ -48,82 +49,4 @@ codeunit 139606 "Shpfy Shipping Test" LibraryAssert.AreEqual(SalesShipmentLine.Quantity, JsonHelper.GetValueAsDecimal(JLineItem, 'quantity'), 'quanity check'); end; end; - - local procedure CreateRandomShopifyOrder(LocationId: BigInteger; DeliveryMethodType: Enum "Shpfy Delivery Method Type"): BigInteger - var - OrderHeader: Record "Shpfy Order Header"; - OrderLine: Record "Shpfy Order Line"; - begin - Clear(OrderHeader); - OrderHeader."Shopify Order Id" := Any.IntegerInRange(10000, 99999); - OrderHeader.Insert(); - - Clear(OrderLine); - OrderLine."Shopify Order Id" := OrderHeader."Shopify Order Id"; - OrderLine."Shopify Product Id" := Any.IntegerInRange(10000, 99999); - OrderLine."Shopify Variant Id" := Any.IntegerInRange(10000, 99999); - OrderLine."Line Id" := Any.IntegerInRange(10000, 99999); - OrderLine.Quantity := Any.IntegerInRange(1, 10); - OrderLine."Location Id" := LocationId; - OrderLine."Delivery Method Type" := DeliveryMethodType; - OrderLine.Insert(); - - exit(OrderHeader."Shopify Order Id"); - end; - - local procedure CreateShopifyFulfillmentOrder(ShopifyOrderId: BigInteger; DeliveryMethodType: Enum "Shpfy Delivery Method Type"): BigInteger - var - OrderLine: Record "Shpfy Order Line"; - FulfillmentOrderHeader: Record "Shpfy FulFillment Order Header"; - FulfillmentOrderLine: Record "Shpfy FulFillment Order Line"; - begin - Clear(FulfillmentOrderHeader); - FulfillmentOrderHeader."Shopify Fulfillment Order Id" := Any.IntegerInRange(10000, 99999); - FulfillmentOrderHeader."Shopify Order Id" := ShopifyOrderId; - FulfillmentOrderHeader."Delivery Method Type" := FulfillmentOrderHeader."Delivery Method Type"::Shipping; - FulfillmentOrderHeader.Insert(); - - OrderLine.Reset(); - OrderLine.SetRange("Shopify Order Id", ShopifyOrderId); - if OrderLine.FindSet() then - repeat - Clear(FulfillmentOrderLine); - FulfillmentOrderLine."Shopify Fulfillment Order Id" := FulfillmentOrderHeader."Shopify Fulfillment Order Id"; - FulfillmentOrderLine."Shopify Fulfillm. Ord. Line Id" := Any.IntegerInRange(10000, 99999); - FulfillmentOrderLine."Shopify Order Id" := FulfillmentOrderHeader."Shopify Order Id"; - FulfillmentOrderLine."Shopify Product Id" := OrderLine."Shopify Product Id"; - FulfillmentOrderLine."Shopify Variant Id" := OrderLine."Shopify Variant Id"; - FulfillmentOrderLine."Remaining Quantity" := OrderLine.Quantity; - FulfillmentOrderLine."Shopify Location Id" := OrderLine."Location Id"; - FulfillmentOrderLine."Delivery Method Type" := DeliveryMethodType; - FulfillmentOrderLine.Insert(); - until OrderLine.Next() = 0; - - exit(FulfillmentOrderHeader."Shopify Fulfillment Order Id"); - end; - - local procedure CreateRandomSalesShipment(var SalesShipmentHeader: Record "Sales Shipment Header"; ShopifyOrderId: BigInteger) - var - SalesShipmentLine: Record "Sales Shipment Line"; - OrderLine: Record "Shpfy Order Line"; - begin - Clear(SalesShipmentHeader); - SalesShipmentHeader."No." := Any.AlphanumericText(MaxStrLen(SalesShipmentHeader."No.")); - SalesShipmentHeader."Shpfy Order Id" := ShopifyOrderId; - SalesShipmentHeader."Package Tracking No." := Any.AlphanumericText(MaxStrLen(SalesShipmentHeader."Package Tracking No.")); - SalesShipmentHeader.Insert(); - - OrderLine.Reset(); - OrderLine.SetRange("Shopify Order Id", ShopifyOrderId); - if OrderLine.FindSet() then - repeat - Clear(SalesShipmentLine); - SalesShipmentLine."Document No." := SalesShipmentHeader."No."; - SalesShipmentLine.Type := SalesShipmentLine.type::Item; - SalesShipmentLine."No." := Any.AlphanumericText(MaxStrLen(SalesShipmentLine."No.")); - SalesShipmentLine."Shpfy Order Line Id" := OrderLine."Line Id"; - SalesShipmentLine.Quantity := OrderLine.Quantity; - SalesShipmentLine.Insert(); - until OrderLine.Next() = 0; - end; } \ No newline at end of file