Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,32 @@ codeunit 30262 "Shpfy Document Link Mgt."
end;

[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])
local procedure OnAfterSalesPosting(var SalesHeader: Record "Sales Header"; PreviewMode: Boolean; CommitIsSuppressed: Boolean; SalesShptHdrNo: Code[20]; SalesInvHdrNo: Code[20]; RetRcpHdrNo: Code[20]; SalesCrMemoHdrNo: Code[20])
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
var
SalesShipmentHeader: Record "Sales Shipment Header";
SalesInvoiceLine: Record "Sales Invoice Line";
SalesShipments: List of [Code[20]];
ShpfyAutoPostTransactions: Codeunit "Shpfy Auto Post Transactions";
begin
if SalesHeader.IsTemporary() then
exit;

if PreviewMode then
exit;

CreateDocLinksToBCDocs(SalesHeader, SalesShptHdrNo, SalesInvHdrNo, RetRcpHdrNo, SalesCrMemoHdrNo);

// When the caller suppresses commit it owns the transaction and expects atomicity, so skip the
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
// best-effort automatic payment posting (which would otherwise commit or risk the caller's work).
if CommitIsSuppressed then
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
exit;

ShpfyAutoPostTransactions.AutoPostTransactions(SalesInvHdrNo, SalesCrMemoHdrNo);
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
end;

local procedure CreateDocLinksToBCDocs(var SalesHeader: Record "Sales Header"; SalesShptHdrNo: Code[20]; SalesInvHdrNo: Code[20]; RetRcpHdrNo: Code[20]; SalesCrMemoHdrNo: Code[20])
Comment thread
onbuyuka marked this conversation as resolved.
var
SalesShipmentHeader: Record "Sales Shipment Header";
SalesInvoiceLine: Record "Sales Invoice Line";
SalesShipments: List of [Code[20]];
begin
DocLinkToBCDoc.SetRange("Document Type", ShpfyBCDocumentTypeConvert.Convert(SalesHeader."Document Type"));
DocLinkToBCDoc.SetRange("Document No.", SalesHeader."No.");
DocLinkToBCDoc.SetCurrentKey("Document Type", "Document No.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -301,6 +303,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace Microsoft.Integration.Shopify;

using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Finance.GeneralLedger.Posting;

/// <summary>
/// Codeunit Shpfy Auto Gen. Jnl.-Post (ID 30422).
/// Creates a dedicated, single-use journal batch (cloned from the configured one) and builds the general
/// journal line(s) for a single Shopify order/refund payment transaction into it. It is invoked through
/// Codeunit.Run so that any failure while creating the batch or building lines is trapped and rolled back
/// without leaving a batch or line behind. While bound, it also pre-confirms the "posting after working
/// date" prompt so the automatic posting stays non-interactive.
/// </summary>
codeunit 30422 "Shpfy Auto Gen. Jnl.-Post"
{
Access = Internal;
EventSubscriberInstance = Manual;
TableNo = "Shpfy Order Transaction";
Permissions = tabledata "Gen. Journal Batch" = rim;

trigger OnRun()
begin
CreateBatchAndBuildLines(Rec);
end;

var
PaymentMethodMapping: Record "Shpfy Payment Method Mapping";
PostingDate: Date;
IsolatedTemplateName: Code[10];
IsolatedBatchName: Code[10];

internal procedure SetParameters(NewPaymentMethodMapping: Record "Shpfy Payment Method Mapping"; NewPostingDate: Date)
begin
PaymentMethodMapping := NewPaymentMethodMapping;
PostingDate := NewPostingDate;
Clear(IsolatedTemplateName);
Clear(IsolatedBatchName);
end;

internal procedure GetIsolatedBatch(var NewTemplateName: Code[10]; var NewBatchName: Code[10])
begin
NewTemplateName := IsolatedTemplateName;
NewBatchName := IsolatedBatchName;
end;

local procedure CreateBatchAndBuildLines(var OrderTransaction: Record "Shpfy Order Transaction")
var
SuggestPayments: Report "Shpfy Suggest Payments";
begin
CreateIsolatedBatch();
SuggestPayments.SetJournalParameters(IsolatedTemplateName, IsolatedBatchName, PostingDate);
SuggestPayments.GetOrderTransactions(OrderTransaction);
SuggestPayments.CreateGeneralJournalLines();
end;

local procedure CreateIsolatedBatch()
var
ConfiguredBatch: Record "Gen. Journal Batch";
IsolatedBatch: Record "Gen. Journal Batch";
begin
ConfiguredBatch.Get(PaymentMethodMapping."Auto-Post Jnl. Template", PaymentMethodMapping."Auto-Post Jnl. Batch");
IsolatedBatch := ConfiguredBatch;
IsolatedBatch.Name := GetUniqueBatchName(ConfiguredBatch."Journal Template Name");
IsolatedBatch.Insert(true);
IsolatedTemplateName := IsolatedBatch."Journal Template Name";
IsolatedBatchName := IsolatedBatch.Name;
end;

local procedure GetUniqueBatchName(TemplateName: Code[10]): Code[10]
var
ExistingBatch: Record "Gen. Journal Batch";
CandidateName: Code[10];
begin
repeat
CandidateName := CopyStr('SHPFY' + CopyStr(DelChr(Format(CreateGuid()), '=', '{}-'), 1, 5), 1, 10);
until not ExistingBatch.Get(TemplateName, CandidateName);
exit(CandidateName);
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Batch", 'OnBeforeCheckLine', '', false, false)]
local procedure PreconfirmWorkingDateOnBeforeCheckLine(var PostingAfterWorkingDateConfirmed: Boolean)
begin
PostingAfterWorkingDateConfirmed := true;
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// ------------------------------------------------------------------------------------------------
// 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.Document;
using Microsoft.Sales.History;

/// <summary>
/// Codeunit Shpfy Auto Post Transactions (ID 30236).
/// Automatically posts Shopify order and refund payment transactions as general journal lines when
/// the related sales invoice or credit memo is posted, provided the transaction's payment method
/// mapping is configured for automatic posting. Posting is synchronous and best-effort: a failure to
/// post a payment is logged as a skipped record and never blocks or reverses the document posting.
/// Each transaction is posted through a dedicated, single-use journal batch so that only the generated
/// lines are posted and pre-existing lines in the configured batch are never touched.
/// </summary>
codeunit 30236 "Shpfy Auto Post Transactions"
{
Access = Internal;
Permissions = tabledata "Gen. Journal Batch" = rimd,
tabledata "Gen. Journal Line" = rimd;

internal procedure AutoPostTransactions(SalesInvoiceHeaderNo: Code[20]; SalesCrMemoHeaderNo: Code[20])
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
begin
if SalesInvoiceHeaderNo <> '' then
PostOrderTransactions(SalesInvoiceHeaderNo);
if SalesCrMemoHeaderNo <> '' then
PostRefundTransactions(SalesCrMemoHeaderNo);
end;

local procedure PostOrderTransactions(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;

// Defer until all sales documents for the order are posted, so a partial invoice can't consume the whole transaction.
if OpenSalesDocumentExistsForOrder(SalesInvoiceHeader."Shpfy Order Id") then
exit;

OrderTransaction.SetRange("Shopify Order Id", SalesInvoiceHeader."Shpfy Order Id");
OrderTransaction.SetFilter(Type, '%1|%2', OrderTransaction.Type::Capture, OrderTransaction.Type::Sale);
Comment thread
onbuyuka marked this conversation as resolved.
PostTransactions(OrderTransaction, SalesInvoiceHeader."Posting Date");
end;

local procedure PostRefundTransactions(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;

// Defer until all credit memos for the refund are posted.
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
if OpenSalesDocumentExistsForRefund(SalesCrMemoHeader."Shpfy Refund Id") then
exit;

OrderTransaction.SetRange("Refund Id", SalesCrMemoHeader."Shpfy Refund Id");
OrderTransaction.SetRange(Type, OrderTransaction.Type::Refund);
PostTransactions(OrderTransaction, SalesCrMemoHeader."Posting Date");
end;

local procedure OpenSalesDocumentExistsForOrder(ShopifyOrderId: BigInteger): Boolean
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange("Shpfy Order Id", ShopifyOrderId);
exit(not SalesHeader.IsEmpty());
end;

local procedure OpenSalesDocumentExistsForRefund(ShopifyRefundId: BigInteger): Boolean
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange("Shpfy Refund Id", ShopifyRefundId);
exit(not SalesHeader.IsEmpty());
end;

local procedure PostTransactions(var OrderTransaction: Record "Shpfy Order Transaction"; PostingDate: Date)
var
PaymentMethodMapping: Record "Shpfy Payment Method Mapping";
AutoGenJnlPost: Codeunit "Shpfy Auto Gen. Jnl.-Post";
begin
OrderTransaction.SetRange(Status, OrderTransaction.Status::Success);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Performance}$

PostTransactions() scans the wide "Shpfy Order Transaction" table with FindSet() but does not call SetLoadFields(...) before the loop, even though the eligibility pass initially reads only a small subset of fields (Shop, Gateway, Credit Card Company, Type, Status, Refund Id, Shopify Order Id) to decide whether a row should be posted. Partial loading for the scan, with an explicit refetch or extra load only for rows that are actually posted, would reduce payload on larger transaction sets.

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.35.4

OrderTransaction.SetRange(Used, false);
if not OrderTransaction.FindSet() then
exit;

// Bind once per document; unbind after the loop is safe because PostTransaction never raises.
BindSubscription(AutoGenJnlPost);
repeat
Comment thread
onbuyuka marked this conversation as resolved.
if GetAutoPostMapping(OrderTransaction, PaymentMethodMapping) then
PostTransaction(AutoGenJnlPost, OrderTransaction, PaymentMethodMapping, PostingDate);
until OrderTransaction.Next() = 0;
UnbindSubscription(AutoGenJnlPost);
end;

local procedure GetAutoPostMapping(OrderTransaction: Record "Shpfy Order Transaction"; var PaymentMethodMapping: Record "Shpfy Payment Method Mapping"): Boolean
begin
if not PaymentMethodMapping.Get(OrderTransaction.Shop, OrderTransaction.Gateway, OrderTransaction."Credit Card Company") then
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
exit(false);
if not PaymentMethodMapping."Post Automatically" then
exit(false);
exit((PaymentMethodMapping."Auto-Post Jnl. Template" <> '') and (PaymentMethodMapping."Auto-Post Jnl. Batch" <> ''));
end;

local procedure PostTransaction(var AutoGenJnlPost: Codeunit "Shpfy Auto Gen. Jnl.-Post"; OrderTransaction: Record "Shpfy Order Transaction"; PaymentMethodMapping: Record "Shpfy Payment Method Mapping"; PostingDate: Date)
Comment thread
onbuyuka marked this conversation as resolved.
Comment thread
onbuyuka marked this conversation as resolved.
var
GenJournalLine: Record "Gen. Journal Line";
GenJnlPostBatch: Codeunit "Gen. Jnl.-Post Batch";
TemplateName: Code[10];
BatchName: Code[10];
ErrorText: Text;
begin
// Build into a single-use batch via Codeunit.Run so any failure is trapped and rolled back.
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
AutoGenJnlPost.SetParameters(PaymentMethodMapping, PostingDate);
if not AutoGenJnlPost.Run(OrderTransaction) then begin
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
LogFailure(OrderTransaction, GetLastErrorText());
exit;
end;

AutoGenJnlPost.GetIsolatedBatch(TemplateName, BatchName);
if BatchName = '' then
exit;

GenJournalLine.SetRange("Journal Template Name", TemplateName);
GenJournalLine.SetRange("Journal Batch Name", BatchName);
if not GenJournalLine.FindSet() then begin
RemoveIsolatedBatch(TemplateName, BatchName);
exit;
end;

// Commit the lines first: batch posting commits internally.
Commit();
Comment thread
onbuyuka marked this conversation as resolved.
Outdated

if GenJnlPostBatch.Run(GenJournalLine) then begin
RemoveIsolatedBatch(TemplateName, BatchName);
exit;
end;

// Posting failed after commit: drop the batch and log, without affecting the posted document.
ErrorText := GetLastErrorText();
RemoveIsolatedBatch(TemplateName, BatchName);
LogFailure(OrderTransaction, ErrorText);
end;

local procedure RemoveIsolatedBatch(TemplateName: Code[10]; BatchName: Code[10])
var
IsolatedBatch: Record "Gen. Journal Batch";
begin
// Delete cascades to leftover lines; commit so a later rollback can't resurrect the batch.
if IsolatedBatch.Get(TemplateName, BatchName) then
IsolatedBatch.Delete(true);
Comment thread
onbuyuka marked this conversation as resolved.
Outdated
Commit();
end;

local procedure LogFailure(OrderTransaction: Record "Shpfy Order Transaction"; ErrorText: Text)
var
Shop: Record "Shpfy Shop";
SkippedRecord: Codeunit "Shpfy Skipped Record";
begin
if Shop.Get(OrderTransaction.Shop) then
SkippedRecord.LogSkippedRecord(OrderTransaction."Shopify Transaction Id", OrderTransaction.RecordId, CopyStr(ErrorText, 1, 250), Shop);
Commit();
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// ------------------------------------------------------------------------------------------------
// 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;

/// <summary>
/// Page Shpfy Filter Transactions (ID 30176).
/// Request dialog used to filter the Shopify transactions list down to the transactions that are
/// ready to be posted, optionally narrowed by payment gateway and creation date range.
/// </summary>
page 30176 "Shpfy Filter Transactions"
{
Caption = 'Filter Postable Transactions';
PageType = StandardDialog;
ApplicationArea = All;
Comment thread
onbuyuka marked this conversation as resolved.

layout
{
area(Content)
{
field(Gateway; Gateway)
{
Caption = 'Gateway';
ToolTip = 'Specifies the transaction gateway to filter transactions by. Leave blank to include all gateways set up for automatic posting.';
Editable = false;

trigger OnAssistEdit()
var
PaymentMethodMapping: Record "Shpfy Payment Method Mapping";
begin
PaymentMethodMapping.SetRange("Post Automatically", true);
if Page.RunModal(Page::"Shpfy Payment Methods Mapping", 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);
if EndDate <> 0D then
NewEndDate := CreateDateTime(EndDate, 235959.999T);
end;
}
Loading
Loading