From 7c77145d3e0fed913f1d282140b2197cf3c38609 Mon Sep 17 00:00:00 2001 From: Tautvydas Labarauskas Date: Fri, 6 Feb 2026 14:40:44 +0200 Subject: [PATCH 1/2] =?UTF-8?q?[Shopify]=20Automatic=20Transaction=20Posti?= =?UTF-8?q?ng=20Summary=20This=20PR=20introduces=20automatic=20posting=20o?= =?UTF-8?q?f=20Shopify=20order=20transactions=20as=20general=20journal=20l?= =?UTF-8?q?ines=20when=20posting=20invoices=20and=20credit=20memos=20conne?= =?UTF-8?q?cted=20to=20Shopify=20Orders=20in=20Business=20Central.=20Chang?= =?UTF-8?q?es=20New=20Functionality:=20=E2=80=A2=09Automatic=20posting=20s?= =?UTF-8?q?etup=20fields=20in=20"Shpfy=20Payment=20Method=20Mapping"=20tab?= =?UTF-8?q?le=20and=20page:=20Added=20"Post=20Automatically",=20"Auto-Post?= =?UTF-8?q?=20Jnl.=20Template"=20and=20"Auto-Post=20Jnl.=20Batch"=20fields?= =?UTF-8?q?.=20=E2=80=A2=09Visibility=20for=20transactions=20which=20will?= =?UTF-8?q?=20be=20automatically=20posted:=20Added=20field=20"Auto-Post=20?= =?UTF-8?q?Enabled"=20to=20"Shpfy=20Order=20Transaction"=20table=20and=20"?= =?UTF-8?q?Shpfy=20Order=20Transaction"=20page.=20=E2=80=A2=09Automatic=20?= =?UTF-8?q?post=20of=20order=20transactions:=20When=20posting=20sales=20or?= =?UTF-8?q?der,=20related=20shopify=20order=20transactions=20are=20created?= =?UTF-8?q?=20as=20cust.=20ledger=20entries=20if=20auto-post=20is=20enable?= =?UTF-8?q?d=20for=20those=20transactions.=20=E2=80=A2=09Automatic=20post?= =?UTF-8?q?=20of=20refund=20transactions:=20When=20posting=20sales=20cr.?= =?UTF-8?q?=20memo,=20related=20shopify=20refund=20transaction=20is=20crea?= =?UTF-8?q?ted=20as=20cust.=20ledger=20entry=20if=20auto-post=20is=20enabl?= =?UTF-8?q?ed=20for=20that=20transaction.=20Prerequisites=20to=20post=20tr?= =?UTF-8?q?ansactions=20automatically:=20=E2=80=A2=09In=20"Shpfy=20Payment?= =?UTF-8?q?=20Method=20Mapping",=20"Post=20Automatically"=20is=20set=20as?= =?UTF-8?q?=20true,=20"Auto-Post=20Jnl.=20Template"=20and=20"Auto-Post=20J?= =?UTF-8?q?nl.=20Batch"=20fields=20are=20filled=20in.=20=E2=80=A2=09Transa?= =?UTF-8?q?ction=20relates=20to=20"Shpfy=20Payment=20Method=20Mapping"=20w?= =?UTF-8?q?hich=20is=20set=20up=20for=20automatic=20post.=20Tests:=20?= =?UTF-8?q?=E2=80=A2=09Added=20ShpfyItemAttrAsOptionTest.Codeunit.al=20(ID?= =?UTF-8?q?=20139540)=20with=20tests=20for:=20o=09Auto-Post=20Jnl.=20Batch?= =?UTF-8?q?=20validation=20when=20journal=20batch=20has=20a=20balancing=20?= =?UTF-8?q?account=20number.=20o=09Auto-Post=20Jnl.=20Batch=20validation?= =?UTF-8?q?=20when=20journal=20batch=20does=20not=20have=20a=20balancing?= =?UTF-8?q?=20account=20number.=20o=09Auto-Post=20Jnl.=20Batch=20validatio?= =?UTF-8?q?n=20when=20value=20is=20empty.=20o=09Successfully=20automatical?= =?UTF-8?q?ly=20posting=20Order=20Transaction=20when=20Post=20Automaticall?= =?UTF-8?q?y=20is=20set=20to=20true.=20o=09Not=20posting=20Order=20Transac?= =?UTF-8?q?tion=20when=20Post=20Automatically=20is=20set=20to=20false.=20o?= =?UTF-8?q?=09Successfully=20automatically=20posting=20multiple=20Order=20?= =?UTF-8?q?Transactions=20when=20Post=20Automatically=20is=20set=20to=20tr?= =?UTF-8?q?ue.=20o=09Successfully=20posting=20only=20transactions=20linked?= =?UTF-8?q?=20to=20auto=20post=20Payment=20Method=20Mapping=20and=20not=20?= =?UTF-8?q?posting=20non=20linked=20transactions=20o=09Successfully=20auto?= =?UTF-8?q?matically=20posting=20Refund=20Transaction=20when=20Post=20Auto?= =?UTF-8?q?matically=20is=20set=20to=20true.=20o=09Successfully=20posting?= =?UTF-8?q?=20sales=20order=20when=20error=20occurs=20in=20posting=20of=20?= =?UTF-8?q?the=20Transaction.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ShpfyDocumentLinkMgt.Codeunit.al | 10 + .../ShpfyAutoPostTransactions.Codeunit.al | 113 ++++ .../ShpfySuggestPayments.Codeunit.al | 20 + .../Pages/ShpfyPaymentMethodsMapping.Page.al | 19 + .../Pages/ShpfyTransactions.Page.al | 5 + .../Reports/ShpfySuggestPayments.Report.al | 38 +- .../ShpfyGenJournalLine.TableExt.al | 5 + .../Tables/ShpfyOrderTransaction.Table.al | 6 + .../Tables/ShpfyPaymentMethodMapping.Table.al | 28 + .../ShpfyAutoPostTransTest.Codeunit.al | 491 ++++++++++++++++++ 10 files changed, 724 insertions(+), 11 deletions(-) create mode 100644 src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al create mode 100644 src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al diff --git a/src/Apps/W1/Shopify/App/src/Document Links/Codeunits/ShpfyDocumentLinkMgt.Codeunit.al b/src/Apps/W1/Shopify/App/src/Document Links/Codeunits/ShpfyDocumentLinkMgt.Codeunit.al index 2c2830ca09..c20f5551dd 100644 --- a/src/Apps/W1/Shopify/App/src/Document Links/Codeunits/ShpfyDocumentLinkMgt.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Document Links/Codeunits/ShpfyDocumentLinkMgt.Codeunit.al @@ -64,6 +64,16 @@ codeunit 30262 "Shpfy Document Link Mgt." [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterPostSalesDoc', '', true, false)] local procedure OnAfterSalesPosting(var SalesHeader: Record "Sales Header"; PreviewMode: Boolean; SalesShptHdrNo: Code[20]; SalesInvHdrNo: Code[20]; RetRcpHdrNo: Code[20]; SalesCrMemoHdrNo: Code[20]) + var + ShpfyAutoPostTransactions: Codeunit "Shpfy Auto Post Transactions"; + begin + CreateDocLinksToBCDocs(SalesHeader, PreviewMode, SalesShptHdrNo, SalesInvHdrNo, RetRcpHdrNo, SalesCrMemoHdrNo); + + Commit(); // Ensure Doc. Link To Doc. creation is committed before running the automatic transaction posting. + ShpfyAutoPostTransactions.AutoPostTransactions(SalesInvHdrNo, SalesCrMemoHdrNo); + end; + + local procedure CreateDocLinksToBCDocs(var SalesHeader: Record "Sales Header"; PreviewMode: Boolean; SalesShptHdrNo: Code[20]; SalesInvHdrNo: Code[20]; RetRcpHdrNo: Code[20]; SalesCrMemoHdrNo: Code[20]) var SalesShipmentHeader: Record "Sales Shipment Header"; SalesInvoiceLine: Record "Sales Invoice Line"; diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al new file mode 100644 index 0000000000..97cea6d1d5 --- /dev/null +++ b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al @@ -0,0 +1,113 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.Integration.Shopify; + +using Microsoft.Finance.GeneralLedger.Journal; +using Microsoft.Finance.GeneralLedger.Posting; +using Microsoft.Sales.History; + +/// +/// Codeunit Shpfy Auto Post Transactions (ID 30236). +/// +codeunit 30236 "Shpfy Auto Post Transactions" +{ + Access = Internal; + + internal procedure AutoPostTransactions(SalesInvoiceHeaderNo: Code[20]; SalesCrMemoHeaderNo: Code[20]) + begin + if SalesInvoiceHeaderNo <> '' then + PostOrderTransaction(SalesInvoiceHeaderNo); + if SalesCrMemoHeaderNo <> '' then + PostRefundTransaction(SalesCrMemoHeaderNo); + end; + + local procedure PostOrderTransaction(SalesInvoiceHeaderNo: Code[20]) + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + OrderTransaction: Record "Shpfy Order Transaction"; + begin + if not SalesInvoiceHeader.Get(SalesInvoiceHeaderNo) then + exit; + + if SalesInvoiceHeader."Shpfy Order Id" = 0 then + exit; + + OrderTransaction.SetRange("Shopify Order Id", SalesInvoiceHeader."Shpfy Order Id"); + OrderTransaction.SetFilter(Type, '%1|%2', OrderTransaction.Type::Capture, OrderTransaction.Type::Sale); + PostTransaction(OrderTransaction, SalesInvoiceHeader."Posting Date"); + end; + + local procedure PostRefundTransaction(SalesCrMemoHeaderNo: Code[20]) + var + SalesCrMemoHeader: Record "Sales Cr.Memo Header"; + OrderTransaction: Record "Shpfy Order Transaction"; + begin + if not SalesCrMemoHeader.Get(SalesCrMemoHeaderNo) then + exit; + + if SalesCrMemoHeader."Shpfy Refund Id" = 0 then + exit; + + OrderTransaction.SetRange("Refund Id", SalesCrMemoHeader."Shpfy Refund Id"); + OrderTransaction.SetRange(Type, OrderTransaction.Type::Refund); + PostTransaction(OrderTransaction, SalesCrMemoHeader."Posting Date"); + end; + + local procedure PostTransaction(var OrderTransaction: Record "Shpfy Order Transaction"; PostingDate: Date) + begin + OrderTransaction.SetRange(Status, OrderTransaction.Status::Success); + OrderTransaction.SetRange(Used, false); + if OrderTransaction.FindSet() then + repeat + if ShouldAutoPost(OrderTransaction) then + CreateAndPostJournalLine(OrderTransaction, PostingDate); + until OrderTransaction.Next() = 0; + end; + + local procedure ShouldAutoPost(OrderTransaction: Record "Shpfy Order Transaction"): Boolean + var + PaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + begin + if not PaymentMethodMapping.Get(OrderTransaction.Shop, OrderTransaction.Gateway, OrderTransaction."Credit Card Company") then + exit(false); + + if not PaymentMethodMapping."Post Automatically" then + exit(false); + + if (PaymentMethodMapping."Auto-Post Jnl. Template" = '') or + (PaymentMethodMapping."Auto-Post Jnl. Batch" = '') then + exit(false); + + exit(true); + end; + + local procedure CreateAndPostJournalLine(var OrderTransaction: Record "Shpfy Order Transaction"; PostingDate: Date) + var + GenJournalLine: Record "Gen. Journal Line"; + PaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + SuggestPayments: Report "Shpfy Suggest Payments"; + begin + PaymentMethodMapping.Get(OrderTransaction.Shop, OrderTransaction.Gateway, OrderTransaction."Credit Card Company"); + SuggestPayments.SetJournalParameters(PaymentMethodMapping."Auto-Post Jnl. Template", PaymentMethodMapping."Auto-Post Jnl. Batch", PostingDate); + SuggestPayments.GetOrderTransactions(OrderTransaction); + SuggestPayments.CreateGeneralJournalLines(true); + GenJournalLine.SetRange("Shpfy Transaction Id", OrderTransaction."Shopify Transaction Id"); + GenJournalLine.SetRange("Automatically Posted", true); + if GenJournalLine.FindFirst() then begin + GenJournalLine.SendToPosting(Codeunit::"Gen. Jnl.-Post"); + RemoveNotPostedLines(OrderTransaction."Shopify Transaction Id"); + end; + end; + + local procedure RemoveNotPostedLines(ShopifyTransactionId: BigInteger) + var + GenJournalLine: Record "Gen. Journal Line"; + begin + GenJournalLine.SetRange("Shpfy Transaction Id", ShopifyTransactionId); + if not GenJournalLine.IsEmpty() then + GenJournalLine.DeleteAll(true); + end; +} \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al index c4c2aa31cb..3773046616 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al @@ -29,4 +29,24 @@ codeunit 30311 "Shpfy Suggest Payments" begin NewCustLedgerEntry."Shpfy Transaction Id" := 0; end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post", 'OnBeforeCode', '', false, false)] + local procedure OnBeforeCode(var GenJournalLine: Record "Gen. Journal Line"; var HideDialog: Boolean) + begin + if GenJournalLine."Automatically Posted" then + HideDialog := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post", 'OnBeforeShowPostResultMessage', '', false, false)] + local procedure OnBeforeShowPostResultMessage(var GenJnlLine: Record "Gen. Journal Line"; TempJnlBatchName: Code[10]; var IsHandled: Boolean) + var + AutomaticallyPosted: Boolean; + begin + if GenJnlLine.GetFilter("Automatically Posted") = '' then + exit; + + Evaluate(AutomaticallyPosted, GenJnlLine.GetFilter("Automatically Posted")); + if AutomaticallyPosted then + IsHandled := true; + end; } diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyPaymentMethodsMapping.Page.al b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyPaymentMethodsMapping.Page.al index e5cab053ed..e4f85863b1 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyPaymentMethodsMapping.Page.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyPaymentMethodsMapping.Page.al @@ -37,6 +37,25 @@ page 30132 "Shpfy Payment Methods Mapping" ApplicationArea = All; ToolTip = 'Specifies the corresponding payment method in D365BC.'; } + field(PostAutomatically; Rec."Post Automatically") + { + ApplicationArea = All; + ToolTip = 'Specifies whether payment transactions using this gateway should be automatically posted when the related invoice or credit memo is posted.'; + } + field(AutoPostJnlTemplate; Rec."Auto-Post Jnl. Template") + { + ApplicationArea = All; + ToolTip = 'Specifies the general journal template to use for automatically posting payment transactions.'; + Enabled = Rec."Post Automatically"; + ShowMandatory = Rec."Post Automatically"; + } + field(AutoPostJnlBatch; Rec."Auto-Post Jnl. Batch") + { + ApplicationArea = All; + ToolTip = 'Specifies the general journal batch to use for automatically posting payment transactions.'; + Enabled = Rec."Post Automatically"; + ShowMandatory = Rec."Post Automatically"; + } } } } diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al index 04b48cbf04..7960e9908e 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al @@ -137,6 +137,11 @@ page 30134 "Shpfy Transactions" ApplicationArea = All; ToolTip = 'Specifies the Posted Invoice number to which the transaction relates.'; } + field("Auto-Post Enabled"; Rec."Auto-Post Enabled") + { + ApplicationArea = All; + ToolTip = 'Specifies whether auto-posting is enabled for the transaction.'; + } } } } diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al b/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al index 8f95f48835..a3754cd106 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al @@ -45,7 +45,7 @@ report 30118 "Shpfy Suggest Payments" trigger OnPostDataItem() begin - CreateGeneralJournalLines(); + CreateGeneralJournalLines(false); end; } } @@ -210,6 +210,21 @@ report 30118 "Shpfy Suggest Payments" IgnorePostedTransactions := NewIgnorePostedTransactions; end; + internal procedure SetOrderTransaction(NewOrderTransaction: Record "Shpfy Order Transaction") + begin + OrderTransaction := NewOrderTransaction; + end; + + internal procedure SetJournalParameters(NewTemplateName: Code[10]; NewBatchName: Code[10]; NewPostingDate: Date) + begin + GeneralJournalTemplateName := NewTemplateName; + GeneralJournalBatchName := NewBatchName; + GenJournalLine."Journal Template Name" := NewTemplateName; + GenJournalLine."Journal Batch Name" := NewBatchName; + PostingDate := NewPostingDate; + ValidatePostingDate(); + end; + local procedure ValidatePostingDate() var NoSeries: Codeunit "No. Series"; @@ -252,7 +267,7 @@ report 30118 "Shpfy Suggest Payments" repeat if SalesInvoiceHeader.Closed then continue; - ApplyCustomerLedgerEntries(SalesInvoiceHeader."No.", "Gen. Journal Document Type"::Invoice, AmountToApply, Applied); + ApplyCustomerLedgerEntries(OrderTransaction, SalesInvoiceHeader."No.", "Gen. Journal Document Type"::Invoice, AmountToApply, Applied); until SalesInvoiceHeader.Next() = 0 else begin DocLinkToDoc.SetRange("Shopify Document Type", DocLinkToDoc."Shopify Document Type"::"Shopify Shop Order"); @@ -263,12 +278,12 @@ report 30118 "Shpfy Suggest Payments" SalesInvoiceHeader.Get(DocLinkToDoc."Document No."); if SalesInvoiceHeader.Closed then continue; - ApplyCustomerLedgerEntries(SalesInvoiceHeader."No.", "Gen. Journal Document Type"::Invoice, AmountToApply, Applied); + ApplyCustomerLedgerEntries(OrderTransaction, SalesInvoiceHeader."No.", "Gen. Journal Document Type"::Invoice, AmountToApply, Applied); until DocLinkToDoc.Next() = 0; end; if Applied and (AmountToApply > 0) then - CreateSuggestPaymentGLAccount(AmountToApply, true); + CreateSuggestPaymentGLAccount(OrderTransaction, AmountToApply, true); end; OrderTransaction.Type::Refund: begin @@ -283,18 +298,18 @@ report 30118 "Shpfy Suggest Payments" repeat if SalesCreditMemoHeader.Paid then continue; - ApplyCustomerLedgerEntries(SalesCreditMemoHeader."No.", "Gen. Journal Document Type"::"Credit Memo", AmountToApply, Applied); + ApplyCustomerLedgerEntries(OrderTransaction, SalesCreditMemoHeader."No.", "Gen. Journal Document Type"::"Credit Memo", AmountToApply, Applied); until SalesCreditMemoHeader.Next() = 0; until RefundHeader.Next() = 0; if Applied and (AmountToApply > 0) then - CreateSuggestPaymentGLAccount(AmountToApply, false); + CreateSuggestPaymentGLAccount(OrderTransaction, AmountToApply, false); end; end; end; end; - local procedure ApplyCustomerLedgerEntries(DocumentNo: Code[20]; DocumentType: Enum "Gen. Journal Document Type"; var AmountToApply: Decimal; var Applied: Boolean) + local procedure ApplyCustomerLedgerEntries(OrderTransaction: Record "Shpfy Order Transaction"; DocumentNo: Code[20]; DocumentType: Enum "Gen. Journal Document Type"; var AmountToApply: Decimal; var Applied: Boolean) var CustLedgerEntry: Record "Cust. Ledger Entry"; begin @@ -307,12 +322,12 @@ report 30118 "Shpfy Suggest Payments" if CustLedgerEntry.FindSet() then begin Applied := true; repeat - CreateSuggestPaymentDocument(CustLedgerEntry, AmountToApply, DocumentType = "Gen. Journal Document Type"::Invoice); + CreateSuggestPaymentDocument(CustLedgerEntry, OrderTransaction, AmountToApply, DocumentType = "Gen. Journal Document Type"::Invoice); until CustLedgerEntry.Next() = 0; end; end; - local procedure CreateSuggestPaymentDocument(var CustLedgerEntry: Record "Cust. Ledger Entry"; var AmountToApply: Decimal; IsInvoice: Boolean) + local procedure CreateSuggestPaymentDocument(var CustLedgerEntry: Record "Cust. Ledger Entry"; OrderTransaction: Record "Shpfy Order Transaction"; var AmountToApply: Decimal; IsInvoice: Boolean) begin TempSuggestPayment.Init(); EntryNo += 1; @@ -346,7 +361,7 @@ report 30118 "Shpfy Suggest Payments" TempSuggestPayment.Insert(); end; - local procedure CreateSuggestPaymentGLAccount(AmountToApply: Decimal; IsInvoice: Boolean) + local procedure CreateSuggestPaymentGLAccount(OrderTransaction: Record "Shpfy Order Transaction"; AmountToApply: Decimal; IsInvoice: Boolean) begin TempSuggestPayment.Init(); EntryNo += 1; @@ -363,7 +378,7 @@ report 30118 "Shpfy Suggest Payments" TempSuggestPayment.Insert(); end; - internal procedure CreateGeneralJournalLines() + internal procedure CreateGeneralJournalLines(AutomaticallyPosted: Boolean) var NoSeriesBatch: Codeunit "No. Series - Batch"; LastLineNo: Integer; @@ -418,6 +433,7 @@ report 30118 "Shpfy Suggest Payments" GenJournalLine.Validate("Applies-to Doc. No.", TempSuggestPayment."Credit Memo No."); end; GenJournalLine."Shpfy Transaction Id" := TempSuggestPayment."Shpfy Transaction Id"; + GenJournalLine."Automatically Posted" := AutomaticallyPosted; GenJournalLine.SetSuppressCommit(false); GenJournalLine.Insert(true); diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Table Extensions/ShpfyGenJournalLine.TableExt.al b/src/Apps/W1/Shopify/App/src/Transactions/Table Extensions/ShpfyGenJournalLine.TableExt.al index 48bcbacdb0..76865a958e 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Table Extensions/ShpfyGenJournalLine.TableExt.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Table Extensions/ShpfyGenJournalLine.TableExt.al @@ -17,5 +17,10 @@ tableextension 30202 "Shpfy Gen. Journal Line" extends "Gen. Journal Line" DataClassification = SystemMetadata; Editable = false; } + field(30101; "Automatically Posted"; Boolean) + { + Caption = 'Automatically Posted'; + DataClassification = SystemMetadata; + } } } \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyOrderTransaction.Table.al b/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyOrderTransaction.Table.al index 809a064dc4..a3a702ac51 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyOrderTransaction.Table.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyOrderTransaction.Table.al @@ -271,6 +271,12 @@ table 30133 "Shpfy Order Transaction" Caption = 'Shop Code'; TableRelation = "Shpfy Shop"; } + field(100; "Auto-Post Enabled"; Boolean) + { + Caption = 'Auto-Post Enabled'; + FieldClass = FlowField; + CalcFormula = lookup("Shpfy Payment Method Mapping"."Post Automatically" where("Shop Code" = field("Shop"), Gateway = field(Gateway), "Credit Card Company" = field("Credit Card Company"))); + } } keys diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al b/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al index e1ee2fb335..8ecbd470cb 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al @@ -6,6 +6,7 @@ namespace Microsoft.Integration.Shopify; using Microsoft.Bank.BankAccount; +using Microsoft.Finance.GeneralLedger.Journal; /// /// Table Shpfy Payment Method Mapping (ID 30134). @@ -59,6 +60,33 @@ table 30134 "Shpfy Payment Method Mapping" DataClassification = SystemMetadata; Editable = false; } + field(7; "Post Automatically"; Boolean) + { + Caption = 'Post Automatically'; + DataClassification = CustomerContent; + } + field(8; "Auto-Post Jnl. Template"; Code[10]) + { + Caption = 'Auto-Post Journal Template'; + DataClassification = CustomerContent; + TableRelation = "Gen. Journal Template"; + } + field(9; "Auto-Post Jnl. Batch"; Code[10]) + { + Caption = 'Auto-Post Journal Batch'; + DataClassification = CustomerContent; + TableRelation = "Gen. Journal Batch".Name where("Journal Template Name" = field("Auto-Post Jnl. Template")); + + trigger OnValidate() + var + GenJournalBatch: Record "Gen. Journal Batch"; + begin + if "Auto-Post Jnl. Batch" <> '' then begin + GenJournalBatch.Get("Auto-Post Jnl. Template", "Auto-Post Jnl. Batch"); + GenJournalBatch.TestField("Bal. Account No."); + end; + end; + } } keys { diff --git a/src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al new file mode 100644 index 0000000000..1e563c762b --- /dev/null +++ b/src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al @@ -0,0 +1,491 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.Integration.Shopify.Test; + +using Microsoft.Finance.GeneralLedger.Account; +using Microsoft.Finance.GeneralLedger.Journal; +using Microsoft.Finance.VAT.Setup; +using Microsoft.Foundation.Enums; +using Microsoft.Foundation.NoSeries; +using Microsoft.Integration.Shopify; +using Microsoft.Inventory.Item; +using Microsoft.Sales.Customer; +using Microsoft.Sales.Document; +using Microsoft.Sales.History; +using Microsoft.Sales.Receivables; +using System.TestLibraries.Utilities; + +/// +/// Codeunit Shpfy Auto Post Transaction Test (ID 139614). +/// +codeunit 139614 "Shpfy Auto Post Trans. Test" +{ + Subtype = Test; + TestPermissions = Disabled; + + var + Customer: Record Customer; + Item: Record Item; + Shop: Record "Shpfy Shop"; + PaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + LibraryAssert: Codeunit "Library Assert"; + LibraryRandom: Codeunit "Library - Random"; + LibrarySales: Codeunit "Library - Sales"; + IsInitialized: Boolean; + + [Test] + procedure UnitTestAutoPostJnlBatchValidateWithBalAccountNo() + var + ShpfyPaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + GenJournalBatch: Record "Gen. Journal Batch"; + begin + // [SCENARIO] Auto-Post Jnl. Batch field validates successfully when journal batch has a balancing account number + + // [GIVEN] A Gen. Journal Batch with a balancing account number + CreateJournalBatch(GenJournalBatch); + ShpfyPaymentMethodMapping."Auto-Post Jnl. Template" := GenJournalBatch."Journal Template Name"; + + // [WHEN] Auto-Post Jnl. Batch is validated + ShpfyPaymentMethodMapping.Validate("Auto-Post Jnl. Batch", GenJournalBatch.Name); + + // [THEN] Validation passes without error + LibraryAssert.AreEqual(GenJournalBatch.Name, ShpfyPaymentMethodMapping."Auto-Post Jnl. Batch", 'Auto-Post Jnl. Batch should be set'); + end; + + [Test] + procedure UnitTestAutoPostJnlBatchValidateWithoutBalAccountNo() + var + ShpfyPaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + GenJournalBatch: Record "Gen. Journal Batch"; + begin + // [SCENARIO] Auto-Post Jnl. Batch field validation fails when journal batch does not have a balancing account number + + // [GIVEN] A Gen. Journal Batch without a balancing account number + CreateJournalBatch(GenJournalBatch); + GenJournalBatch."Bal. Account No." := ''; + GenJournalBatch.Modify(); + ShpfyPaymentMethodMapping."Auto-Post Jnl. Template" := GenJournalBatch."Journal Template Name"; + + // [WHEN] Auto-Post Jnl. Batch is validated + // [THEN] Validation fails with error + asserterror ShpfyPaymentMethodMapping.Validate("Auto-Post Jnl. Batch", GenJournalBatch.Name); + end; + + [Test] + procedure UnitTestAutoPostJnlBatchValidateWithEmptyValue() + var + ShpfyPaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + begin + // [SCENARIO] Auto-Post Jnl. Batch field can be set to empty without validation error + + // [WHEN] Auto-Post Jnl. Batch is set to empty + ShpfyPaymentMethodMapping.Validate("Auto-Post Jnl. Batch", ''); + + // [THEN] Validation passes without error + LibraryAssert.AreEqual('', ShpfyPaymentMethodMapping."Auto-Post Jnl. Batch", 'Auto-Post Jnl. Batch should be empty'); + end; + + [Test] + procedure UnitTestPostSalesOrderWithAutoPostTransaction() + var + SalesHeader: Record "Sales Header"; + CustLedgerEntry: Record "Cust. Ledger Entry"; + OrderId: BigInteger; + TransactionId: BigInteger; + begin + // [SCENARIO] When a sales order with Shopify Order Id is posted and Post Automatically is true, transaction is auto-posted + + // [GIVEN] Initialized test environment + Initialize(); + + // [GIVEN] A Shopify order with transaction + OrderId := LibraryRandom.RandIntInRange(1000000, 1999999); + TransactionId := LibraryRandom.RandIntInRange(1000000, 1999999); + CreateShopifyOrder(OrderId); + CreateOrderTransaction(TransactionId, OrderId, 0, PaymentMethodMapping.Gateway, Enum::"Shpfy Transaction Type"::Sale, Item."Unit Price"); + + // [GIVEN] Payment method mapping with auto-post enabled + EnablePaymentMethodMappingAutoPost(true); + + // [GIVEN] A sales order with Shopify Order Id + CreateSalesOrder(SalesHeader, OrderId); + + // [WHEN] The sales order is posted + LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] A Cust. Ledger Entry is created + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId); + LibraryAssert.IsTrue(not CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should be created for the auto-posted transaction'); + end; + + [Test] + procedure UnitTestPostSalesOrderWithoutAutoPostTransaction() + var + SalesHeader: Record "Sales Header"; + CustLedgerEntry: Record "Cust. Ledger Entry"; + OrderId: BigInteger; + TransactionId: BigInteger; + begin + // [SCENARIO] When a sales order with Shopify Order Id is posted and Post Automatically is false, transaction is not auto-posted + + // [GIVEN] Initialized test environment + Initialize(); + + // [GIVEN] A Shopify order with transaction + OrderId := LibraryRandom.RandIntInRange(2000000, 2999999); + TransactionId := LibraryRandom.RandIntInRange(2000000, 2999999); + CreateShopifyOrder(OrderId); + CreateOrderTransaction(TransactionId, OrderId, 0, PaymentMethodMapping.Gateway, Enum::"Shpfy Transaction Type"::Sale, Item."Unit Price"); + + // [GIVEN] Payment method mapping with auto-post disabled + EnablePaymentMethodMappingAutoPost(false); + + // [GIVEN] A sales order with Shopify Order Id + CreateSalesOrder(SalesHeader, OrderId); + + // [WHEN] The sales order is posted + LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] A Cust. Ledger Entry is not created + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId); + LibraryAssert.IsTrue(CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should not be created for the non-auto-posted transaction'); + end; + + [Test] + procedure UnitTestPostSalesOrderWithAutoPostMultipleTransaction() + var + SalesHeader: Record "Sales Header"; + CustLedgerEntry: Record "Cust. Ledger Entry"; + TransactionId1: BigInteger; + TransactionId2: BigInteger; + OrderId: BigInteger; + begin + // [SCENARIO] When a sales order with Shopify Order Id is posted and Post Automatically is true, multiple transactions are auto-posted + + // [GIVEN] Initialized test environment + Initialize(); + + // [GIVEN] A Shopify order with multiple transactions + OrderId := LibraryRandom.RandIntInRange(3000000, 3999999); + CreateShopifyOrder(OrderId); + TransactionId1 := LibraryRandom.RandIntInRange(3000000, 3499999); + TransactionId2 := LibraryRandom.RandIntInRange(3500000, 3999999); + CreateOrderTransaction(TransactionId1, OrderId, 0, PaymentMethodMapping.Gateway, Enum::"Shpfy Transaction Type"::Sale, Item."Unit Price" / 2); + CreateOrderTransaction(TransactionId2, OrderId, 0, PaymentMethodMapping.Gateway, Enum::"Shpfy Transaction Type"::Sale, Item."Unit Price" / 2); + + // [GIVEN] Payment method mapping with auto-post enabled + EnablePaymentMethodMappingAutoPost(true); + + // [GIVEN] A sales order with Shopify Order Id + CreateSalesOrder(SalesHeader, OrderId); + + // [WHEN] The sales order is posted + LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] A Cust. Ledger Entry is created + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId1); + LibraryAssert.IsTrue(not CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should be created for the auto-posted transaction'); + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId2); + LibraryAssert.IsTrue(not CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should be created for the auto-posted transaction'); + end; + + [Test] + procedure UnitTestPostSalesOrderWithMultipleTransaction() + var + SalesHeader: Record "Sales Header"; + CustLedgerEntry: Record "Cust. Ledger Entry"; + TransactionId1: BigInteger; + TransactionId2: BigInteger; + OrderId: BigInteger; + begin + // [SCENARIO] When a sales order with Shopify Order Id is posted and Post Automatically is true, only transactions linked to auto post Payment Method Mapping are auto-posted + + // [GIVEN] Initialized test environment + Initialize(); + + // [GIVEN] A Shopify order + OrderId := LibraryRandom.RandIntInRange(4000000, 4999999); + CreateShopifyOrder(OrderId); + + // [GIVEN] Payment method mapping with auto-post enabled + EnablePaymentMethodMappingAutoPost(true); + + // [GIVEN] Transaction linked to auto post Payment Method Mapping + TransactionId1 := LibraryRandom.RandIntInRange(4000000, 4499999); + CreateOrderTransaction(TransactionId1, OrderId, 0, PaymentMethodMapping.Gateway, Enum::"Shpfy Transaction Type"::Sale, Item."Unit Price" / 2); + + // [GIVEN] Transaction not linked to auto post Payment Method Mapping + TransactionId2 := LibraryRandom.RandIntInRange(4500000, 4999999); + CreateOrderTransaction(TransactionId2, OrderId, 0, 'auto post disabled', Enum::"Shpfy Transaction Type"::Sale, Item."Unit Price" / 2); + + // [GIVEN] A sales order with Shopify Order Id + CreateSalesOrder(SalesHeader, OrderId); + + // [WHEN] The sales order is posted + LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] A Cust. Ledger Entry is created + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId1); + LibraryAssert.IsTrue(not CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should be created for the auto-posted transaction'); + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId2); + LibraryAssert.IsTrue(CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should not be created for the non-auto-posted transaction'); + end; + + [Test] + procedure UnitTestPostCreditMemoWithAutoPostTransaction() + var + SalesHeader: Record "Sales Header"; + CustLedgerEntry: Record "Cust. Ledger Entry"; + TransactionId: BigInteger; + RefundId: BigInteger; + OrderId: BigInteger; + begin + // [SCENARIO] When a credit memo with Shopify Refund Id is posted and Post Automatically is true, refund transaction is auto-posted + + // [GIVEN] Initialized test environment + Initialize(); + + // [GIVEN] A refund with transaction + RefundId := LibraryRandom.RandIntInRange(5000000, 5999999); + OrderId := LibraryRandom.RandIntInRange(5000000, 5999999); + CreateShopifyOrder(OrderId); + CreateRefund(RefundId, OrderId); + TransactionId := LibraryRandom.RandIntInRange(5000000, 5999999); + CreateOrderTransaction(TransactionId, OrderId, RefundId, PaymentMethodMapping.Gateway, Enum::"Shpfy Transaction Type"::Refund, Item."Unit Price"); + + // [GIVEN] Payment method mapping with auto-post enabled + EnablePaymentMethodMappingAutoPost(true); + + // [GIVEN] A sales credit memo with Shopify Refund Id + CreateCreditMemo(SalesHeader, RefundId); + + // [WHEN] The credit memo is posted + LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] A Cust. Ledger Entry is created + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId); + LibraryAssert.IsTrue(not CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should be created for the auto-posted refund transaction'); + end; + + [Test] + procedure UnitTestPostSalesOrderWithUnsuccessfulTransactionPost() + var + SalesHeader: Record "Sales Header"; + SalesInvoiceHeader: Record "Sales Invoice Header"; + CustLedgerEntry: Record "Cust. Ledger Entry"; + OrderId: BigInteger; + TransactionId: BigInteger; + begin + // [SCENARIO] When a sales order with Shopify Order Id is posted and Post Automatically is true, transaction post is unsuccessful, but sales order posting is completed + + // [GIVEN] Initialized test environment + Initialize(); + + // [GIVEN] A Shopify order with transaction + OrderId := LibraryRandom.RandIntInRange(6000000, 6999999); + TransactionId := LibraryRandom.RandIntInRange(6000000, 6999999); + CreateShopifyOrder(OrderId); + CreateOrderTransaction(TransactionId, OrderId, 0, PaymentMethodMapping.Gateway, Enum::"Shpfy Transaction Type"::Sale, Item."Unit Price"); + + // [GIVEN] Payment method mapping with auto-post enabled + EnablePaymentMethodMappingAutoPost(true); + + // [GIVEN] A sales order with Shopify Order Id + CreateSalesOrder(SalesHeader, OrderId); + + // [GIVEN] Transaction auto post No. Series is closed + OpenNoSeriesLine(false); + + // [WHEN] The sales order is posted + asserterror LibrarySales.PostSalesDocument(SalesHeader, true, true); + + // [THEN] Sales order posting is completed + SalesInvoiceHeader.SetRange("Shpfy Order Id", OrderId); + LibraryAssert.IsTrue(not SalesInvoiceHeader.IsEmpty(), 'Posted sales invoice should exist'); + + // [THEN] A Cust. Ledger Entry is not created + CustLedgerEntry.SetRange("Shpfy Transaction Id", TransactionId); + LibraryAssert.IsTrue(CustLedgerEntry.IsEmpty(), 'Cust. Ledger Entry should not be created for the auto-posted transaction'); + + OpenNoSeriesLine(true); + end; + + local procedure Initialize() + var + LibraryERMCountryData: Codeunit "Library - ERM Country Data"; + CommunicationMgt: Codeunit "Shpfy Communication Mgt."; + begin + if IsInitialized then + exit; + + Codeunit.Run(Codeunit::"Shpfy Initialize Test"); + + LibraryERMCountryData.CreateVATData(); + LibraryERMCountryData.UpdateGeneralPostingSetup(); + CreateItem(); + LibrarySales.CreateCustomer(Customer); + + Shop := CommunicationMgt.GetShopRecord(); + + CreatePaymentMethodMapping(); + + IsInitialized := true; + end; + + local procedure CreateItem() + var + LibraryInventory: Codeunit "Library - Inventory"; + Amount: Decimal; + begin + Amount := LibraryRandom.RandIntInRange(10000, 99999); + LibraryInventory.CreateItem(Item); + Item.Validate("Unit Price", Amount); + Item.Validate("Last Direct Cost", Amount); + Item.Modify(true); + end; + + local procedure CreateShopifyOrder(OrderId: BigInteger) + var + ShpfyOrderHeader: Record "Shpfy Order Header"; + begin + ShpfyOrderHeader.Init(); + ShpfyOrderHeader."Shopify Order Id" := OrderId; + ShpfyOrderHeader.Processed := true; + ShpfyOrderHeader.Insert(); + end; + + local procedure CreateOrderTransaction(TransactionId: BigInteger; OrderId: BigInteger; RefundId: BigInteger; Gateway: Text[30]; TransactionType: Enum "Shpfy Transaction Type"; Amount: Decimal) + var + OrderTransaction: Record "Shpfy Order Transaction"; + begin + OrderTransaction.Init(); + OrderTransaction."Shopify Transaction Id" := TransactionId; + OrderTransaction."Shopify Order Id" := OrderId; + OrderTransaction."Refund Id" := RefundId; + OrderTransaction.Shop := Shop.Code; + OrderTransaction.Gateway := Gateway; + OrderTransaction.Type := TransactionType; + OrderTransaction.Status := OrderTransaction.Status::Success; + OrderTransaction.Amount := Amount; + OrderTransaction.Used := false; + OrderTransaction.Insert(); + end; + + local procedure CreateSalesOrder(var SalesHeader: Record "Sales Header"; OrderId: BigInteger) + var + SalesLine: Record "Sales Line"; + begin + LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Invoice, Customer."No."); + SalesHeader."Shpfy Order Id" := OrderId; + SalesHeader.Modify(); + LibrarySales.CreateSalesLine(SalesLine, SalesHeader, SalesLine.Type::Item, Item."No.", 1); + end; + + local procedure CreateCreditMemo(var SalesHeader: Record "Sales Header"; RefundId: BigInteger) + var + SalesLine: Record "Sales Line"; + begin + LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::"Credit Memo", Customer."No."); + SalesHeader."Shpfy Refund Id" := RefundId; + SalesHeader.Modify(); + LibrarySales.CreateSalesLine(SalesLine, SalesHeader, SalesLine.Type::Item, Item."No.", 1); + end; + + local procedure CreateRefund(RefundId: BigInteger; OrderId: BigInteger) + var + RefundHeader: Record "Shpfy Refund Header"; + begin + RefundHeader.Init(); + RefundHeader."Refund Id" := RefundId; + RefundHeader."Order Id" := OrderId; + RefundHeader.Insert(); + end; + + local procedure EnablePaymentMethodMappingAutoPost(AutoPost: Boolean) + begin + PaymentMethodMapping."Post Automatically" := AutoPost; + PaymentMethodMapping.Modify(); + end; + + local procedure CreatePaymentMethodMapping() + var + GenJournalBatch: Record "Gen. Journal Batch"; + begin + PaymentMethodMapping.Init(); + PaymentMethodMapping."Shop Code" := Shop.Code; + PaymentMethodMapping.Gateway := CopyStr(LibraryRandom.RandText(30), 1, MaxStrLen(PaymentMethodMapping.Gateway)); + PaymentMethodMapping."Post Automatically" := true; + CreateJournalBatch(GenJournalBatch); + PaymentMethodMapping."Auto-Post Jnl. Template" := GenJournalBatch."Journal Template Name"; + PaymentMethodMapping."Auto-Post Jnl. Batch" := GenJournalBatch.Name; + PaymentMethodMapping.Insert(); + end; + + local procedure CreateJournalBatch(var GenJournalBatch: Record "Gen. Journal Batch") + var + GenJournalTemplate: Record "Gen. Journal Template"; + begin + GenJournalTemplate.Name := CopyStr(LibraryRandom.RandText(10), 1, MaxStrLen(GenJournalTemplate.Name)); + GenJournalTemplate.Type := GenJournalTemplate.Type::"Cash Receipts"; + GenJournalTemplate.Insert(); + + GenJournalBatch."Journal Template Name" := GenJournalTemplate.Name; + GenJournalBatch.Name := CopyStr(LibraryRandom.RandText(10), 1, MaxStrLen(GenJournalBatch.Name)); + GenJournalBatch."Bal. Account Type" := GenJournalBatch."Bal. Account Type"::"G/L Account"; + GenJournalBatch."Bal. Account No." := CreateGLAccount(); + GenJournalBatch."No. Series" := CopyStr(LibraryRandom.RandText(20), 1, MaxStrLen(GenJournalBatch."No. Series")); + CreateNoSeries(GenJournalBatch."No. Series"); + GenJournalBatch.Insert(); + end; + + local procedure CreateGLAccount(): Code[20] + var + GLAccount: Record "G/L Account"; + VATPostingSetup: Record "VAT Posting Setup"; + LibraryERM: Codeunit "Library - ERM"; + ShpfyInitializeTest: Codeunit "Shpfy Initialize Test"; + begin + LibraryERM.CreateVATPostingSetupWithAccounts(VATPostingSetup, VATPostingSetup."VAT Calculation Type"::"Normal VAT", LibraryRandom.RandDecInDecimalRange(10, 25, 0)); + GLAccount.Get(LibraryERM.CreateGLAccountWithVATPostingSetup(VATPostingSetup, Enum::"General Posting Type"::Sale)); + GLAccount."Direct Posting" := true; + + ShpfyInitializeTest.CreateVATPostingSetup(Shop."VAT Bus. Posting Group", GLAccount."VAT Prod. Posting Group"); + + GLAccount.Modify(false); + exit(GLAccount."No."); + end; + + local procedure CreateNoSeries(Code: Code[20]) + var + NoSeries: Record "No. Series"; + NoSeriesLine: Record "No. Series Line"; + begin + if not NoSeries.Get(Code) then begin + NoSeries.Code := Code; + NoSeries."Default Nos." := true; + NoSeries.Insert(); + NoSeriesLine."Series Code" := Code; + NoSeriesLine."Starting No." := Format(LibraryRandom.RandIntInRange(10000, 39999)); + NoSeriesLine."Increment-by No." := 1; + NoSeriesLine."Ending No." := Format(LibraryRandom.RandIntInRange(50000, 99999)); + NoSeriesLine.Open := true; + NoSeriesLine.Insert(); + end; + end; + + local procedure OpenNoSeriesLine(Open: Boolean) + var + GenJournalBatch: Record "Gen. Journal Batch"; + NoSeriesLine: Record "No. Series Line"; + begin + GenJournalBatch.Get(PaymentMethodMapping."Auto-Post Jnl. Template", PaymentMethodMapping."Auto-Post Jnl. Batch"); + NoSeriesLine.SetRange("Series Code", GenJournalBatch."No. Series"); + if NoSeriesLine.FindFirst() then begin + NoSeriesLine.Open := Open; + NoSeriesLine.Modify(); + end; + end; +} From 5fdf9c86ed97062f10d2705ed597f24cb44920a1 Mon Sep 17 00:00:00 2001 From: Tautvydas Labarauskas Date: Tue, 10 Feb 2026 15:35:00 +0200 Subject: [PATCH 2/2] PR comments fixes: Changed to skipping posting dialogs by using manual event bindings Adjustments to removing old gen journal lines Additional features: In "Shpfy Transactions" page added action to filter postable transactions Newly created objects added to "Shpfy - Objects" permission set --- .../ShpfyObjects.PermissionSet.al | 3 + .../Codeunits/ShpfyAutoGenJnlPost.Codeunit.al | 29 +++++++ .../ShpfyAutoPostTransactions.Codeunit.al | 16 +--- .../ShpfySuggestPayments.Codeunit.al | 20 ----- .../Pages/ShpfyFilterTransactions.Page.al | 67 +++++++++++++++ .../Pages/ShpfyTransactions.Page.al | 81 +++++++++++++++++++ .../Reports/ShpfySuggestPayments.Report.al | 14 +++- .../Tables/ShpfyPaymentMethodMapping.Table.al | 4 +- .../ShpfyAutoPostTransTest.Codeunit.al | 12 +++ 9 files changed, 210 insertions(+), 36 deletions(-) create mode 100644 src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoGenJnlPost.Codeunit.al create mode 100644 src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyFilterTransactions.Page.al diff --git a/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al b/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al index 52af1b5cba..aa065d2006 100644 --- a/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al +++ b/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al @@ -103,6 +103,8 @@ permissionset 30104 "Shpfy - Objects" report "Shpfy Translator" = X, codeunit "Company Details Checklist Item" = X, codeunit "Shpfy Authentication Mgt." = X, + codeunit "Shpfy Auto Gen. Jnl.-Post" = X, + codeunit "Shpfy Auto Post Transactions" = X, codeunit "Shpfy Background Syncs" = X, codeunit "Shpfy Balance Today" = X, codeunit "Shpfy Base64" = X, @@ -441,6 +443,7 @@ permissionset 30104 "Shpfy - Objects" page "Shpfy Customers" = X, page "Shpfy Data Capture List" = X, page "Shpfy Disputes" = X, + page "Shpfy Filter Transactions" = X, page "Shpfy Fulfillment Order Card" = X, page "Shpfy Fulfillment Order Lines" = X, page "Shpfy Fulfillment Orders" = X, diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoGenJnlPost.Codeunit.al b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoGenJnlPost.Codeunit.al new file mode 100644 index 0000000000..91cebf76ae --- /dev/null +++ b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoGenJnlPost.Codeunit.al @@ -0,0 +1,29 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.Integration.Shopify; + +using Microsoft.Finance.GeneralLedger.Journal; +using Microsoft.Finance.GeneralLedger.Posting; + +/// +/// Codeunit Shpfy Auto Gen. Jnl.-Post (ID 30422). +/// +codeunit 30422 "Shpfy Auto Gen. Jnl.-Post" +{ + EventSubscriberInstance = Manual; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post", 'OnBeforeCode', '', false, false)] + local procedure OnBeforeCode(var GenJournalLine: Record "Gen. Journal Line"; var HideDialog: Boolean) + begin + HideDialog := true; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post", 'OnBeforeShowPostResultMessage', '', false, false)] + local procedure OnBeforeShowPostResultMessage(var GenJnlLine: Record "Gen. Journal Line"; TempJnlBatchName: Code[10]; var IsHandled: Boolean) + begin + IsHandled := true; + end; +} diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al index 97cea6d1d5..ac8dbeb6d1 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfyAutoPostTransactions.Codeunit.al @@ -89,25 +89,17 @@ codeunit 30236 "Shpfy Auto Post Transactions" GenJournalLine: Record "Gen. Journal Line"; PaymentMethodMapping: Record "Shpfy Payment Method Mapping"; SuggestPayments: Report "Shpfy Suggest Payments"; + AutoGenJnlPost: Codeunit "Shpfy Auto Gen. Jnl.-Post"; begin PaymentMethodMapping.Get(OrderTransaction.Shop, OrderTransaction.Gateway, OrderTransaction."Credit Card Company"); SuggestPayments.SetJournalParameters(PaymentMethodMapping."Auto-Post Jnl. Template", PaymentMethodMapping."Auto-Post Jnl. Batch", PostingDate); SuggestPayments.GetOrderTransactions(OrderTransaction); - SuggestPayments.CreateGeneralJournalLines(true); + SuggestPayments.CreateGeneralJournalLines(); GenJournalLine.SetRange("Shpfy Transaction Id", OrderTransaction."Shopify Transaction Id"); - GenJournalLine.SetRange("Automatically Posted", true); if GenJournalLine.FindFirst() then begin + BindSubscription(AutoGenJnlPost); GenJournalLine.SendToPosting(Codeunit::"Gen. Jnl.-Post"); - RemoveNotPostedLines(OrderTransaction."Shopify Transaction Id"); + UnbindSubscription(AutoGenJnlPost); end; end; - - local procedure RemoveNotPostedLines(ShopifyTransactionId: BigInteger) - var - GenJournalLine: Record "Gen. Journal Line"; - begin - GenJournalLine.SetRange("Shpfy Transaction Id", ShopifyTransactionId); - if not GenJournalLine.IsEmpty() then - GenJournalLine.DeleteAll(true); - end; } \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al index 3773046616..c4c2aa31cb 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Codeunits/ShpfySuggestPayments.Codeunit.al @@ -29,24 +29,4 @@ codeunit 30311 "Shpfy Suggest Payments" begin NewCustLedgerEntry."Shpfy Transaction Id" := 0; end; - - [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post", 'OnBeforeCode', '', false, false)] - local procedure OnBeforeCode(var GenJournalLine: Record "Gen. Journal Line"; var HideDialog: Boolean) - begin - if GenJournalLine."Automatically Posted" then - HideDialog := true; - end; - - [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post", 'OnBeforeShowPostResultMessage', '', false, false)] - local procedure OnBeforeShowPostResultMessage(var GenJnlLine: Record "Gen. Journal Line"; TempJnlBatchName: Code[10]; var IsHandled: Boolean) - var - AutomaticallyPosted: Boolean; - begin - if GenJnlLine.GetFilter("Automatically Posted") = '' then - exit; - - Evaluate(AutomaticallyPosted, GenJnlLine.GetFilter("Automatically Posted")); - if AutomaticallyPosted then - IsHandled := true; - end; } diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyFilterTransactions.Page.al b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyFilterTransactions.Page.al new file mode 100644 index 0000000000..be7153dba0 --- /dev/null +++ b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyFilterTransactions.Page.al @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.Integration.Shopify; + +/// +/// Page Shpfy Filter Transactions (ID 30176). +/// +page 30176 "Shpfy Filter Transactions" +{ + Caption = 'Filter Postable Transactions'; + PageType = StandardDialog; + ApplicationArea = All; + + layout + { + area(Content) + { + field(Gateway; Gateway) + { + Caption = 'Gateway'; + ToolTip = 'Specifies the transaction gateway to filter transactions by. Leave blank to include all gateways.'; + Editable = false; + + trigger OnAssistEdit() + var + PaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + begin + PaymentMethodMapping.SetRange("Post Automatically", true); + if Page.RunModal(0, PaymentMethodMapping) = Action::LookupOK then begin + ShopCode := PaymentMethodMapping."Shop Code"; + Gateway := PaymentMethodMapping.Gateway; + CreditCardCompany := PaymentMethodMapping."Credit Card Company"; + end; + end; + } + field(StartDate; StartDate) + { + Caption = 'Start Date'; + ToolTip = 'Specifies the earliest transaction creation date to include in the filter. Leave blank to include all transactions from the beginning.'; + } + field(EndDate; EndDate) + { + Caption = 'End Date'; + ToolTip = 'Specifies the latest transaction creation date to include in the filter. Leave blank to include all transactions.'; + } + } + } + + var + ShopCode: Code[20]; + Gateway: Text[30]; + CreditCardCompany: Text[50]; + StartDate: Date; + EndDate: Date; + + internal procedure GetParameters(var NewShopCode: Code[20]; var NewGateway: Text[30]; var NewCreditCardCompany: Text[50]; var NewStartDate: DateTime; var NewEndDate: DateTime) + begin + NewShopCode := ShopCode; + NewGateway := Gateway; + NewCreditCardCompany := CreditCardCompany; + NewStartDate := CreateDateTime(StartDate, 0T); + NewEndDate := CreateDateTime(EndDate, 0T); + end; +} \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al index 7960e9908e..f056e31515 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Pages/ShpfyTransactions.Page.al @@ -199,6 +199,31 @@ page 30134 "Shpfy Transactions" SuggestPayments.Run(); end; } + action(ShowPostableTransactions) + { + ApplicationArea = All; + Caption = 'Filter Postable Transactions'; + Image = FilterLines; + ToolTip = 'Show transactions that need to be posted. Filters by auto-post enabled, not yet posted, and linked to a posted invoice. Optionally filter by gateway and date range.'; + + trigger OnAction() + begin + FilterPostableTransactions() + end; + } + action(ClearFilter) + { + ApplicationArea = All; + Caption = 'Clear Filter'; + Image = ClearFilter; + ToolTip = 'Remove the postable transactions filter and show all transactions.'; + + trigger OnAction() + begin + Rec.ClearMarks(); + Rec.MarkedOnly(false); + end; + } } area(Promoted) { @@ -215,6 +240,8 @@ page 30134 "Shpfy Transactions" { Caption = 'Related'; actionref(CustLedgerEntries_Promoted; CustLedgerEntries) { } + actionref(ShowPostableTransactions_Promoted; ShowPostableTransactions) { } + actionref(ClearFilter_Promoted; ClearFilter) { } } } } @@ -237,4 +264,58 @@ page 30134 "Shpfy Transactions" PresentmentCurrencyVisible := OrderHeader.IsPresentmentCurrencyOrder(); end; + + local procedure FilterPostableTransactions() + var + PaymentMethodMapping: Record "Shpfy Payment Method Mapping"; + FilterTransactions: Page "Shpfy Filter Transactions"; + FilterShopCode: Code[20]; + FilterCreditCardCompany: Text[50]; + FilterGateway: Text[30]; + FilterStartDate: DateTime; + FilterEndDate: DateTime; + begin + if not (FilterTransactions.RunModal() = Action::OK) then + exit; + + FilterTransactions.GetParameters(FilterShopCode, FilterGateway, FilterCreditCardCompany, FilterStartDate, FilterEndDate); + if (FilterStartDate <> 0DT) or (FilterEndDate <> 0DT) then + if FilterEndDate <> 0DT then + Rec.SetRange("Created At", FilterStartDate, FilterEndDate) + else + Rec.SetRange("Created At", FilterStartDate, CreateDateTime(DMY2Date(31, 12, 9999), 0T)); + Rec.SetRange(Used, false); + Rec.SetFilter("Posted Invoice No.", '<>%1', ''); + + Rec.ClearMarks(); + Rec.MarkedOnly(false); + if FilterGateway <> '' then + MarkPostableTransactions(FilterShopCode, FilterCreditCardCompany, FilterGateway) + else begin + PaymentMethodMapping.SetRange("Post Automatically", true); + if PaymentMethodMapping.FindSet() then + repeat + MarkPostableTransactions(PaymentMethodMapping."Shop Code", PaymentMethodMapping."Credit Card Company", PaymentMethodMapping.Gateway); + until PaymentMethodMapping.Next() = 0; + end; + + Rec.MarkedOnly(true); + Rec.SetRange(Shop); + Rec.SetRange(Gateway); + Rec.SetRange("Credit Card Company"); + Rec.SetRange("Created At"); + Rec.SetRange(Used); + Rec.SetRange("Posted Invoice No."); + end; + + local procedure MarkPostableTransactions(FilterShopCode: Code[20]; FilterCreditCardCompany: Text[50]; FilterGateway: Text[30]) + begin + Rec.SetRange(Shop, FilterShopCode); + Rec.SetRange(Gateway, FilterGateway); + Rec.SetRange("Credit Card Company", FilterCreditCardCompany); + if Rec.FindSet() then + repeat + Rec.Mark(true); + until Rec.Next() = 0; + end; } \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al b/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al index a3754cd106..6b6fd80d28 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Reports/ShpfySuggestPayments.Report.al @@ -45,7 +45,7 @@ report 30118 "Shpfy Suggest Payments" trigger OnPostDataItem() begin - CreateGeneralJournalLines(false); + CreateGeneralJournalLines(); end; } } @@ -378,7 +378,7 @@ report 30118 "Shpfy Suggest Payments" TempSuggestPayment.Insert(); end; - internal procedure CreateGeneralJournalLines(AutomaticallyPosted: Boolean) + internal procedure CreateGeneralJournalLines() var NoSeriesBatch: Codeunit "No. Series - Batch"; LastLineNo: Integer; @@ -390,6 +390,8 @@ report 30118 "Shpfy Suggest Payments" if GenJournalLine.FindLast() then LastLineNo := GenJournalLine."Line No."; + RemoveGenJournalLines(TempSuggestPayment."Shpfy Transaction Id"); + if OrderNoInDescription then TempSuggestPayment.SetAutoCalcFields("Shpfy Order No."); if TempSuggestPayment.FindSet() then @@ -433,7 +435,6 @@ report 30118 "Shpfy Suggest Payments" GenJournalLine.Validate("Applies-to Doc. No.", TempSuggestPayment."Credit Memo No."); end; GenJournalLine."Shpfy Transaction Id" := TempSuggestPayment."Shpfy Transaction Id"; - GenJournalLine."Automatically Posted" := AutomaticallyPosted; GenJournalLine.SetSuppressCommit(false); GenJournalLine.Insert(true); @@ -442,6 +443,13 @@ report 30118 "Shpfy Suggest Payments" until TempSuggestPayment.Next() = 0; end; + local procedure RemoveGenJournalLines(ShopifyTransactionId: BigInteger) + begin + GenJournalLine.SetRange("Shpfy Transaction Id", ShopifyTransactionId); + if not GenJournalLine.IsEmpty() then + GenJournalLine.DeleteAll(true); + end; + local procedure SetGenJournallLineDimension(CustomerLedgerEntryDimensionSetId: Integer) var DimensionManagement: Codeunit DimensionManagement; diff --git a/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al b/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al index 8ecbd470cb..c57b7f5e7d 100644 --- a/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al +++ b/src/Apps/W1/Shopify/App/src/Transactions/Tables/ShpfyPaymentMethodMapping.Table.al @@ -15,6 +15,8 @@ table 30134 "Shpfy Payment Method Mapping" { Access = Internal; Caption = 'Shopify Payment Method'; + DrillDownPageID = "Shpfy Payment Methods Mapping"; + LookupPageID = "Shpfy Payment Methods Mapping"; DataClassification = CustomerContent; fields @@ -69,7 +71,7 @@ table 30134 "Shpfy Payment Method Mapping" { Caption = 'Auto-Post Journal Template'; DataClassification = CustomerContent; - TableRelation = "Gen. Journal Template"; + TableRelation = "Gen. Journal Template" where(Type = const("Cash Receipts")); } field(9; "Auto-Post Jnl. Batch"; Code[10]) { diff --git a/src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al b/src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al index 1e563c762b..4688231372 100644 --- a/src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al +++ b/src/Apps/W1/Shopify/Test/Payments/ShpfyAutoPostTransTest.Codeunit.al @@ -7,6 +7,7 @@ namespace Microsoft.Integration.Shopify.Test; using Microsoft.Finance.GeneralLedger.Account; using Microsoft.Finance.GeneralLedger.Journal; +using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.Finance.VAT.Setup; using Microsoft.Foundation.Enums; using Microsoft.Foundation.NoSeries; @@ -332,9 +333,20 @@ codeunit 139614 "Shpfy Auto Post Trans. Test" CreatePaymentMethodMapping(); + DisablePostWithJobQueue(); + IsInitialized := true; end; + local procedure DisablePostWithJobQueue() + var + GeneralLedgerSetup: Record "General Ledger Setup"; + begin + GeneralLedgerSetup.Get(); + GeneralLedgerSetup."Post with Job Queue" := false; + GeneralLedgerSetup.Modify(); + end; + local procedure CreateItem() var LibraryInventory: Codeunit "Library - Inventory";