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