Codeunit 370 Bank Acc. Reconciliation Post, source in 29

Source29

src/Layers/W1/BaseApp/Bank/Reconciliation/BankAccReconciliationPost.Codeunit.al1276 lines, Copyright (c) Microsoft Corporation. MIT

// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Bank.Reconciliation;

using Microsoft.Bank.BankAccount;
using Microsoft.Bank.Check;
using Microsoft.Bank.Ledger;
using Microsoft.Bank.Statement;
using Microsoft.Finance.Analysis;
using Microsoft.Finance.Currency;
using Microsoft.Finance.Dimension;
using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Finance.GeneralLedger.Posting;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Foundation.AuditCodes;
using Microsoft.HumanResources.Payables;
using Microsoft.Purchases.Payables;
using Microsoft.Sales.Receivables;
using System.Reflection;
using System.Telemetry;
using System.Utilities;

/// <summary>
/// Handles the posting process for bank account reconciliation and payment application documents.
/// This codeunit processes completed reconciliation statements, creating permanent ledger entries,
/// applying payments to customer and vendor accounts, and finalizing bank reconciliation transactions.
/// Supports both bank reconciliation and payment application workflows with comprehensive validation,
/// posting preview, and audit trail generation.
/// </summary>
/// <remarks>
/// Key features include posting validation, applied payment entries creation, bank ledger entry updates,
/// check ledger entry processing, dimension posting, and integration with general journal posting engine.
/// Provides posting preview capability for validation before final commitment and comprehensive error handling
/// for application discrepancies, tolerance violations, and incomplete reconciliations.
/// Integration events enable customization of posting logic, validation rules, and posting procedures.
/// </remarks>
codeunit 370 "Bank Acc. Reconciliation Post"
{
    Permissions = TableData "Bank Account Ledger Entry" = rm,
                  TableData "Check Ledger Entry" = rm,
                  TableData "Bank Account" = rm,
                  TableData "Bank Account Statement" = ri,
                  TableData "Bank Account Statement Line" = ri,
                  TableData "Posted Payment Recon. Hdr" = ri;
    TableNo = "Bank Acc. Reconciliation";

    trigger OnRun()
    begin
        if GuiAllowed and not PreviewMode then begin
            Window.Open('#1#################################\\' + PostingLinesTxt);
            Window.Update(1, StrSubstNo('%1 %2', Rec."Bank Account No.", Rec."Statement No."));
        end;

        InitPost(Rec);
        Post(Rec);
        FinalizePost(Rec);

        if PreviewMode then
            exit;

        if GuiAllowed then
            Window.Close();

        Commit();
    end;

    var
        BankAcc: Record "Bank Account";
        BankAccLedgEntry: Record "Bank Account Ledger Entry";
        CheckLedgEntry: Record "Check Ledger Entry";
        GenJnlLine: Record "Gen. Journal Line";
        GLSetup: Record "General Ledger Setup";
        SourceCodeSetup: Record "Source Code Setup";
        GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line";
        Window: Dialog;
        SourceCode: Code[10];
        PostedStamentNo: Code[20];
        TotalAmount: Decimal;
        TotalAppliedAmount: Decimal;
        Lines: Integer;
        Difference: Decimal;
        PreviewMode: Boolean;
        PostPaymentsOnly: Boolean;

#pragma warning disable AA0470
        PostingLinesTxt: Label 'Posting lines              #2######';
        StatementEndingBalanceErr: Label '%1 must be equal to Total Balance.';
#pragma warning restore AA0470
#pragma warning disable AA0074
#pragma warning disable AA0470
        Text003: Label 'The application is not correct. The total amount applied is %1; it should be %2.';
        Text004: Label 'The total difference is %1. It must be %2.';
#pragma warning restore AA0470
#pragma warning restore AA0074
        ExcessiveAmtErr: Label 'You must apply the excessive amount of %1 %2 manually.', Comment = '%1 a decimal number, %2 currency code';
        NotFullyAppliedErr: Label 'One or more payments are not fully applied.\\The sum of applied amounts is %1. It must be %2.', Comment = '%1 - total applied amount, %2 - total transaction amount';
        LineNoTAppliedErr: Label 'The line with transaction date %1 and transaction text ''%2'' is not applied. You must apply all lines.', Comment = '%1 - transaction date, %2 - arbitrary text';
        TransactionAlreadyReconciledErr: Label 'The line with transaction date %1 and transaction text ''%2'' is already reconciled.\\You must remove it from the payment reconciliation journal before posting.', Comment = '%1 - transaction date, %2 - arbitrary text';
        EventNameTelemetryTxt: Label 'Post bank reconciliation', Locked = true;
        EventNameTelemetryPmtTxt: Label 'Post payment application', Locked = true;
        PaymentRecCategoryLbl: Label 'AL Payment Reconciliation', Locked = true;
        BankAccountRecCategoryLbl: Label 'AL Bank Account Rec', Locked = true;
        MissingBankAccReconLineQst: Label 'There''s nothing to post because the reconciliation doesn''t contain any entries.\\Do you want to continue?';
        EmptyDescriptionTxt: Label 'Autogenerated blank entry';
        PartialApplicationBankLedgerEntryQst: Label 'Line %1 relates to a bank ledger entry that isn''t fully reconciled. Carefully review the entry before proceeding. Partial applications can make your financial records inaccurate, so they aren''t recommended.\\Do you want to continue?', Comment = '%1 - Statement Line No.';

    /// <summary>
    /// Runs the posting process in preview mode to validate transactions before final posting.
    /// Enables users to review the impact of posting without committing changes to the database,
    /// providing validation of applied amounts, account assignments, and posting calculations.
    /// </summary>
    /// <param name="BankAccReconciliation">Bank reconciliation document to preview for posting.</param>
    /// <returns>True if preview completed successfully; false if validation errors occurred.</returns>
    [Scope('OnPrem')]
    [CommitBehavior(CommitBehavior::Ignore)]
    procedure RunPreview(var BankAccReconciliation: Record "Bank Acc. Reconciliation"): Boolean
    begin
        PreviewMode := true;
        InitPost(BankAccReconciliation);
        Post(BankAccReconciliation);
        FinalizePost(BankAccReconciliation);
        exit(true);
    end;

    local procedure InitPost(var BankAccRecon: Record "Bank Acc. Reconciliation")
    begin
        OnBeforeInitPost(BankAccRecon);
        GLSetup.Get();
        case BankAccRecon."Statement Type" of
            BankAccRecon."Statement Type"::"Bank Reconciliation":
                begin
                    BankAccRecon.TestField("Statement Date");
                    CheckLinesMatchEndingBalance(BankAccRecon, Difference);
                end;
            BankAccRecon."Statement Type"::"Payment Application":
                begin
                    SourceCodeSetup.Get();
                    SourceCode := SourceCodeSetup."Payment Reconciliation Journal";
                    PostPaymentsOnly := BankAccRecon."Post Payments Only";
                    if PreviewMode then
                        exit;
                    if not PostPaymentsOnly then
                        if GuiAllowed then begin
                            if PAGE.RunModal(Page::"Post Pmts and Rec. Bank Acc.", BankAccRecon) <> ACTION::LookupOK then
                                Error('');

                            BankAccRecon.Get(BankAccRecon."Statement Type", BankAccRecon."Bank Account No.", BankAccRecon."Statement No.");
                            CheckLinesMatchEndingBalance(BankAccRecon, Difference);
                        end;
                end;
        end;
    end;

    local procedure StoreFieldsPrePosting(BankAccRecon: Record "Bank Acc. Reconciliation"; var PrePostingOutstdPayments: Decimal; var PrePostingOutstdBankTransactions: Decimal; var PrePostingGLBalance: Decimal; var PrePostingTotalPositiveDifference: Decimal; var PrePostingTotalNegativeDifference: Decimal)
    var
        PreBankAcc: Record "Bank Account";
        BankAccPostingGroup: Record "Bank Account Posting Group";
        BankAccReconTest: Codeunit "Bank Acc. Recon. Test";
    begin
        BankAccRecon.CalcFields(
            "Total Applied Amount",
            "Total Outstd Bank Transactions",
            "Total Outstd Payments",
            "Total Unposted Applied Amount"
        );
        PrePostingOutstdPayments := BankAccReconTest.TotalOutstandingPayments(BankAccRecon);
        PrePostingOutstdBankTransactions := BankAccReconTest.TotalOutstandingBankTransactions(BankAccRecon);
        PrePostingTotalPositiveDifference := BankAccReconTest.TotalPositiveDifference(BankAccRecon);
        PrePostingTotalNegativeDifference := BankAccReconTest.TotalNegativeDifference(BankAccRecon);
        PreBankAcc.SetFilter("Date Filter", '..%1', BankAccRecon."Statement Date");
        PreBankAcc.SetAutoCalcFields("Balance at Date");
        if PreBankAcc.Get(BankAccRecon."Bank Account No.") then
            if BankAccPostingGroup.Get(PreBankAcc."Bank Acc. Posting Group") then
                PrePostingGLBalance := BankAccReconTest.GetGLAccountBalanceLCY(PreBankAcc, BankAccPostingGroup, BankAccRecon."Statement Date");
    end;

    local procedure Post(BankAccRecon: Record "Bank Acc. Reconciliation")
    var
        BankAccReconLine: Record "Bank Acc. Reconciliation Line";
        BankAccRecMatchBuffer: Record "Bank Acc. Rec. Match Buffer";
        FeatureTelemetry: Codeunit "Feature Telemetry";
        AppliedAmount: Decimal;
        PrePostingOutstdPayments: Decimal;
        PrePostingOutstdBankTransactions: Decimal;
        PrePostingGLBalance: Decimal;
        PrePostingTotalPositiveDifference: Decimal;
        PrePostingTotalNegativeDifference: Decimal;
        TotalTransAmtNotAppliedErr: Text;
    begin
        OnBeforePost(BankAccRecon, BankAccReconLine);
        case BankAccRecon."Statement Type" of
            BankAccRecon."Statement Type"::"Bank Reconciliation":
                FeatureTelemetry.LogUptake('0000JLO', BankAccRecon.GetBankReconciliationTelemetryFeatureName(), Enum::"Feature Uptake Status"::Used);
            BankAccRecon."Statement Type"::"Payment Application":
                FeatureTelemetry.LogUptake('0000KMI', BankAccReconLine.GetPaymentRecJournalTelemetryFeatureName(), Enum::"Feature Uptake Status"::Used);
        end;
        StoreFieldsPrePosting(BankAccRecon, PrePostingOutstdPayments, PrePostingOutstdBankTransactions, PrePostingGLBalance, PrePostingTotalPositiveDifference, PrePostingTotalNegativeDifference);
        // Run through lines
        BankAccReconLine.FilterBankRecLines(BankAccRecon);
        OnPostAfterFilterBankAccRecLines(BankAccReconLine, BankAccRecon);
        TotalAmount := 0;
        TotalAppliedAmount := 0;
        Lines := 0;
        if BankAccReconLine.IsEmpty() then
            if Confirm(MissingBankAccReconLineQst) then
                InsertEmptyBankAccReconLine(BankAccRecon)
            else
                Error('');
        BankAccLedgEntry.LockTable();
        CheckLedgEntry.LockTable();

        PostedStamentNo := GetPostedStamentNo(BankAccRecon);

        if BankAccReconLine.FindSet() then
            repeat
                Lines := Lines + 1;
                if GuiAllowed then
                    if not PreviewMode then
                        Window.Update(2, Lines);
                AppliedAmount := 0;

                BankAccReconLine.FilterManyToOneMatches(BankAccRecMatchBuffer);
                if (BankAccRecon."Statement Type" = BankAccRecon."Statement Type"::"Bank Reconciliation") and BankAccRecMatchBuffer.FindFirst() then begin
                    if (not BankAccRecMatchBuffer."Is Processed") then
                        CloseBankAccLEManyToOne(BankAccRecMatchBuffer, AppliedAmount, BankAccRecon."Statement Date");
                end else begin
                    // Adjust entries
                    // Test amount and settled amount
                    case BankAccRecon."Statement Type" of
                        BankAccRecon."Statement Type"::"Bank Reconciliation":
                            CloseBankAccLedgEntry(BankAccReconLine, AppliedAmount, BankAccRecon."Statement Date");
                        BankAccRecon."Statement Type"::"Payment Application":
                            PostPaymentApplications(BankAccReconLine, AppliedAmount);
                    end;
                    OnBeforeAppliedAmountCheck(BankAccReconLine, AppliedAmount);
                    BankAccReconLine.TestField("Applied Amount", AppliedAmount);
                end;
                TotalAmount += BankAccReconLine."Statement Amount";
                TotalAppliedAmount += AppliedAmount;
            until BankAccReconLine.Next() = 0;
        // Test amount
        if BankAccRecon."Statement Type" = BankAccRecon."Statement Type"::"Payment Application" then
            TotalTransAmtNotAppliedErr := NotFullyAppliedErr
        else
            TotalTransAmtNotAppliedErr := Text003;
        if TotalAmount <> TotalAppliedAmount then
            Error(
              TotalTransAmtNotAppliedErr,
              TotalAppliedAmount, TotalAmount);
        if Difference <> 0 then
            Error(Text004, Difference, 0);

        if PreviewMode then
            exit;
        // Get bank
        if not PostPaymentsOnly then
            UpdateBank(BankAccRecon, TotalAmount);

        case BankAccRecon."Statement Type" of
            BankAccRecon."Statement Type"::"Bank Reconciliation":
                begin
                    TransferToBankStmt(BankAccRecon, PrePostingOutstdPayments, PrePostingOutstdBankTransactions, PrePostingGLBalance, PrePostingTotalPositiveDifference, PrePostingTotalNegativeDifference);
                    FeatureTelemetry.LogUsage('0000JLP', BankAccRecon.GetBankReconciliationTelemetryFeatureName(), EventNameTelemetryTxt);
                    Session.LogMessage('0000JLQ', Format(Lines), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', BankAccountRecCategoryLbl);
                end;
            BankAccRecon."Statement Type"::"Payment Application":
                begin
                    HandlePaymentApplicationTransfer(BankAccRecon, PrePostingOutstdPayments, PrePostingOutstdBankTransactions, PrePostingGLBalance, PrePostingTotalPositiveDifference, PrePostingTotalNegativeDifference);
                    FeatureTelemetry.LogUsage('0000KMJ', BankAccReconLine.GetPaymentRecJournalTelemetryFeatureName(), EventNameTelemetryPmtTxt);
                    Session.LogMessage('0000KMK', Format(Lines), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', PaymentRecCategoryLbl);
                end;
        end;
    end;

    local procedure HandlePaymentApplicationTransfer(BankAccRecon: Record "Bank Acc. Reconciliation"; PrePostingOutstdPayments: Decimal; PrePostingOutstdBankTransactions: Decimal; PrePostingGLBalance: Decimal; PrePostingTotalPositiveDifference: Decimal; PrePostingTotalNegativeDifference: Decimal)
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeHandlePaymentApplicationTransfer(BankAccRecon, IsHandled);
        if IsHandled then
            exit;

        TransferToPostPmtAppln(BankAccRecon);
        if not BankAccRecon."Post Payments Only" then
            TransferToBankStmt(BankAccRecon, PrePostingOutstdPayments, PrePostingOutstdBankTransactions, PrePostingGLBalance, PrePostingTotalPositiveDifference, PrePostingTotalNegativeDifference);
    end;

    local procedure FinalizePost(BankAccRecon: Record "Bank Acc. Reconciliation")
    var
        UpdateAnalysisView: Codeunit "Update Analysis View";
        CreationDateTime: DateTime;
        MatchedWithAI: Boolean;
        LineCount: Integer;
        TelemetryCategories: Dictionary of [Text, Text];
        DurationUntilPosting: BigInteger;
    begin
        if PreviewMode then
            exit;
        OnBeforeFinalizePost(BankAccRecon);
        CreationDateTime := BankAccRecon.SystemCreatedAt;
        MatchedWithAI := AIMatchProposalsExist(BankAccRecon);

        DeleteRelatedRecords(BankAccRecon, LineCount);

        UpdateAnalysisView.UpdateAll(0, true);
        TelemetryCategories.Add('Category', BankAccountRecCategoryLbl);
        TelemetryCategories.Add('MatchedWithAI', Format(MatchedWithAI, 0, 9));
        TelemetryCategories.Add('NumberOfLines', Format(LineCount));
        if TryCalculateDurationToPost(DurationUntilPosting, CreationDateTime) then
            Session.LogMessage('0000LHY', Format(DurationUntilPosting), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, TelemetryCategories);
        OnAfterFinalizePost(BankAccRecon);
    end;

    local procedure DeleteRelatedRecords(var BankAccReconciliation: Record "Bank Acc. Reconciliation"; var LineCount: Integer)
    var
        BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line";
        AppliedPaymentEntry: Record "Applied Payment Entry";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeDeleteRelatedRecords(BankAccReconciliation, IsHandled);
        if IsHandled then
            exit;

        if BankAccReconciliationLine.LinesExist(BankAccReconciliation) then
            repeat
                AppliedPaymentEntry.FilterAppliedPmtEntry(BankAccReconciliationLine);
                AppliedPaymentEntry.DeleteAll();

                BankAccReconciliationLine.Delete();
                BankAccReconciliationLine.ClearDataExchEntries();
                LineCount += 1;

            until BankAccReconciliationLine.Next() = 0;

        BankAccReconciliation.Find();
        BankAccReconciliation.Delete();
    end;

    [TryFunction]
    local procedure TryCalculateDurationToPost(var DurationUntilPosting: BigInteger; CreationDateTime: DateTime)
    begin
        DurationUntilPosting := CurrentDateTime() - CreationDateTime;
    end;

    local procedure AIMatchProposalsExist(var BankAccReconciliation: Record "Bank Acc. Reconciliation"): Boolean
    var
        PaymentMatchingDetails: Record "Payment Matching Details";
    begin
        PaymentMatchingDetails.SetRange("Statement Type", BankAccReconciliation."Statement Type");
        PaymentMatchingDetails.SetRange("Bank Account No.", BankAccReconciliation."Bank Account No.");
        PaymentMatchingDetails.SetRange("Statement No.", BankAccReconciliation."Statement No.");
        PaymentMatchingDetails.SetFilter(Message, '*Copilot*');
        exit(not PaymentMatchingDetails.IsEmpty());
    end;

    local procedure CheckLinesMatchEndingBalance(BankAccRecon: Record "Bank Acc. Reconciliation"; var Difference: Decimal)
    var
        BankAccReconLine: Record "Bank Acc. Reconciliation Line";
    begin
        BankAccReconLine.LinesExist(BankAccRecon);
        BankAccReconLine.CalcSums(BankAccReconLine."Statement Amount", BankAccReconLine.Difference);
        OnCheckLinesMatchEndingBalanceOnAfterCalcSums(BankAccReconLine);

        if BankAccReconLine."Statement Amount" <>
           BankAccRecon."Statement Ending Balance" - BankAccRecon."Balance Last Statement"
        then
            Error(StatementEndingBalanceErr, BankAccRecon.FieldCaption("Statement Ending Balance"));
        Difference := BankAccReconLine.Difference;
    end;

    local procedure CloseBankAccLEManyToOne(BankAccRecMatchBuffer: Record "Bank Acc. Rec. Match Buffer"; var AppliedAmount: Decimal; StatementDate: Date);
    var
        BankAccRecMatchBufferCopy: Record "Bank Acc. Rec. Match Buffer";
        BankAccRecLine: Record "Bank Acc. Reconciliation Line";
        LedgerEntryNo: Integer;
        TotalRecLinesAmount: Decimal;
    begin
        BankAccRecMatchBufferCopy.SetRange("Bank Account No.", BankAccRecMatchBuffer."Bank Account No.");
        BankAccRecMatchBufferCopy.SetRange("Statement No.", BankAccRecMatchBuffer."Statement No.");
        BankAccRecMatchBufferCopy.SetRange("Match ID", BankAccRecMatchBuffer."Match ID");
        if (not BankAccRecMatchBufferCopy.IsEmpty()) then
            if BankAccRecMatchBufferCopy.FindSet() then
                repeat
                    LedgerEntryNo := BankAccRecMatchBufferCopy."Ledger Entry No.";
                    BankAccRecMatchBufferCopy."Is Processed" := true;
                    BankAccRecMatchBufferCopy.Modify();
                    BankAccRecLine.SetRange("Bank Account No.", BankAccRecMatchBufferCopy."Bank Account No.");
                    BankAccRecLine.SetRange("Statement No.", BankAccRecMatchBufferCopy."Statement No.");
                    BankAccRecLine.SetRange("Statement Line No.", BankAccRecMatchBufferCopy."Statement Line No.");
                    if BankAccRecLine.FindFirst() then
                        TotalRecLinesAmount += BankAccRecLine."Statement Amount";
                until BankAccRecMatchBufferCopy.Next() = 0;

        BankAccLedgEntry.Get(LedgerEntryNo);
        BankAccLedgEntry.TestField("Remaining Amount", TotalRecLinesAmount);
        AppliedAmount += BankAccLedgEntry."Remaining Amount";
        BankAccLedgEntry."Remaining Amount" := 0;
        BankAccLedgEntry.Open := false;
        BankAccLedgEntry."Closed at Date" := StatementDate;
        BankAccLedgEntry."Statement Status" := BankAccLedgEntry."Statement Status"::Closed;
        OnCloseBankAccLedgEntryOnBeforeBankAccLedgEntryModify(BankAccLedgEntry, BankAccRecLine);
        BankAccLedgEntry.Modify();

        CheckLedgEntry.Reset();
        CheckLedgEntry.SetCurrentKey("Bank Account Ledger Entry No.");
        CheckLedgEntry.SetRange(
            "Bank Account Ledger Entry No.", BankAccLedgEntry."Entry No.");
        CheckLedgEntry.SetRange(Open, true);
        if CheckLedgEntry.Find('-') then
            repeat
                CheckLedgEntry.TestField(Open, true);
                CheckLedgEntry.TestField(
                    "Statement Status",
                    CheckLedgEntry."Statement Status"::"Bank Acc. Entry Applied");
                CheckLedgEntry.Open := false;
                CheckLedgEntry."Statement Status" := CheckLedgEntry."Statement Status"::Closed;
                CheckLedgEntry.Modify();
            until CheckLedgEntry.Next() = 0;
    end;

    local procedure CloseBankAccLedgEntry(BankAccReconLine: Record "Bank Acc. Reconciliation Line"; var AppliedAmount: Decimal; StatementClosingDate: Date)
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeCloseBankAccLedgEntry(BankAccReconLine, AppliedAmount, IsHandled);
        if IsHandled then
            exit;

        BankAccLedgEntry.Reset();
        BankAccLedgEntry.SetCurrentKey("Bank Account No.", Open);
        BankAccLedgEntry.SetRange("Bank Account No.", BankAccReconLine."Bank Account No.");
        BankAccLedgEntry.SetRange(Open, true);
        BankAccLedgEntry.SetFilter("Statement Status", '%1|%2', BankAccLedgEntry."Statement Status"::"Bank Acc. Entry Applied", BankAccLedgEntry."Statement Status"::"Check Entry Applied");
        BankAccLedgEntry.SetRange("Statement No.", BankAccReconLine."Statement No.");
        BankAccLedgEntry.SetRange("Statement Line No.", BankAccReconLine."Statement Line No.");
        OnCloseBankAccLedgEntryOnAfterBankAccLedgEntrySetFilters(BankAccLedgEntry, BankAccReconLine);
        if BankAccLedgEntry.FindSet(true) then
            repeat
                AppliedAmount += BankAccLedgEntry."Remaining Amount";
                BankAccLedgEntry."Remaining Amount" := 0;
                BankAccLedgEntry.Open := false;
                BankAccLedgEntry."Closed at Date" := StatementClosingDate;
                BankAccLedgEntry."Statement Status" := BankAccLedgEntry."Statement Status"::Closed;
                BankAccLedgEntry."Statement No." := PostedStamentNo;
                OnCloseBankAccLedgEntryOnBeforeBankAccLedgEntryModify(BankAccLedgEntry, BankAccReconLine);
                BankAccLedgEntry.Modify();

                CheckLedgEntry.Reset();
                CheckLedgEntry.SetCurrentKey("Bank Account Ledger Entry No.");
                CheckLedgEntry.SetRange(
                  "Bank Account Ledger Entry No.", BankAccLedgEntry."Entry No.");
                CheckLedgEntry.SetRange(Open, true);
                if CheckLedgEntry.Find('-') then
                    repeat
                        CheckLedgEntry.TestField(Open, true);
                        CheckLedgEntry.TestField("Statement No.", BankAccReconLine."Statement No.");
                        CheckLedgEntry.TestField("Statement Line No.", BankAccReconLine."Statement Line No.");
                        CheckLedgEntry.Open := false;
                        CheckLedgEntry."Statement Status" := CheckLedgEntry."Statement Status"::Closed;
                        OnCloseBankAccLedgEntryOnBeforeCheckLedgEntryModify(CheckLedgEntry, BankAccReconLine);
                        CheckLedgEntry.Modify();
                    until CheckLedgEntry.Next() = 0;
            until BankAccLedgEntry.Next() = 0;
    end;

    local procedure PostPaymentApplications(BankAccReconLine: Record "Bank Acc. Reconciliation Line"; var AppliedAmount: Decimal)
    var
        BankAccReconciliation: Record "Bank Acc. Reconciliation";
        Currency: Record Currency;
        CurrExchRate: Record "Currency Exchange Rate";
        AppliedPmtEntry: Record "Applied Payment Entry";
        BankAccountLedgerEntry: Record "Bank Account Ledger Entry";
        DimensionManagement: Codeunit DimensionManagement;
        ConfirmManagement: Codeunit "Confirm Management";
        PaymentLineAmount: Decimal;
        RemainingAmount: Decimal;
        IsApplied: Boolean;
        IsHandled: Boolean;
        ContinueWithPartialApplicationOfBLE: Boolean;
    begin
        IsHandled := false;
        OnBeforePostPaymentApplications(BankAccReconLine, AppliedAmount, IsHandled);
        if IsHandled then
            exit;

        if BankAccReconLine.IsTransactionPostedAndReconciled() then
            Error(TransactionAlreadyReconciledErr, BankAccReconLine."Transaction Date", BankAccReconLine."Transaction Text");

        OnPostPaymentApplicationsOnAfterTransactionPostedAndReconciledCheck(BankAccReconLine, AppliedAmount, SourceCode);
        if BankAccReconLine."Account No." = '' then
            Error(LineNoTAppliedErr, BankAccReconLine."Transaction Date", BankAccReconLine."Transaction Text");
        BankAcc.Get(BankAccReconLine."Bank Account No.");

        GenJnlLine.Init();
        GenJnlLine.SetSuppressCommit(true);
        GenJnlLine."Document Type" := GenJnlLine."Document Type"::Payment;

        if IsRefund(BankAccReconLine) then
            if BankAccReconLine."Account Type" = BankAccReconLine."Account Type"::Employee then
                GenJnlLine."Document Type" := GenJnlLine."Document Type"::" "
            else
                GenJnlLine."Document Type" := GenJnlLine."Document Type"::Refund;

        GenJnlLine."Posting Date" := BankAccReconLine."Transaction Date";
        GenJnlLine."VAT Reporting Date" := BankAccReconLine."Transaction Date";
        GenJnlLine."Account Type" := Enum::"Gen. Journal Account Type".FromInteger(BankAccReconLine.GetAppliedToAccountType());
        BankAccReconciliation.Get(
          BankAccReconLine."Statement Type", BankAccReconLine."Bank Account No.", BankAccReconLine."Statement No.");
        GenJnlLine."Copy VAT Setup to Jnl. Lines" := BankAccReconciliation."Copy VAT Setup to Jnl. Line";
        GenJnlLine.Validate("Account No.", BankAccReconLine.GetAppliedToAccountNo());
        GenJnlLine."Dimension Set ID" := BankAccReconLine."Dimension Set ID";
        DimensionManagement.UpdateGlobalDimFromDimSetID(
          BankAccReconLine."Dimension Set ID", GenJnlLine."Shortcut Dimension 1 Code", GenJnlLine."Shortcut Dimension 2 Code");

        GenJnlLine.Description := BankAccReconLine.GetDescription();

        GenJnlLine."Document No." := PostedStamentNo;
        GenJnlLine."Bal. Account Type" := GenJnlLine."Bal. Account Type"::"Bank Account";
        GenJnlLine."Bal. Account No." := BankAcc."No.";

        GenJnlLine."Source Code" := SourceCode;
        GenJnlLine."Allow Zero-Amount Posting" := true;

        GenJnlLine."Applies-to ID" := BankAccReconLine.GetAppliesToID();
        if GLSetup."Journal Templ. Name Mandatory" then begin
            GLSetup.TestField("Bank Acc. Recon. Template Name");
            GLSetup.TestField("Bank Acc. Recon. Batch Name");
            GenJnlLine."Journal Template Name" := GLSetup."Bank Acc. Recon. Template Name";
            GenJnlLine."Journal Batch Name" := GLSetup."Bank Acc. Recon. Batch Name";
        end;

        IsApplied := false;
        IsHandled := false;
        OnPostPaymentApplicationsOnAfterInitGenJnlLine(GenJnlLine, BankAccReconLine, IsApplied, AppliedAmount, PaymentLineAmount, IsHandled);
        if IsHandled then
            exit;

        if AppliedPmtEntry.AppliedPmtEntryLinesExist(BankAccReconLine) then
            repeat
                AppliedAmount += AppliedPmtEntry."Applied Amount" - AppliedPmtEntry."Applied Pmt. Discount";
                PaymentLineAmount += AppliedPmtEntry."Applied Amount" - AppliedPmtEntry."Applied Pmt. Discount";
                AppliedPmtEntry.TestField("Account Type", BankAccReconLine."Account Type");
                AppliedPmtEntry.TestField("Account No.", BankAccReconLine."Account No.");
                if AppliedPmtEntry."Applies-to Entry No." <> 0 then begin
                    case AppliedPmtEntry."Account Type" of
                        AppliedPmtEntry."Account Type"::Customer:
                            ApplyCustLedgEntry(
                              AppliedPmtEntry, GenJnlLine."Applies-to ID", GenJnlLine."Posting Date", 0D, 0D, AppliedPmtEntry."Applied Pmt. Discount");
                        AppliedPmtEntry."Account Type"::Vendor:
                            ApplyVendLedgEntry(
                              AppliedPmtEntry, GenJnlLine."Applies-to ID", GenJnlLine."Posting Date", 0D, 0D, AppliedPmtEntry."Applied Pmt. Discount");
                        AppliedPmtEntry."Account Type"::Employee:
                            ApplyEmployeeLedgEntry(
                              AppliedPmtEntry, GenJnlLine."Applies-to ID", GenJnlLine."Posting Date", 0D, 0D, AppliedPmtEntry."Applied Pmt. Discount");
                        AppliedPmtEntry."Account Type"::"Bank Account":
                            begin
                                BankAccountLedgerEntry.Get(AppliedPmtEntry."Applies-to Entry No.");
                                RemainingAmount := BankAccountLedgerEntry."Remaining Amount";
                                case true of
                                    RemainingAmount = AppliedPmtEntry."Applied Amount":
                                        begin
                                            if not PostPaymentsOnly then
                                                CloseBankAccountLedgerEntry(AppliedPmtEntry."Applies-to Entry No.", AppliedPmtEntry."Applied Amount", BankAccReconciliation."Statement Date", BankAccReconLine."Statement Line No.");
                                            PaymentLineAmount -= AppliedPmtEntry."Applied Amount";
                                        end;
                                    Abs(RemainingAmount) > Abs(AppliedPmtEntry."Applied Amount"):
                                        begin
                                            ContinueWithPartialApplicationOfBLE := ConfirmManagement.GetResponseOrDefault(StrSubstNo(PartialApplicationBankLedgerEntryQst, AppliedPmtEntry."Statement Line No."), false);
                                            Session.LogMessage('0000KSG', 'Partial application of BLE', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All, 'Continue', Format(ContinueWithPartialApplicationOfBLE));
                                            if not ContinueWithPartialApplicationOfBLE then
                                                Error('');

                                            if not PostPaymentsOnly then begin
                                                BankAccountLedgerEntry."Remaining Amount" -= AppliedPmtEntry."Applied Amount";
                                                BankAccountLedgerEntry.Modify();
                                            end;
                                            PaymentLineAmount -= AppliedPmtEntry."Applied Amount";
                                        end;
                                    Abs(RemainingAmount) < Abs(AppliedPmtEntry."Applied Amount"):
                                        begin
                                            if not PostPaymentsOnly then
                                                CloseBankAccountLedgerEntry(AppliedPmtEntry."Applies-to Entry No.", RemainingAmount, BankAccReconciliation."Statement Date", BankAccReconLine."Statement Line No.");
                                            PaymentLineAmount -= RemainingAmount;
                                        end;
                                end;
                            end;
                        else
                            OnPostPaymentApplicationsOnAccountTypeCaseElse(AppliedPmtEntry, GenJnlLine);
                    end;
                    IsApplied := true;
                end;
            until AppliedPmtEntry.Next() = 0;

        if PaymentLineAmount <> 0 then begin
            if not IsApplied then
                GenJnlLine."Applies-to ID" := '';
            if (GenJnlLine."Account Type" <> GenJnlLine."Account Type"::"Bank Account") or
               (GenJnlLine."Currency Code" = BankAcc."Currency Code")
            then begin
                GenJnlLine.Validate("Currency Code", BankAcc."Currency Code");
                Currency.Initialize(GenJnlLine."Currency Code");
                GenJnlLine.Amount := Round(-PaymentLineAmount, Currency."Amount Rounding Precision");
                if GenJnlLine."Currency Code" <> '' then
                    GenJnlLine."Amount (LCY)" := Round(
                        CurrExchRate.ExchangeAmtFCYToLCY(
                          GenJnlLine."Posting Date", GenJnlLine."Currency Code",
                          GenJnlLine.Amount, GenJnlLine."Currency Factor"));
                GenJnlLine.Validate("VAT %");
                GenJnlLine.Validate("Bal. VAT %")
            end else
                Error(ExcessiveAmtErr, PaymentLineAmount, GLSetup.GetCurrencyCode(BankAcc."Currency Code"));

            OnPostPaymentApplicationsOnBeforeValidateApplyRequirements(BankAccReconLine, GenJnlLine, AppliedAmount);

            GenJnlLine.ValidateApplyRequirements(GenJnlLine);
            GenJnlPostLine.RunWithCheck(GenJnlLine);
            OnPostPaymentApplicationsOnAfterPostGenJnlLine(GenJnlLine, GenJnlPostLine);
            if not PostPaymentsOnly then begin
                BankAccountLedgerEntry.SetRange(Open, true);
                BankAccountLedgerEntry.SetRange("Bank Account No.", BankAcc."No.");
                BankAccountLedgerEntry.SetRange("Document Type", GenJnlLine."Document Type");
                BankAccountLedgerEntry.SetRange("Document No.", PostedStamentNo);
                BankAccountLedgerEntry.SetRange("Posting Date", GenJnlLine."Posting Date");
                OnPostPaymentApplicationsOnAfterBankAccountLedgerEntrySetFilters(BankAccountLedgerEntry, GenJnlLine);
                if BankAccountLedgerEntry.FindLast() then
                    CloseBankAccountLedgerEntry(BankAccountLedgerEntry."Entry No.", BankAccountLedgerEntry.Amount, BankAccReconciliation."Statement Date", BankAccReconLine."Statement Line No.");
            end;
        end;
    end;

    local procedure UpdateBank(BankAccRecon: Record "Bank Acc. Reconciliation"; Amt: Decimal)
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeUpdateBank(BankAccRecon, BankAcc, IsHandled, Amt, PostedStamentNo);
        if IsHandled then
            exit;
        BankAcc.LockTable();
        BankAcc.Get(BankAccRecon."Bank Account No.");
        BankAcc.TestField(Blocked, false);
        BankAcc."Last Statement No." := PostedStamentNo;
        BankAcc."Balance Last Statement" := BankAccRecon."Balance Last Statement" + Amt;
        BankAcc.Modify();
    end;

    local procedure TransferToBankStmt(BankAccRecon: Record "Bank Acc. Reconciliation"; PrePostingOutstdPayments: Decimal; PrePostingOutstdBankTransactions: Decimal; PrePostingGLBalance: Decimal; PrePostingTotalPositiveDifference: Decimal; PrePostingTotalNegativeDifference: Decimal)
    var
        BankAccStmt: Record "Bank Account Statement";
        BankAccStmtLine: Record "Bank Account Statement Line";
        BankAccReconLine: Record "Bank Acc. Reconciliation Line";
        BankAccountLedgerEntry: Record "Bank Account Ledger Entry";
        AppliedPaymentEntry: Record "Applied Payment Entry";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTransferToBankStmt(BankAccRecon, PrePostingOutstdPayments, PrePostingOutstdBankTransactions, PrePostingGLBalance, PrePostingTotalPositiveDifference, PrePostingTotalNegativeDifference, IsHandled);
        if IsHandled then
            exit;

        BankAccStmt.Init();
        BankAccStmt.TransferFields(BankAccRecon);
        BankAccStmt."Statement No." := PostedStamentNo;
        if BankAccReconLine.LinesExist(BankAccRecon) then
            repeat
                BankAccStmtLine.TransferFields(BankAccReconLine);
                BankAccStmtLine."Statement No." := BankAccStmt."Statement No.";
                OnTransferToBankStmtOnBeforeBankAccStmtLineInsert(BankAccStmtLine, BankAccReconLine);
                if AppliedPaymentEntry.AppliedPmtEntryLinesExist(BankAccReconLine) then
                    if (AppliedPaymentEntry."Applies-to Entry No." <> 0) and (AppliedPaymentEntry."Account Type" = AppliedPaymentEntry."Account Type"::"Bank Account") then begin
                        BankAccountLedgerEntry.Get(AppliedPaymentEntry."Applies-to Entry No.");
                        BankAccStmtLine.Difference := BankAccountLedgerEntry."Remaining Amount";
                    end;
                BankAccStmtLine.Insert();
                BankAccReconLine.ClearDataExchEntries();
            until BankAccReconLine.Next() = 0;

        BankAccStmtLine.SetRange("Bank Account No.", BankAccStmt."Bank Account No.");
        BankAccStmtLine.SetRange("Statement No.", BankAccStmt."Statement No.");
        BankAccStmtLine.CalcSums("Statement Amount");
        BankAccStmt."G/L Balance at Posting Date" := PrePostingGLBalance;
        BankAccStmt."Outstd. Payments at Posting" := PrePostingOutstdPayments;
        BankAccStmt."Outstd. Transact. at Posting" := PrePostingOutstdBankTransactions;
        BankAccStmt."Total Pos. Diff. at Posting" := PrePostingTotalPositiveDifference;
        BankAccStmt."Total Neg. Diff. at Posting" := PrePostingTotalNegativeDifference;

        OnBeforeBankAccStmtInsert(BankAccStmt, BankAccRecon);
        BankAccStmt.Insert();
    end;

    local procedure GetPostedStamentNo(BankAccRecon: Record "Bank Acc. Reconciliation") StatementNo: Code[20]
    var
        BankAccStmt: Record "Bank Account Statement";
    begin
        StatementNo := BankAccRecon."Statement No.";

        BankAccStmt.SetRange("Bank Account No.", BankAccRecon."Bank Account No.");
        BankAccStmt.SetRange("Statement No.", BankAccRecon."Statement No.");
        if not BankAccStmt.IsEmpty() then
            StatementNo := GetNextStatementNoAndUpdateBankAccount(BankAccRecon."Bank Account No.");
    end;

    local procedure TransferToPostPmtAppln(BankAccRecon: Record "Bank Acc. Reconciliation")
    var
        PostedPmtReconHdr: Record "Posted Payment Recon. Hdr";
        PostedPmtReconLine: Record "Posted Payment Recon. Line";
        BankAccReconLine: Record "Bank Acc. Reconciliation Line";
        TypeHelper: Codeunit "Type Helper";
        FieldLength: Integer;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTransferToPostPmtAppln(BankAccRecon, IsHandled);
        if IsHandled then
            exit;

        if BankAccReconLine.LinesExist(BankAccRecon) then
            repeat
                PostedPmtReconLine.TransferFields(BankAccReconLine);

                FieldLength := TypeHelper.GetFieldLength(DATABASE::"Posted Payment Recon. Line",
                    PostedPmtReconLine.FieldNo("Applied Document No."));
                PostedPmtReconLine."Applied Document No." := CopyStr(BankAccReconLine.GetAppliedToDocumentNo(), 1, FieldLength);

                FieldLength := TypeHelper.GetFieldLength(DATABASE::"Posted Payment Recon. Line",
                    PostedPmtReconLine.FieldNo("Applied Entry No."));
                PostedPmtReconLine."Applied Entry No." := CopyStr(BankAccReconLine.GetAppliedToEntryNo(), 1, FieldLength);

                PostedPmtReconLine.Reconciled := not PostPaymentsOnly;

                OnTransferToPostPmtApplnOnBeforePostedPmtReconLineInsert(PostedPmtReconLine, BankAccReconLine);
                PostedPmtReconLine.Insert();
                BankAccReconLine.ClearDataExchEntries();
            until BankAccReconLine.Next() = 0;

        PostedPmtReconHdr.TransferFields(BankAccRecon);
        OnBeforePostedPmtReconInsert(PostedPmtReconHdr, BankAccRecon);
        PostedPmtReconHdr.Insert();
    end;

    /// <summary>
    /// Applies a payment entry to a customer ledger entry during bank reconciliation posting.
    /// Updates the customer ledger entry with payment application details including discount dates,
    /// tolerance calculations, and application amounts. Handles currency exchange rate calculations
    /// for foreign currency transactions and integrates with the payment discount calculation engine.
    /// </summary>
    /// <param name="AppliedPmtEntry">Applied payment entry containing application details and amounts.</param>
    /// <param name="AppliesToID">Application identifier for grouping related applications; empty for immediate application.</param>
    /// <param name="PostingDate">Date for payment application posting and currency rate calculations.</param>
    /// <param name="PmtDiscDueDate">Payment discount due date for discount eligibility calculation.</param>
    /// <param name="PmtDiscToleranceDate">Payment discount tolerance date for extended discount availability.</param>
    /// <param name="RemPmtDiscPossible">Remaining payment discount amount available for the application.</param>
    procedure ApplyCustLedgEntry(AppliedPmtEntry: Record "Applied Payment Entry"; AppliesToID: Code[50]; PostingDate: Date; PmtDiscDueDate: Date; PmtDiscToleranceDate: Date; RemPmtDiscPossible: Decimal)
    var
        CustLedgEntry: Record "Cust. Ledger Entry";
        CurrExchRate: Record "Currency Exchange Rate";
        IsHandled: Boolean;
    begin
        CustLedgEntry.Get(AppliedPmtEntry."Applies-to Entry No.");
        CustLedgEntry.TestField(Open);
        BankAcc.Get(AppliedPmtEntry."Bank Account No.");

        IsHandled := false;
        OnBeforeApplyCustLedgEntry(CustLedgEntry, AppliedPmtEntry, BankAcc, AppliesToID, PostingDate, PmtDiscDueDate, PmtDiscToleranceDate, RemPmtDiscPossible, IsHandled);
        if IsHandled then
            exit;

        if AppliesToID = '' then begin
            CustLedgEntry."Pmt. Discount Date" := PmtDiscDueDate;
            CustLedgEntry."Pmt. Disc. Tolerance Date" := PmtDiscToleranceDate;

            CustLedgEntry."Remaining Pmt. Disc. Possible" := RemPmtDiscPossible;
            if BankAcc.IsInLocalCurrency() then
                CustLedgEntry."Remaining Pmt. Disc. Possible" :=
                  CurrExchRate.ExchangeAmount(CustLedgEntry."Remaining Pmt. Disc. Possible", '', CustLedgEntry."Currency Code", PostingDate);
        end else begin
            CustLedgEntry."Applies-to ID" := AppliesToID;
            CustLedgEntry."Amount to Apply" := AppliedPmtEntry.CalcAmountToApply(PostingDate);
        end;

        if PreviewMode then
            CustEntryEditNoCommit(CustLedgEntry)
        else
            CODEUNIT.Run(CODEUNIT::"Cust. Entry-Edit", CustLedgEntry);
    end;

    [CommitBehavior(CommitBehavior::Ignore)]
    local procedure CustEntryEditNoCommit(var CustLedgerEntry: Record "Cust. Ledger Entry")
    begin
        CODEUNIT.Run(CODEUNIT::"Cust. Entry-Edit", CustLedgerEntry);
    end;

    /// <summary>
    /// Applies a payment entry to a vendor ledger entry during bank reconciliation posting.
    /// Updates the vendor ledger entry with payment application details including discount dates,
    /// tolerance calculations, and application amounts. Handles currency exchange rate calculations
    /// for foreign currency transactions and manages vendor-specific payment discount processing.
    /// </summary>
    /// <param name="AppliedPmtEntry">Applied payment entry containing application details and amounts.</param>
    /// <param name="AppliesToID">Application identifier for grouping related applications; empty for immediate application.</param>
    /// <param name="PostingDate">Date for payment application posting and currency rate calculations.</param>
    /// <param name="PmtDiscDueDate">Payment discount due date for discount eligibility calculation.</param>
    /// <param name="PmtDiscToleranceDate">Payment discount tolerance date for extended discount availability.</param>
    /// <param name="RemPmtDiscPossible">Remaining payment discount amount available for the application.</param>
    procedure ApplyVendLedgEntry(AppliedPmtEntry: Record "Applied Payment Entry"; AppliesToID: Code[50]; PostingDate: Date; PmtDiscDueDate: Date; PmtDiscToleranceDate: Date; RemPmtDiscPossible: Decimal)
    var
        VendLedgEntry: Record "Vendor Ledger Entry";
        CurrExchRate: Record "Currency Exchange Rate";
        ApplyVendLedgEntryHandled: Boolean;
    begin
        VendLedgEntry.Get(AppliedPmtEntry."Applies-to Entry No.");
        VendLedgEntry.TestField(Open);
        BankAcc.Get(AppliedPmtEntry."Bank Account No.");

        ApplyVendLedgEntryHandled := false;
        OnBeforeApplyVendLedgEntry(VendLedgEntry, AppliedPmtEntry, BankAcc, AppliesToID, PostingDate, PmtDiscDueDate, PmtDiscToleranceDate, RemPmtDiscPossible, ApplyVendLedgEntryHandled);
        if not ApplyVendLedgEntryHandled then
            if AppliesToID = '' then begin
                VendLedgEntry."Pmt. Discount Date" := PmtDiscDueDate;
                VendLedgEntry."Pmt. Disc. Tolerance Date" := PmtDiscToleranceDate;

                VendLedgEntry."Remaining Pmt. Disc. Possible" := RemPmtDiscPossible;
                if BankAcc.IsInLocalCurrency() then
                    VendLedgEntry."Remaining Pmt. Disc. Possible" :=
                      CurrExchRate.ExchangeAmount(VendLedgEntry."Remaining Pmt. Disc. Possible", '', VendLedgEntry."Currency Code", PostingDate);
            end else begin
                VendLedgEntry."Applies-to ID" := AppliesToID;
                VendLedgEntry."Amount to Apply" := AppliedPmtEntry.CalcAmountToApply(PostingDate);
            end;

        if PreviewMode then
            VendEntryEditNoCommit(VendLedgEntry)
        else
            CODEUNIT.Run(CODEUNIT::"Vend. Entry-Edit", VendLedgEntry);
    end;

    [CommitBehavior(CommitBehavior::Ignore)]
    local procedure VendEntryEditNoCommit(var VendorLedgerEntry: Record "Vendor Ledger Entry")
    begin
        CODEUNIT.Run(CODEUNIT::"Vend. Entry-Edit", VendorLedgerEntry);
    end;

    procedure ApplyEmployeeLedgEntry(AppliedPmtEntry: Record "Applied Payment Entry"; AppliesToID: Code[50]; PostingDate: Date; PmtDiscDueDate: Date; PmtDiscToleranceDate: Date; RemPmtDiscPossible: Decimal)
    var
        EmployeeLedgerEntry: Record "Employee Ledger Entry";
    begin
        EmployeeLedgerEntry.Get(AppliedPmtEntry."Applies-to Entry No.");
        EmployeeLedgerEntry.TestField(Open);
        BankAcc.Get(AppliedPmtEntry."Bank Account No.");
        if AppliesToID <> '' then begin
            EmployeeLedgerEntry."Applies-to ID" := AppliesToID;
            EmployeeLedgerEntry."Amount to Apply" := AppliedPmtEntry.CalcAmountToApply(PostingDate);
        end;

        if PreviewMode then
            EmplEntryEditNoCommit(EmployeeLedgerEntry)
        else
            CODEUNIT.Run(CODEUNIT::"Empl. Entry-Edit", EmployeeLedgerEntry);
    end;

    [CommitBehavior(CommitBehavior::Ignore)]
    local procedure EmplEntryEditNoCommit(var EmployeeLedgerEntry: Record "Employee Ledger Entry")
    begin
        CODEUNIT.Run(CODEUNIT::"Empl. Entry-Edit", EmployeeLedgerEntry);
    end;

    local procedure CloseBankAccountLedgerEntry(EntryNo: Integer; AppliedAmount: Decimal; StatementDate: Date; StatementLineNo: Integer)
    var
        BankAccountLedgerEntry: Record "Bank Account Ledger Entry";
        CheckLedgerEntry: Record "Check Ledger Entry";
    begin
        BankAccountLedgerEntry.Get(EntryNo);
        BankAccountLedgerEntry.TestField(Open);
        BankAccountLedgerEntry.TestField("Remaining Amount", AppliedAmount);
        BankAccountLedgerEntry."Remaining Amount" := 0;
        BankAccountLedgerEntry.Open := false;
        BankAccountLedgerEntry."Statement Status" := BankAccountLedgerEntry."Statement Status"::Closed;
        BankAccountLedgerEntry."Closed at Date" := StatementDate;
        BankAccountLedgerEntry."Statement No." := PostedStamentNo;
        BankAccountLedgerEntry."Statement Line No." := StatementLineNo;
        BankAccountLedgerEntry.Modify();

        CheckLedgerEntry.Reset();
        CheckLedgerEntry.SetCurrentKey("Bank Account Ledger Entry No.");
        CheckLedgerEntry.SetRange(
          "Bank Account Ledger Entry No.", BankAccountLedgerEntry."Entry No.");
        CheckLedgerEntry.SetRange(Open, true);
        if CheckLedgerEntry.FindSet() then
            repeat
                CheckLedgerEntry.Open := false;
                CheckLedgerEntry."Statement Status" := CheckLedgerEntry."Statement Status"::Closed;
                CheckLedgEntry."Statement No." := PostedStamentNo;
                CheckLedgEntry."Statement Line No." := StatementLineNo;
                CheckLedgerEntry.Modify();
            until CheckLedgerEntry.Next() = 0;
    end;

    local procedure GetNextStatementNoAndUpdateBankAccount(BankAccountNo: Code[20]): Code[20]
    var
        BankAccount: Record "Bank Account";
    begin
        BankAccount.SetLoadFields("Last Statement No.");
        BankAccount.Get(BankAccountNo);
        if BankAccount."Last Statement No." <> '' then
            BankAccount."Last Statement No." := IncStr(BankAccount."Last Statement No.")
        else
            BankAccount."Last Statement No." := '1';
        BankAccount.Modify();
        exit(BankAccount."Last Statement No.");
    end;

    local procedure IsRefund(BankAccReconLine: Record "Bank Acc. Reconciliation Line"): Boolean
    begin
        if (BankAccReconLine."Account Type" = BankAccReconLine."Account Type"::Customer) and (BankAccReconLine."Statement Amount" < 0) or
            (BankAccReconLine."Account Type" in [BankAccReconLine."Account Type"::Vendor, BankAccReconLine."Account Type"::Employee]) and (BankAccReconLine."Statement Amount" > 0)
        then
            exit(true);
        exit(false);
    end;

    procedure Preview(var BankAccReconciliationSource: Record "Bank Acc. Reconciliation")
    var
        BankAccReconPostPreview: Codeunit "Bank. Acc. Recon. Post Preview";
        BankAccountReconciliationPost: Codeunit "Bank Acc. Reconciliation Post";
    begin
        BankAccReconPostPreview.Preview(BankAccountReconciliationPost, BankAccReconciliationSource);
    end;

    local procedure InsertEmptyBankAccReconLine(BankAccReconciliation: Record "Bank Acc. Reconciliation")
    var
        BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line";
    begin
        BankAccReconciliationLine.Init();
        BankAccReconciliationLine."Bank Account No." := BankAccReconciliation."Bank Account No.";
        BankAccReconciliationLine."Statement No." := BankAccReconciliation."Statement No.";
        BankAccReconciliationLine."Statement Line No." := 10000;
        BankAccReconciliationLine."Transaction Date" := BankAccReconciliation."Statement Date";
        BankAccReconciliationLine.Description := EmptyDescriptionTxt;
        BankAccReconciliationLine.Validate("Statement Amount", 0);
        BankAccReconciliationLine.Insert();
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Bank. Acc. Recon. Post Preview", 'OnRunPreview', '', false, false)]
    local procedure OnRunPreview(var Result: Boolean; var Subscriber: Codeunit "Bank Acc. Reconciliation Post"; var BankAccReconciliationSource: Record "Bank Acc. Reconciliation")
    var
        BankAccReconciliation: Record "Bank Acc. Reconciliation";
        BankAccountReconciliationPost: Codeunit "Bank Acc. Reconciliation Post";
    begin
        BankAccountReconciliationPost := Subscriber;
        BankAccReconciliation.Copy(BankAccReconciliationSource);
        Result := not BankAccountReconciliationPost.RunPreview(BankAccReconciliation);
    end;

    /// <summary>
    /// Event raised before checking the applied amount for a bank account reconciliation line.
    /// </summary>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    /// <param name="AppliedAmount">The applied amount being checked.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeAppliedAmountCheck(var BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; var AppliedAmount: Decimal)
    begin
    end;

    /// <summary>
    /// Event raised before inserting a bank account statement record during posting.
    /// </summary>
    /// <param name="BankAccStatement">The bank account statement record being inserted.</param>
    /// <param name="BankAccReconciliation">The bank account reconciliation record being posted.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeBankAccStmtInsert(var BankAccStatement: Record "Bank Account Statement"; BankAccReconciliation: Record "Bank Acc. Reconciliation")
    begin
    end;

    /// <summary>
    /// Event raised before closing a bank account ledger entry during reconciliation posting.
    /// </summary>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    /// <param name="AppliedAmount">The applied amount.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCloseBankAccLedgEntry(var BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; var AppliedAmount: Decimal; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised before finalizing the posting process for a bank account reconciliation.
    /// </summary>
    /// <param name="BankAccReconciliation">The bank account reconciliation record being finalized.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeFinalizePost(var BankAccReconciliation: Record "Bank Acc. Reconciliation")
    begin
    end;

    /// <summary>
    /// Event raised before deleting related records during bank account reconciliation posting.
    /// </summary>
    /// <param name="BankAccReconciliation">The bank account reconciliation record.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeDeleteRelatedRecords(BankAccReconciliation: Record "Bank Acc. Reconciliation"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised before posting payment applications for a bank account reconciliation line.
    /// </summary>
    /// <param name="BankAccReconLine">The bank account reconciliation line record.</param>
    /// <param name="AppliedAmount">The applied amount.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforePostPaymentApplications(BankAccReconLine: Record "Bank Acc. Reconciliation Line"; var AppliedAmount: Decimal; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised after finalizing the posting process for a bank account reconciliation.
    /// </summary>
    /// <param name="BankAccReconciliation">The bank account reconciliation record that was finalized.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterFinalizePost(var BankAccReconciliation: Record "Bank Acc. Reconciliation")
    begin
    end;

    /// <summary>
    /// Event raised before initializing the posting process for a bank account reconciliation.
    /// </summary>
    /// <param name="BankAccReconciliation">The bank account reconciliation record being initialized.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeInitPost(var BankAccReconciliation: Record "Bank Acc. Reconciliation")
    begin
    end;

    /// <summary>
    /// Event raised before posting a bank account reconciliation.
    /// </summary>
    /// <param name="BankAccReconciliation">The bank account reconciliation record being posted.</param>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforePost(var BankAccReconciliation: Record "Bank Acc. Reconciliation"; var BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line")
    begin
    end;

    /// <summary>
    /// Event raised before inserting a posted payment reconciliation header record.
    /// </summary>
    /// <param name="PostedPaymentReconHdr">The posted payment reconciliation header record being inserted.</param>
    /// <param name="BankAccReconciliation">The bank account reconciliation record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforePostedPmtReconInsert(var PostedPaymentReconHdr: Record "Posted Payment Recon. Hdr"; BankAccReconciliation: Record "Bank Acc. Reconciliation")
    begin
    end;

    /// <summary>
    /// Event raised before transferring to posted payment application during reconciliation posting.
    /// </summary>
    /// <param name="BankAccReconciliation">The bank account reconciliation record.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransferToPostPmtAppln(var BankAccReconciliation: Record "Bank Acc. Reconciliation"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised before transferring to bank statement during reconciliation posting.
    /// </summary>
    /// <param name="BankAccRecon">The bank account reconciliation record.</param>
    /// <param name="PrePostingOutstdPayments">The pre-posting outstanding payments amount.</param>
    /// <param name="PrePostingOutstdBankTransactions">The pre-posting outstanding bank transactions amount.</param>
    /// <param name="PrePostingGLBalance">The pre-posting general ledger balance.</param>
    /// <param name="PrePostingTotalPositiveDifference">The pre-posting total positive difference.</param>
    /// <param name="PrePostingTotalNegativeDifference">The pre-posting total negative difference.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransferToBankStmt(var BankAccRecon: Record "Bank Acc. Reconciliation"; PrePostingOutstdPayments: Decimal; PrePostingOutstdBankTransactions: Decimal; PrePostingGLBalance: Decimal; PrePostingTotalPositiveDifference: Decimal; PrePostingTotalNegativeDifference: Decimal; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised after calculating sums when checking if lines match ending balance.
    /// </summary>
    /// <param name="BankAccReconLine">The bank account reconciliation line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCheckLinesMatchEndingBalanceOnAfterCalcSums(var BankAccReconLine: Record "Bank Acc. Reconciliation Line")
    begin
    end;

    /// <summary>
    /// Event raised before modifying bank account ledger entry when closing bank account ledger entry.
    /// </summary>
    /// <param name="BankAccountLedgerEntry">The bank account ledger entry record being modified.</param>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCloseBankAccLedgEntryOnBeforeBankAccLedgEntryModify(var BankAccountLedgerEntry: Record "Bank Account Ledger Entry"; BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line")
    begin
    end;

    /// <summary>
    /// Event raised before modifying check ledger entry when closing bank account ledger entry.
    /// </summary>
    /// <param name="CheckLedgerEntry">The check ledger entry record being modified.</param>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCloseBankAccLedgEntryOnBeforeCheckLedgEntryModify(var CheckLedgerEntry: Record "Check Ledger Entry"; BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line")
    begin
    end;

    /// <summary>
    /// Event raised after setting filters on bank account ledger entry when closing bank account ledger entry.
    /// </summary>
    /// <param name="BankAccountLedgerEntry">The bank account ledger entry record with filters applied.</param>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCloseBankAccLedgEntryOnAfterBankAccLedgEntrySetFilters(var BankAccountLedgerEntry: Record "Bank Account Ledger Entry"; BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line")
    begin
    end;

    /// <summary>
    /// Event raised before handling payment application transfer during reconciliation posting.
    /// </summary>
    /// <param name="BankAccRecon">The bank account reconciliation record.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeHandlePaymentApplicationTransfer(var BankAccRecon: Record "Bank Acc. Reconciliation"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised after initializing the general journal line when posting payment applications.
    /// </summary>
    /// <param name="GenJournalLine">The general journal line record being initialized.</param>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    /// <param name="IsApplied">Indicates whether the payment has been applied.</param>
    /// <param name="AppliedAmount">The total applied amount.</param>
    /// <param name="PaymentLineAmount">The payment line amount.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostPaymentApplicationsOnAfterInitGenJnlLine(var GenJournalLine: Record "Gen. Journal Line"; BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; var IsApplied: Boolean; var AppliedAmount: Decimal; var PaymentLineAmount: Decimal; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised after posting a general journal line during payment application posting.
    /// </summary>
    /// <param name="GenJournalLine">The general journal line record that was posted.</param>
    /// <param name="GenJnlPostLine">The general journal posting line codeunit instance.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostPaymentApplicationsOnAfterPostGenJnlLine(var GenJournalLine: Record "Gen. Journal Line"; var GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line")
    begin
    end;

    /// <summary>
    /// Event raised after setting filters on bank account ledger entry during payment application posting.
    /// </summary>
    /// <param name="BankAccountLedgerEntry">The bank account ledger entry record with filters applied.</param>
    /// <param name="GenJournalLine">The general journal line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostPaymentApplicationsOnAfterBankAccountLedgerEntrySetFilters(var BankAccountLedgerEntry: Record "Bank Account Ledger Entry"; GenJournalLine: Record "Gen. Journal Line")
    begin
    end;

    /// <summary>
    /// Event raised after checking that transaction is posted and reconciled during payment application posting.
    /// </summary>
    /// <param name="BankAccReconLine">The bank account reconciliation line record.</param>
    /// <param name="AppliedAmount">The applied amount.</param>
    /// <param name="SourceCode">The source code for the posting.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostPaymentApplicationsOnAfterTransactionPostedAndReconciledCheck(BankAccReconLine: Record "Bank Acc. Reconciliation Line"; var AppliedAmount: Decimal; SourceCode: Code[10])
    begin
    end;

    /// <summary>
    /// Event raised before validating apply requirements during payment application posting.
    /// </summary>
    /// <param name="BankAccReconciliationLine">The bank account reconciliation line record.</param>
    /// <param name="GenJournalLine">The general journal line record.</param>
    /// <param name="AppliedAmount">The applied amount.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostPaymentApplicationsOnBeforeValidateApplyRequirements(var BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; var GenJournalLine: Record "Gen. Journal Line"; AppliedAmount: Decimal)
    begin
    end;

    /// <summary>
    /// Event raised for account type case handling not covered by standard customer, vendor, or employee types.
    /// </summary>
    /// <param name="AppliedPaymentEntry">The applied payment entry record.</param>
    /// <param name="GenJournalLine">The general journal line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostPaymentApplicationsOnAccountTypeCaseElse(var AppliedPaymentEntry: Record "Applied Payment Entry"; var GenJournalLine: Record "Gen. Journal Line")
    begin
    end;

    /// <summary>
    /// Event raised after filtering bank account reconciliation lines during posting.
    /// </summary>
    /// <param name="BankAccReconLines">The bank account reconciliation lines record with filters applied.</param>
    /// <param name="BankAccRecon">The bank account reconciliation record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostAfterFilterBankAccRecLines(var BankAccReconLines: Record "Bank Acc. Reconciliation Line"; BankAccRecon: Record "Bank Acc. Reconciliation")
    begin
    end;

    /// <summary>
    /// Event raised before inserting bank account statement line when transferring to bank statement.
    /// </summary>
    /// <param name="BankAccStmtLine">The bank account statement line record being inserted.</param>
    /// <param name="BankAccReconLine">The bank account reconciliation line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferToBankStmtOnBeforeBankAccStmtLineInsert(var BankAccStmtLine: Record "Bank Account Statement Line"; BankAccReconLine: Record "Bank Acc. Reconciliation Line")
    begin
    end;

    /// <summary>
    /// Event raised before updating the bank account during reconciliation posting.
    /// </summary>
    /// <param name="BankAccRecon">The bank account reconciliation record.</param>
    /// <param name="BankAccount">The bank account record being updated.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    /// <param name="Amt">The amount for the update.</param>
    /// <param name="PostedStamentNo">The posted statement number.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeUpdateBank(var BankAccRecon: Record "Bank Acc. Reconciliation"; var BankAccount: Record "Bank Account"; var IsHandled: Boolean; Amt: Decimal; PostedStamentNo: Code[20])
    begin
    end;

    /// <summary>
    /// Event raised before inserting posted payment reconciliation line when transferring to posted payment application.
    /// </summary>
    /// <param name="PostedPmtReconLine">The posted payment reconciliation line record being inserted.</param>
    /// <param name="BankAccReconLine">The bank account reconciliation line record.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferToPostPmtApplnOnBeforePostedPmtReconLineInsert(var PostedPmtReconLine: Record "Posted Payment Recon. Line"; BankAccReconLine: Record "Bank Acc. Reconciliation Line")
    begin
    end;

    /// <summary>
    /// Event raised before applying a customer ledger entry during bank reconciliation posting.
    /// </summary>
    /// <param name="CustLedgerEntry">The customer ledger entry record being applied.</param>
    /// <param name="AppliedPaymentEntry">The applied payment entry record.</param>
    /// <param name="BankAccount">The bank account record.</param>
    /// <param name="AppliesToID">The applies-to identifier for the application.</param>
    /// <param name="PostingDate">The posting date for the application.</param>
    /// <param name="PmtDiscDueDate">The payment discount due date.</param>
    /// <param name="PmtDiscToleranceDate">The payment discount tolerance date.</param>
    /// <param name="RemPmtDiscPossible">The remaining payment discount possible amount.</param>
    /// <param name="IsHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeApplyCustLedgEntry(var CustLedgerEntry: Record "Cust. Ledger Entry"; AppliedPaymentEntry: Record "Applied Payment Entry"; var BankAccount: Record "Bank Account"; AppliesToID: Code[50]; PostingDate: Date; PmtDiscDueDate: Date; PmtDiscToleranceDate: Date; RemPmtDiscPossible: Decimal; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Event raised before applying a vendor ledger entry during bank reconciliation posting.
    /// </summary>
    /// <param name="VendLedgerEntry">The vendor ledger entry record being applied.</param>
    /// <param name="AppliedPaymentEntry">The applied payment entry record.</param>
    /// <param name="BankAccount">The bank account record.</param>
    /// <param name="AppliesToID">The applies-to identifier for the application.</param>
    /// <param name="PostingDate">The posting date for the application.</param>
    /// <param name="PmtDiscDueDate">The payment discount due date.</param>
    /// <param name="PmtDiscToleranceDate">The payment discount tolerance date.</param>
    /// <param name="RemPmtDiscPossible">The remaining payment discount possible amount.</param>
    /// <param name="ApplyVendLedgEntryHandled">Indicates whether the event has been handled.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeApplyVendLedgEntry(var VendLedgerEntry: Record "Vendor Ledger Entry"; AppliedPaymentEntry: Record "Applied Payment Entry"; var BankAccount: Record "Bank Account"; AppliesToID: Code[50]; PostingDate: Date; PmtDiscDueDate: Date; PmtDiscToleranceDate: Date; RemPmtDiscPossible: Decimal; var ApplyVendLedgEntryHandled: Boolean)
    begin
    end;
}