Table 1293 Payment Application Proposal, source in 29

Source29

src/Layers/W1/BaseApp/Bank/Reconciliation/PaymentApplicationProposal.Table.al1141 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.Finance.Currency;
using Microsoft.Finance.GeneralLedger.Account;
using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Finance.GeneralLedger.Ledger;
using Microsoft.FixedAssets.FixedAsset;
using Microsoft.HumanResources.Employee;
using Microsoft.HumanResources.Payables;
using Microsoft.Intercompany.Partner;
using Microsoft.Purchases.Payables;
using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Customer;
using Microsoft.Sales.Receivables;

/// <summary>
/// Temporary table for managing payment application proposals during bank reconciliation processes.
/// This table stores potential matches between bank statement lines and ledger entries, allowing
/// users to review, modify, and approve automatic payment applications before final posting.
/// Provides comprehensive proposal details including amounts, currencies, match quality, and
/// supporting documentation for informed decision-making.
/// </summary>
/// <remarks>
/// Key functionality includes proposal validation, amount calculations with currency handling,
/// applied amount tracking, difference management, and integration with multiple ledger entry types
/// (customer, vendor, employee, bank account). Supports both automatic proposal generation and
/// manual proposal creation workflows with comprehensive audit trails and approval mechanisms.
/// Used extensively in payment reconciliation journals for proposal review and application processing.
/// </remarks>
table 1293 "Payment Application Proposal"
{
    Caption = 'Payment Application Proposal';
    DataClassification = CustomerContent;

    fields
    {
        /// <summary>
        /// Bank account number for the payment application proposal.
        /// Identifies the bank account where the payment was received or will be processed.
        /// </summary>
        field(1; "Bank Account No."; Code[20])
        {
            Caption = 'Bank Account No.';
            TableRelation = "Bank Account";
        }
        /// <summary>
        /// Statement number from the bank reconciliation being processed.
        /// Links the proposal to the specific bank statement import or reconciliation session.
        /// </summary>
        field(2; "Statement No."; Code[20])
        {
            Caption = 'Statement No.';
            TableRelation = "Bank Acc. Reconciliation"."Statement No." where("Bank Account No." = field("Bank Account No."),
                                                                              "Statement Type" = field("Statement Type"));
        }
        /// <summary>
        /// Line number within the bank statement being processed.
        /// Identifies the specific transaction line for payment application.
        /// </summary>
        field(3; "Statement Line No."; Integer)
        {
            Caption = 'Statement Line No.';
        }
        /// <summary>
        /// Type of statement being processed for payment application.
        /// Determines whether this is a bank reconciliation or payment application workflow.
        /// </summary>
        field(20; "Statement Type"; Enum "Bank Acc. Rec. Stmt. Type")
        {
            Caption = 'Statement Type';
        }
        /// <summary>
        /// Type of account that the payment should be applied to.
        /// Determines the target ledger entry table for payment application.
        /// </summary>
        field(21; "Account Type"; Enum "Gen. Journal Account Type")
        {
            Caption = 'Account Type';
            ToolTip = 'Specifies the type of account that the payment application will be posted to when you post the payment reconciliation journal.';

            trigger OnValidate()
            begin
                VerifyLineIsNotApplied();
            end;
        }
        /// <summary>
        /// Account number for the payment application target.
        /// Identifies the specific customer, vendor, or other account for payment application.
        /// </summary>
        field(22; "Account No."; Code[20])
        {
            Caption = 'Account No.';
            ToolTip = 'Specifies the account number the payment application will be posted to when you post the payment reconciliation journal.';
            TableRelation = if ("Account Type" = const("G/L Account")) "G/L Account" where("Account Type" = const(Posting),
                                                                                          Blocked = const(false))
            else
            if ("Account Type" = const(Customer)) Customer
            else
            if ("Account Type" = const(Vendor)) Vendor
            else
            if ("Account Type" = const("Bank Account")) "Bank Account"
            else
            if ("Account Type" = const("Fixed Asset")) "Fixed Asset"
            else
            if ("Account Type" = const("IC Partner")) "IC Partner";

            trigger OnValidate()
            begin
                VerifyLineIsNotApplied();
            end;
        }
        /// <summary>
        /// Entry number of the ledger entry to apply the payment to.
        /// Links the payment to a specific open customer, vendor, or other ledger entry.
        /// </summary>
        field(23; "Applies-to Entry No."; Integer)
        {
            Caption = 'Applies-to Entry No.';
            ToolTip = 'Specifies the number of the customer or vendor ledger entry that the payment will be applied to when you post the payment reconciliation journal line.';
            TableRelation = if ("Account Type" = const("G/L Account")) "G/L Entry"
            else
            if ("Account Type" = const(Customer)) "Cust. Ledger Entry" where(Open = const(true))
            else
            if ("Account Type" = const(Vendor)) "Vendor Ledger Entry" where(Open = const(true))
            else
            if ("Account Type" = const("Bank Account")) "Bank Account Ledger Entry" where(Open = const(true));
        }
        /// <summary>
        /// Amount to apply from the payment to the target ledger entry.
        /// Can be partial amount allowing for split applications across multiple entries.
        /// </summary>
        field(24; "Applied Amount"; Decimal)
        {
            Caption = 'Applied Amount';
            AutoFormatExpression = Rec."Currency Code";
            AutoFormatType = 1;

            trigger OnValidate()
            begin
                if ("Applied Amount" = 0) and (xRec."Applied Amount" <> 0) then
                    Unapply()
                else
                    UpdateAppliedAmt();
            end;
        }
        /// <summary>
        /// Indicates whether the payment application proposal has been applied.
        /// Controls the application state and enables unapplication functionality.
        /// </summary>
        field(25; Applied; Boolean)
        {
            Caption = 'Applied';
            ToolTip = 'Specifies that the payment specified on the header of the Payment Application window is applied to the open entry.';

            trigger OnValidate()
            var
                BankAccReconLine: Record "Bank Acc. Reconciliation Line";
            begin
                if xRec.Applied = Applied then
                    exit;

                if not Applied then
                    Unapply();

                if Applied then begin
                    if "Document Type" = "Document Type"::"Credit Memo" then
                        CrMemoSelectedToApply()
                    else begin
                        BankAccReconLine.Get("Statement Type", "Bank Account No.", "Statement No.", "Statement Line No.");
                        if BankAccReconLine.Difference = 0 then
                            Error(PaymentAppliedErr);
                        ValidateEntryNotApplied(Rec, BankAccReconLine);
                    end;
                    Apply(GetRemainingAmountAfterPosting(), "Applies-to Entry No." <> 0);
                end;
            end;
        }
        /// <summary>
        /// Payment discount amount applied during the payment application.
        /// Reduces the payment amount when early payment discounts are taken.
        /// </summary>
        field(29; "Applied Pmt. Discount"; Decimal)
        {
            AutoFormatExpression = Rec."Currency Code";
            Caption = 'Applied Pmt. Discount';
            AutoFormatType = 1;
        }
        /// <summary>
        /// Quality score for the automatic matching proposal.
        /// Higher values indicate better matching confidence based on matching criteria.
        /// </summary>
        field(30; Quality; Integer)
        {
            Caption = 'Quality';
        }
        /// <summary>
        /// Original posting date of the target ledger entry.
        /// Used for matching and validation during payment application processing.
        /// </summary>
        field(31; "Posting Date"; Date)
        {
            Caption = 'Posting Date';
            ToolTip = 'Specifies the posting date of the open entry.';
        }
        /// <summary>
        /// Document type of the target ledger entry.
        /// Determines application behavior and business logic for different transaction types.
        /// </summary>
        field(32; "Document Type"; Enum "Gen. Journal Document Type")
        {
            Caption = 'Document Type';
            ToolTip = 'Specifies the type of document that is related to the open entry.';
        }
        /// <summary>
        /// Document number of the target ledger entry.
        /// Used for reference matching and identification during payment application.
        /// </summary>
        field(33; "Document No."; Code[20])
        {
            Caption = 'Document No.';
            ToolTip = 'Specifies the number of the document that is related to the open entry.';
        }
        /// <summary>
        /// Description from the target ledger entry.
        /// Provides context and identification information for payment application.
        /// </summary>
        field(34; Description; Text[100])
        {
            Caption = 'Description';
            ToolTip = 'Specifies the description of the open entry.';
        }
        /// <summary>
        /// Currency code for the payment application proposal.
        /// Must match the currency of both payment and target ledger entry.
        /// </summary>
        field(35; "Currency Code"; Code[10])
        {
            Caption = 'Currency Code';
            ToolTip = 'Specifies the currency code of the open entry.';
            Editable = false;
            TableRelation = Currency;
        }
        /// <summary>
        /// Due date of the target ledger entry.
        /// Used for payment discount calculations and aging analysis.
        /// </summary>
        field(36; "Due Date"; Date)
        {
            Caption = 'Due Date';
            ToolTip = 'Specifies the due date of the open entry.';
            Editable = false;
        }
        /// <summary>
        /// External document number from the target ledger entry.
        /// Used for matching with external references in payment data.
        /// </summary>
        field(37; "External Document No."; Code[35])
        {
            Caption = 'External Document No.';
            ToolTip = 'Specifies a document number that refers to the customer''s or vendor''s numbering system.';
        }
        /// <summary>
        /// Confidence level for the automatic matching proposal.
        /// Indicates the system's confidence in the accuracy of the proposed application.
        /// </summary>
        field(50; "Match Confidence"; Enum "Bank Rec. Match Confidence")
        {
            Caption = 'Match Confidence';
            ToolTip = 'Specifies the quality of the match between the payment and the open entry for payment application purposes.';
            Editable = false;
            InitValue = "None";
        }
        field(51; "Pmt. Disc. Due Date"; Date)
        {
            Caption = 'Pmt. Disc. Due Date';
            ToolTip = 'Specifies the date on which the remaining amount on the open entry must be paid to grant a discount.';

            trigger OnValidate()
            begin
                ChangeDiscountAmounts();
            end;
        }
        /// <summary>
        /// Specifies the remaining payment discount amount that can still be utilized.
        /// </summary>
        field(52; "Remaining Pmt. Disc. Possible"; Decimal)
        {
            Caption = 'Remaining Pmt. Disc. Possible';
            ToolTip = 'Specifies how much discount you can grant for the payment if you apply it to the open entry.';
            AutoFormatExpression = Rec."Currency Code";
            AutoFormatType = 1;

            trigger OnValidate()
            begin
                ChangeDiscountAmounts();
            end;
        }
        /// <summary>
        /// Specifies the payment discount tolerance date for the payment application.
        /// </summary>
        field(53; "Pmt. Disc. Tolerance Date"; Date)
        {
            Caption = 'Pmt. Disc. Tolerance Date';
            ToolTip = 'Specifies the latest date the amount in the entry must be paid in order for payment discount tolerance to be granted.';

            trigger OnValidate()
            begin
                ChangeDiscountAmounts();
            end;
        }
        /// <summary>
        /// Specifies the applied amount including any payment discount applied.
        /// </summary>
        field(60; "Applied Amt. Incl. Discount"; Decimal)
        {
            Caption = 'Applied Amt. Incl. Discount';
            ToolTip = 'Specifies the payment amount, excluding the value in the Applied Pmt. Discount field, that is applied to the open entry.';
            AutoFormatExpression = Rec."Currency Code";
            AutoFormatType = 1;

            trigger OnValidate()
            begin
                if ("Applied Amt. Incl. Discount" = 0) and Applied then
                    Unapply()
                else
                    Validate("Applied Amount", "Applied Amt. Incl. Discount");
            end;
        }
        /// <summary>
        /// Specifies the remaining amount after application of this payment proposal.
        /// </summary>
        field(61; "Remaining Amount"; Decimal)
        {
            Caption = 'Remaining Amount';
            ToolTip = 'Specifies the amount that remains to be paid for the open entry.';
            Editable = false;
            AutoFormatExpression = Rec."Currency Code";
            AutoFormatType = 1;
        }
        /// <summary>
        /// Specifies the remaining amount including any available payment discount.
        /// </summary>
        field(62; "Remaining Amt. Incl. Discount"; Decimal)
        {
            Caption = 'Remaining Amt. Incl. Discount';
            ToolTip = 'Specifies the amount that remains to be paid for the open entry, minus any granted payment discount.';
            Editable = false;
            AutoFormatExpression = Rec."Currency Code";
            AutoFormatType = 1;
        }
        /// <summary>
        /// Specifies the type of ledger entry being applied to in this payment proposal.
        /// </summary>
        field(63; Type; Option)
        {
            Caption = 'Type';
            OptionCaption = 'Bank Account Ledger Entry,Check Ledger Entry';
            OptionMembers = "Bank Account Ledger Entry","Check Ledger Entry";
        }
        /// <summary>
        /// Specifies the sorting order for displaying payment application proposals.
        /// </summary>
        field(100; "Sorting Order"; Integer)
        {
            Caption = 'Sorting Order';
            Editable = false;
        }
        /// <summary>
        /// Specifies the difference between the statement amount and the remaining amount after application.
        /// </summary>
        field(101; "Stmt To Rem. Amount Difference"; Decimal)
        {
            Caption = 'Stmt To Rem. Amount Difference';
            Editable = false;
            AutoFormatExpression = Rec."Currency Code";
            AutoFormatType = 1;
        }
    }

    keys
    {
        key(Key1; "Statement Type", "Bank Account No.", "Statement No.", "Statement Line No.", "Account Type", "Account No.", "Applies-to Entry No.")
        {
            Clustered = true;
        }
        key(Key2; Applied, Quality)
        {
        }
        key(Key3; "Sorting Order")
        {
        }
        key(Key4; Applied, "Account Type", "Account No.", Type)
        {
        }
    }

    fieldgroups
    {
    }

    trigger OnDelete()
    begin
        TestField("Applies-to Entry No.", 0);
        if Applied then
            Unapply();
    end;

    trigger OnInsert()
    begin
        UpdateSortingOrder();
    end;

    trigger OnModify()
    begin
        UpdateSortingOrder();
    end;

    trigger OnRename()
    begin
        VerifyLineIsNotApplied();
    end;

    var
        StmtAmtIsFullyAppliedErr: Label 'The statement amount is already fully applied.';
        EntryDoesntExistErr: Label 'The entry does not exist.';
        CannotChangeAppliedLineErr: Label 'You cannot change the line because the entry is applied. Remove the applied entry first.';
        TransactionDateIsBeforePostingDateMsg: Label 'The transaction date %1 is before the posting date %2.', Comment = '%1 Transaction Date; %2: Posting Date';
        PaymentAppliedErr: Label 'The payment is fully applied. To apply the payment to this entry, you must first unapply the payment from another entry.';
        WantToApplyCreditMemoAndInvoicesMsg: Label 'If you want to apply credit memos and invoices, we recommend that you start by applying credit memos and then apply all others entries.';
        EntryAlreadyHasAnApplicationErr: Label 'This entry has an ongoing application process ''%1'', it is applied in another journal. Process this journal before proceeding.', Comment = '%1 a code for the payment application process';

    local procedure Unapply()
    var
        AppliedPmtEntry: Record "Applied Payment Entry";
    begin
        if not GetAppliedPaymentEntry(AppliedPmtEntry) then
            Error(EntryDoesntExistErr);

        AppliedPmtEntry.Delete(true);

        TransferFields(AppliedPmtEntry, false);
        Applied := false;

        "Applied Amt. Incl. Discount" := 0;

        Modify(true);
    end;

    local procedure UpdateAppliedAmt()
    var
        AmountToApply: Decimal;
    begin
        AmountToApply := "Applied Amount";
        if Applied then
            Unapply();

        if AmountToApply = 0 then
            exit;

        Apply(AmountToApply, false);
    end;

    procedure GetAppliedPaymentEntry(var AppliedPaymentEntry: Record "Applied Payment Entry"): Boolean
    begin
        exit(
          AppliedPaymentEntry.Get(
            "Statement Type", "Bank Account No.",
            "Statement No.", "Statement Line No.",
            "Account Type", "Account No.", "Applies-to Entry No."));
    end;

    local procedure GetLedgEntryInfo()
    var
        TempAppliedPmtEntry: Record "Applied Payment Entry" temporary;
    begin
        TempAppliedPmtEntry.TransferFields(Rec);
        TempAppliedPmtEntry.GetLedgEntryInfo();
        TransferFields(TempAppliedPmtEntry);
    end;

    local procedure LoadLedgEntryInfoAndRemainingAmount(BankAccount: Record "Bank Account")
    var
        CustLedgEntry: Record "Cust. Ledger Entry";
        VendLedgEntry: Record "Vendor Ledger Entry";
        EmployeeLedgEntry: Record "Employee Ledger Entry";
        BankAccLedgEntry: Record "Bank Account Ledger Entry";
        LedgerRemainingAmount: Decimal;
        IsHandled: Boolean;
        RemainingAmountHandled: Boolean;
        LedgEntryInfoLoaded: Boolean;
    begin
        // Reads the applied ledger entry only once to populate both the informational fields and the remaining amount.
        IsHandled := false;
        OnBeforeLoadLedgEntryInfoAndRemainingAmount(Rec, BankAccount, IsHandled);
        if IsHandled then begin
            GetLedgEntryInfo();
            exit;
        end;

        "Remaining Amount" := 0;
        if "Applies-to Entry No." = 0 then
            exit;

        LedgEntryInfoLoaded := true;
        case "Account Type" of
            "Account Type"::Customer:
                begin
                    CustLedgEntry.SetLoadFields(
                      Description, "Posting Date", "Due Date", "Document Type", "Document No.", "External Document No.", "Currency Code");
                    if BankAccount.IsInLocalCurrency() then
                        CustLedgEntry.SetAutoCalcFields("Remaining Amt. (LCY)")
                    else
                        CustLedgEntry.SetAutoCalcFields("Remaining Amount");
                    CustLedgEntry.Get("Applies-to Entry No.");
                    Description := CustLedgEntry.Description;
                    "Posting Date" := CustLedgEntry."Posting Date";
                    "Due Date" := CustLedgEntry."Due Date";
                    "Document Type" := CustLedgEntry."Document Type";
                    "Document No." := CustLedgEntry."Document No.";
                    "External Document No." := CustLedgEntry."External Document No.";
                    "Currency Code" := CustLedgEntry."Currency Code";
                    if BankAccount.IsInLocalCurrency() then
                        LedgerRemainingAmount := CustLedgEntry."Remaining Amt. (LCY)"
                    else
                        LedgerRemainingAmount := CustLedgEntry."Remaining Amount";
                end;
            "Account Type"::Vendor:
                begin
                    VendLedgEntry.SetLoadFields(
                      Description, "Posting Date", "Due Date", "Document Type", "Document No.", "External Document No.", "Currency Code");
                    if BankAccount.IsInLocalCurrency() then
                        VendLedgEntry.SetAutoCalcFields("Remaining Amt. (LCY)")
                    else
                        VendLedgEntry.SetAutoCalcFields("Remaining Amount");
                    VendLedgEntry.Get("Applies-to Entry No.");
                    Description := VendLedgEntry.Description;
                    "Posting Date" := VendLedgEntry."Posting Date";
                    "Due Date" := VendLedgEntry."Due Date";
                    "Document Type" := VendLedgEntry."Document Type";
                    "Document No." := VendLedgEntry."Document No.";
                    "External Document No." := VendLedgEntry."External Document No.";
                    "Currency Code" := VendLedgEntry."Currency Code";
                    if BankAccount.IsInLocalCurrency() then
                        LedgerRemainingAmount := VendLedgEntry."Remaining Amt. (LCY)"
                    else
                        LedgerRemainingAmount := VendLedgEntry."Remaining Amount";
                end;
            "Account Type"::Employee:
                begin
                    EmployeeLedgEntry.SetLoadFields(
                      Description, "Posting Date", "Document Type", "Document No.", "Currency Code");
                    if BankAccount.IsInLocalCurrency() then
                        EmployeeLedgEntry.SetAutoCalcFields("Remaining Amt. (LCY)")
                    else
                        EmployeeLedgEntry.SetAutoCalcFields("Remaining Amount");
                    EmployeeLedgEntry.Get("Applies-to Entry No.");
                    Description := EmployeeLedgEntry.Description;
                    "Posting Date" := EmployeeLedgEntry."Posting Date";
                    "Document Type" := EmployeeLedgEntry."Document Type";
                    "Document No." := EmployeeLedgEntry."Document No.";
                    "Currency Code" := EmployeeLedgEntry."Currency Code";
                    if BankAccount.IsInLocalCurrency() then
                        LedgerRemainingAmount := EmployeeLedgEntry."Remaining Amt. (LCY)"
                    else
                        LedgerRemainingAmount := EmployeeLedgEntry."Remaining Amount";
                end;
            "Account Type"::"Bank Account":
                begin
                    BankAccLedgEntry.SetLoadFields(
                      Description, "Posting Date", "Document Type", "Document No.", "External Document No.", "Currency Code", "Remaining Amount");
                    BankAccLedgEntry.Get("Applies-to Entry No.");
                    Description := BankAccLedgEntry.Description;
                    "Posting Date" := BankAccLedgEntry."Posting Date";
                    "Due Date" := 0D;
                    "Document Type" := BankAccLedgEntry."Document Type";
                    "Document No." := BankAccLedgEntry."Document No.";
                    "External Document No." := BankAccLedgEntry."External Document No.";
                    "Currency Code" := BankAccLedgEntry."Currency Code";
                    LedgerRemainingAmount := BankAccLedgEntry."Remaining Amount";
                end;
            else begin
                GetLedgEntryInfo();
                LedgEntryInfoLoaded := false;
            end;
        end;

        // Re-fire the legacy OnAfterGetLedgEntryInfo extension point for the fast path without a second ledger read.
        if LedgEntryInfoLoaded then
            RaiseOnAfterGetLedgEntryInfoEvent();

        // Keep firing the legacy event so extensions that override the remaining amount still run when a proposal is created.
        RemainingAmountHandled := false;
        OnBeforeUpdateRemainingAmount(Rec, BankAccount, RemainingAmountHandled);
        if not RemainingAmountHandled then
            "Remaining Amount" := LedgerRemainingAmount;
    end;

    local procedure RaiseOnAfterGetLedgEntryInfoEvent()
    var
        TempAppliedPmtEntry: Record "Applied Payment Entry" temporary;
    begin
        // Round-trips already-loaded fields through a temp Applied Payment Entry so existing OnAfterGetLedgEntryInfo subscribers still run.
        TempAppliedPmtEntry.TransferFields(Rec);
        TempAppliedPmtEntry.RunOnAfterGetLedgEntryInfo();
        TransferFields(TempAppliedPmtEntry);
    end;

    procedure TransferFromBankAccReconLine(BankAccReconLine: Record "Bank Acc. Reconciliation Line")
    begin
        "Statement Type" := BankAccReconLine."Statement Type";
        "Bank Account No." := BankAccReconLine."Bank Account No.";
        "Statement No." := BankAccReconLine."Statement No.";
        "Statement Line No." := BankAccReconLine."Statement Line No.";
    end;

    procedure CreateFromAppliedPaymentEntry(AppliedPaymentEntry: Record "Applied Payment Entry")
    var
        BankAccount: Record "Bank Account";
    begin
        Init();
        TransferFields(AppliedPaymentEntry);
        UpdatePaymentDiscInfo();

        if AppliedPaymentEntry."Applied Amount" <> 0 then
            Applied := true;

        BankAccount.Get(AppliedPaymentEntry."Bank Account No.");

        UpdateDefaultCalculatedFields(BankAccount, AppliedPaymentEntry."Applies-to Entry No.");
        "Applied Amt. Incl. Discount" := "Applied Amount" - "Applied Pmt. Discount";
        Insert(true);
    end;

    procedure CreateFromBankStmtMacthingBuffer(TempBankStmtMatchingBuffer: Record "Bank Statement Matching Buffer" temporary; BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; BankAccount: Record "Bank Account")
    var
        BankPmtApplRule: Record "Bank Pmt. Appl. Rule";
    begin
        Init();
        "Account Type" := TempBankStmtMatchingBuffer."Account Type";
        "Account No." := TempBankStmtMatchingBuffer."Account No.";

        if TempBankStmtMatchingBuffer."Entry No." < 0 then
            "Applies-to Entry No." := 0
        else
            "Applies-to Entry No." := TempBankStmtMatchingBuffer."Entry No.";

        LoadLedgEntryInfoAndRemainingAmount(BankAccount);
        Quality := TempBankStmtMatchingBuffer.Quality;
        "Match Confidence" :=
            Enum::"Bank Rec. Match Confidence".FromInteger(BankPmtApplRule.GetMatchConfidence(TempBankStmtMatchingBuffer.Quality));

        UpdateDefaultCalculatedFields(BankAccount, Rec."Applies-to Entry No.", true);

        "Stmt To Rem. Amount Difference" := Abs(BankAccReconciliationLine."Statement Amount" - "Remaining Amount");
        "Applied Amt. Incl. Discount" := "Applied Amount" - "Applied Pmt. Discount";

        OnAfterCreateFromBankStmtMacthingBuffer(Rec, TempBankStmtMatchingBuffer, BankAccReconciliationLine, BankAccount);
    end;

    procedure UpdateDefaultCalculatedFields(var BankAccount: Record "Bank Account"; AppliesToEntryNo: Integer)
    begin
        UpdateDefaultCalculatedFields(BankAccount, AppliesToEntryNo, false);
    end;

    procedure UpdateDefaultCalculatedFields(var BankAccount: Record "Bank Account"; AppliesToEntryNo: Integer; SkipRemainingAmountUpdate: Boolean)
    begin
        UpdatePaymentDiscInfo();
        if not SkipRemainingAmountUpdate then
            UpdateRemainingAmount(BankAccount);
        UpdateRemainingAmountExclDiscount();

        if AppliesToEntryNo > 0 then
            UpdateTypeOption(AppliesToEntryNo);
    end;

    local procedure UpdateSortingOrder()
    var
        BankPmtApplRule: Record "Bank Pmt. Appl. Rule";
    begin
        "Sorting Order" := -Quality;
        if Applied then
            "Sorting Order" -= BankPmtApplRule.GetHighestPossibleScore();
    end;

    local procedure Apply(AmtToApply: Decimal; SuggestDiscAmt: Boolean)
    var
        AppliedPaymentEntry: Record "Applied Payment Entry";
        BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line";
        MatchBankPayments: Codeunit "Match Bank Payments";
        ShowMessage: Boolean;
    begin
        AppliedPaymentEntry.TransferFields(Rec);
        BankAccReconciliationLine.Get(
          AppliedPaymentEntry."Statement Type", AppliedPaymentEntry."Bank Account No.",
          AppliedPaymentEntry."Statement No.", AppliedPaymentEntry."Statement Line No.");
        MatchBankPayments.SetApplicationDataInCVLedgEntry(
          "Account Type", "Applies-to Entry No.", BankAccReconciliationLine.GetAppliesToID());

        if AmtToApply = 0 then
            Error(StmtAmtIsFullyAppliedErr);

        if SuggestDiscAmt then
            AppliedPaymentEntry.Validate("Applies-to Entry No.")
        else
            AppliedPaymentEntry.Validate("Applied Amount", AmtToApply);

        AppliedPaymentEntry.Insert(true);

        TransferFields(AppliedPaymentEntry);
        Applied := true;
        UpdateRemainingAmountExclDiscount();
        "Applied Amt. Incl. Discount" := "Applied Amount" - "Applied Pmt. Discount";
        Modify(true);

        if BankAccReconciliationLine."Transaction Date" < "Posting Date" then begin
            ShowMessage := true;
            OnApplyOnBeforeShowMessage(BankAccReconciliationLine, "Posting Date", ShowMessage);
            if ShowMessage then
                Message(StrSubstNo(TransactionDateIsBeforePostingDateMsg, BankAccReconciliationLine."Transaction Date", "Posting Date"));
        end;
    end;

    procedure GetRemainingAmountAfterPostingValue(): Decimal
    begin
        if "Applies-to Entry No." = 0 then
            exit(0);

        exit(GetRemainingAmountAfterPosting());
    end;

    local procedure GetRemainingAmountAfterPosting(): Decimal
    var
        TempAppliedPaymentEntry: Record "Applied Payment Entry" temporary;
    begin
        TempAppliedPaymentEntry.TransferFields(Rec);
        exit(
          TempAppliedPaymentEntry.GetRemAmt() -
          TempAppliedPaymentEntry."Applied Amount" -
          TempAppliedPaymentEntry.GetAmtAppliedToOtherStmtLines());
    end;

    procedure RemoveApplications()
    var
        AppliedPaymentEntry: Record "Applied Payment Entry";
        TempPaymentApplicationProposal: Record "Payment Application Proposal" temporary;
    begin
        TempPaymentApplicationProposal := Rec;

        AddFilterOnAppliedPmtEntry(AppliedPaymentEntry);

        if AppliedPaymentEntry.FindSet() then
            repeat
                Get(
                  AppliedPaymentEntry."Statement Type", AppliedPaymentEntry."Bank Account No.",
                  AppliedPaymentEntry."Statement No.", AppliedPaymentEntry."Statement Line No.",
                  AppliedPaymentEntry."Account Type", AppliedPaymentEntry."Account No.",
                  AppliedPaymentEntry."Applies-to Entry No.");
                Unapply();
            until AppliedPaymentEntry.Next() = 0;

        Rec := TempPaymentApplicationProposal;
    end;

    procedure AccountNameDrillDown()
    var
        Customer: Record Customer;
        Vendor: Record Vendor;
        Employee: Record Employee;
        GLAccount: Record "G/L Account";
        BankAccount: Record "Bank Account";
        AccountType: Enum "Gen. Journal Account Type";
        AccountNo: Code[20];
    begin
        AccountType := GetAppliedToAccountType();
        AccountNo := GetAppliedToAccountNo();
        case AccountType of
            "Account Type"::Customer:
                begin
                    Customer.Get(AccountNo);
                    PAGE.Run(PAGE::"Customer Card", Customer);
                end;
            "Account Type"::Vendor:
                begin
                    Vendor.Get(AccountNo);
                    PAGE.Run(PAGE::"Vendor Card", Vendor);
                end;
            "Account Type"::Employee:
                begin
                    Employee.Get(AccountNo);
                    PAGE.Run(PAGE::"Employee Card", Employee);
                end;
            "Account Type"::"G/L Account":
                begin
                    GLAccount.Get(AccountNo);
                    PAGE.Run(PAGE::"G/L Account Card", GLAccount);
                end;
            "Account Type"::"Bank Account":
                begin
                    BankAccount.Get(AccountNo);
                    PAGE.Run(PAGE::"Bank Account Card", BankAccount);
                end;
        end;
    end;

    procedure GetAccountName(): Text
    var
        Customer: Record Customer;
        Vendor: Record Vendor;
        Employee: Record Employee;
        GLAccount: Record "G/L Account";
        BankAccount: Record "Bank Account";
        AccountType: Enum "Gen. Journal Account Type";
        AccountNo: Code[20];
        Name: Text;
    begin
        AccountType := GetAppliedToAccountType();
        AccountNo := GetAppliedToAccountNo();
        Name := '';

        case AccountType of
            "Account Type"::Customer:
                if Customer.Get(AccountNo) then
                    Name := Customer.Name;
            "Account Type"::Vendor:
                if Vendor.Get(AccountNo) then
                    Name := Vendor.Name;
            "Account Type"::Employee:
                if Employee.Get(AccountNo) then
                    Name := Employee.FullName();
            "Account Type"::"G/L Account":
                if GLAccount.Get(AccountNo) then
                    Name := GLAccount.Name;
            "Account Type"::"Bank Account":
                if BankAccount.Get(AccountNo) then
                    Name := BankAccount.Name;
        end;

        exit(Name);
    end;

    local procedure GetAppliedToAccountType(): Enum "Gen. Journal Account Type"
    var
        BankAccountLedgerEntry: Record "Bank Account Ledger Entry";
    begin
        if "Account Type" = "Account Type"::"Bank Account" then
            if BankAccountLedgerEntry.Get("Applies-to Entry No.") then
                exit(BankAccountLedgerEntry."Bal. Account Type");
        exit("Account Type");
    end;

    local procedure GetAppliedToAccountNo(): Code[20]
    var
        BankAccountLedgerEntry: Record "Bank Account Ledger Entry";
    begin
        if "Account Type" = "Account Type"::"Bank Account" then
            if BankAccountLedgerEntry.Get("Applies-to Entry No.") then
                exit(BankAccountLedgerEntry."Bal. Account No.");
        exit("Account No.");
    end;

    local procedure ChangeDiscountAmounts()
    begin
        UpdateLedgEntryDisc();
        UpdateRemainingAmountExclDiscount();

        if "Applied Pmt. Discount" <> 0 then begin
            "Applied Amount" -= "Applied Pmt. Discount";
            "Applied Pmt. Discount" := 0;
        end;

        UpdateAppliedAmt();
    end;

    local procedure UpdateLedgEntryDisc()
    var
        BankAccReconLine: Record "Bank Acc. Reconciliation Line";
        AppliedPmtEntry: Record "Applied Payment Entry";
        BankAccReconPost: Codeunit "Bank Acc. Reconciliation Post";
    begin
        TestField("Applies-to Entry No.");
        AppliedPmtEntry.TransferFields(Rec);
        BankAccReconLine.Get("Statement Type", "Bank Account No.", "Statement No.", "Statement Line No.");

        case "Account Type" of
            "Account Type"::Customer:
                BankAccReconPost.ApplyCustLedgEntry(
                  AppliedPmtEntry, '', BankAccReconLine."Transaction Date",
                  "Pmt. Disc. Due Date", "Pmt. Disc. Tolerance Date", "Remaining Pmt. Disc. Possible");
            "Account Type"::Vendor:
                BankAccReconPost.ApplyVendLedgEntry(
                  AppliedPmtEntry, '', BankAccReconLine."Transaction Date",
                  "Pmt. Disc. Due Date", "Pmt. Disc. Tolerance Date", "Remaining Pmt. Disc. Possible");
        end;
    end;

    local procedure UpdateRemainingAmountExclDiscount()
    var
        BankAccReconLine: Record "Bank Acc. Reconciliation Line";
    begin
        "Remaining Amt. Incl. Discount" := "Remaining Amount";

        if not ("Document Type" in ["Document Type"::"Credit Memo", "Document Type"::Invoice]) then
            exit;

        BankAccReconLine.Get("Statement Type", "Bank Account No.", "Statement No.", "Statement Line No.");
        if BankAccReconLine."Transaction Date" > "Pmt. Disc. Due Date" then
            exit;

        "Remaining Amt. Incl. Discount" -= "Remaining Pmt. Disc. Possible";
    end;

    local procedure UpdateRemainingAmount(BankAccount: Record "Bank Account")
    var
        CustLedgEntry: Record "Cust. Ledger Entry";
        VendLedgEntry: Record "Vendor Ledger Entry";
        EmployeeLedgEntry: Record "Employee Ledger Entry";
        BankAccLedgEntry: Record "Bank Account Ledger Entry";
        RemainingAmount: Decimal;
        RemainingAmountLCY: Decimal;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeUpdateRemainingAmount(Rec, BankAccount, IsHandled);
        if IsHandled then
            exit;

        "Remaining Amount" := 0;

        if "Applies-to Entry No." = 0 then
            exit;

        case "Account Type" of
            "Account Type"::"Bank Account":
                begin
                    BankAccLedgEntry.SetRange(Open, true);
                    BankAccLedgEntry.SetRange("Bank Account No.", "Account No.");
                    BankAccLedgEntry.Get("Applies-to Entry No.");
                    "Remaining Amount" := BankAccLedgEntry."Remaining Amount";
                    exit;
                end;
            "Account Type"::Customer:
                begin
                    CustLedgEntry.SetRange(Open, true);
                    CustLedgEntry.SetRange("Customer No.", "Account No.");
                    CustLedgEntry.SetAutoCalcFields("Remaining Amount", "Remaining Amt. (LCY)");
                    CustLedgEntry.Get("Applies-to Entry No.");

                    RemainingAmount := CustLedgEntry."Remaining Amount";
                    RemainingAmountLCY := CustLedgEntry."Remaining Amt. (LCY)";
                end;
            "Account Type"::Vendor:
                begin
                    VendLedgEntry.SetRange(Open, true);
                    VendLedgEntry.SetRange("Vendor No.", "Account No.");
                    VendLedgEntry.SetAutoCalcFields("Remaining Amount", "Remaining Amt. (LCY)");
                    VendLedgEntry.Get("Applies-to Entry No.");

                    RemainingAmount := VendLedgEntry."Remaining Amount";
                    RemainingAmountLCY := VendLedgEntry."Remaining Amt. (LCY)";
                end;
            "Account Type"::Employee:
                begin
                    EmployeeLedgEntry.SetRange(Open, true);
                    EmployeeLedgEntry.SetRange("Employee No.", "Account No.");
                    EmployeeLedgEntry.SetAutoCalcFields("Remaining Amount", "Remaining Amt. (LCY)");
                    EmployeeLedgEntry.Get("Applies-to Entry No.");

                    RemainingAmount := EmployeeLedgEntry."Remaining Amount";
                    RemainingAmountLCY := EmployeeLedgEntry."Remaining Amt. (LCY)";
                end;
        end;

        if BankAccount.IsInLocalCurrency() then
            "Remaining Amount" := RemainingAmountLCY
        else
            "Remaining Amount" := RemainingAmount;
    end;

    local procedure UpdatePaymentDiscInfo()
    var
        AppliedPaymentEntry: Record "Applied Payment Entry";
    begin
        AppliedPaymentEntry.TransferFields(Rec);
        AppliedPaymentEntry.GetDiscInfo("Pmt. Disc. Due Date", "Pmt. Disc. Tolerance Date", "Remaining Pmt. Disc. Possible");
        if "Remaining Pmt. Disc. Possible" = 0 then begin
            "Pmt. Disc. Due Date" := 0D;
            "Pmt. Disc. Tolerance Date" := 0D;
        end;
    end;

    local procedure VerifyLineIsNotApplied()
    begin
        if Applied then
            Error(CannotChangeAppliedLineErr);
    end;

    procedure AppliesToEntryNoDrillDown()
    var
        CustLedgEntry: Record "Cust. Ledger Entry";
        VendLedgEntry: Record "Vendor Ledger Entry";
        EmployeeLedgEntry: Record "Employee Ledger Entry";
        BankAccLedgEntry: Record "Bank Account Ledger Entry";
        GLEntry: Record "G/L Entry";
    begin
        if "Applies-to Entry No." = 0 then
            exit;

        case "Account Type" of
            "Account Type"::"G/L Account":
                begin
                    GLEntry.SetRange("G/L Account No.", "Account No.");
                    PAGE.RunModal(0, GLEntry);
                end;
            "Account Type"::Customer:
                begin
                    CustLedgEntry.SetRange(Open, true);
                    CustLedgEntry.SetRange("Customer No.", "Account No.");
                    CustLedgEntry.Get("Applies-to Entry No.");
                    PAGE.RunModal(0, CustLedgEntry);
                end;
            "Account Type"::Vendor:
                begin
                    VendLedgEntry.SetRange(Open, true);
                    VendLedgEntry.SetRange("Vendor No.", "Account No.");
                    VendLedgEntry.Get("Applies-to Entry No.");
                    PAGE.RunModal(0, VendLedgEntry);
                end;
            "Account Type"::Employee:
                begin
                    EmployeeLedgEntry.SetRange(Open, true);
                    EmployeeLedgEntry.SetRange("Employee No.", "Account No.");
                    EmployeeLedgEntry.Get("Applies-to Entry No.");
                    PAGE.RunModal(0, EmployeeLedgEntry);
                end;
            "Account Type"::"Bank Account":
                begin
                    BankAccLedgEntry.SetRange(Open, true);
                    BankAccLedgEntry.SetRange("Bank Account No.", "Account No.");
                    BankAccLedgEntry.Get("Applies-to Entry No.");
                    PAGE.RunModal(0, BankAccLedgEntry);
                end;
        end;
    end;

    local procedure CrMemoSelectedToApply()
    var
        AppliedPaymentEntry: Record "Applied Payment Entry";
    begin
        AddFilterOnAppliedPmtEntry(AppliedPaymentEntry);
        if AppliedPaymentEntry.Count > 0 then begin
            AppliedPaymentEntry.SetRange("Document Type", "Document Type"::"Credit Memo");
            if AppliedPaymentEntry.Count = 0 then
                Message(WantToApplyCreditMemoAndInvoicesMsg);
        end;
    end;

    local procedure AddFilterOnAppliedPmtEntry(var AppliedPaymentEntry: Record "Applied Payment Entry")
    begin
        AppliedPaymentEntry.SetRange("Statement Type", "Statement Type");
        AppliedPaymentEntry.SetRange("Bank Account No.", "Bank Account No.");
        AppliedPaymentEntry.SetRange("Statement No.", "Statement No.");
        AppliedPaymentEntry.SetRange("Statement Line No.", "Statement Line No.");
    end;

    local procedure UpdateTypeOption(EntryNo: Integer)
    var
        CheckLedgerEntry: Record "Check Ledger Entry";
    begin
        // Type only distinguishes bank account entries; the check-ledger lookup is skipped for other account types.
        if "Account Type" <> "Account Type"::"Bank Account" then begin
            Type := Type::"Bank Account Ledger Entry";
            exit;
        end;

        CheckLedgerEntry.SetRange("Bank Account Ledger Entry No.", EntryNo);
        if CheckLedgerEntry.FindFirst() then
            Type := Type::"Check Ledger Entry"
        else
            Type := Type::"Bank Account Ledger Entry";
    end;

    local procedure AppliesToIDHasDifferentPrefixAndItsNotEmpty(AppliesToID: Code[50]; AppliesToIDPrefix: Code[50]): Boolean
    begin
        if AppliesToID = '' then
            exit(false);
        exit(CopyStr(AppliestoID, 1, StrLen(AppliesToIDPrefix)) <> AppliesToIDPrefix);
    end;

    local procedure ValidateEntryNotApplied(var PaymentApplicationProposal: Record "Payment Application Proposal"; BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line")
    var
        CustLedgerEntry: Record "Cust. Ledger Entry";
        VendorLedgerEntry: Record "Vendor Ledger Entry";
        EmployeeLedgerEntry: Record "Employee Ledger Entry";
        AppliesToIDPrefix: Code[50];
    begin
        AppliesToIDPrefix := BankAccReconciliationLine.GetAppliesToIDForBankStatement();
        case PaymentApplicationProposal."Account Type" of
            PaymentApplicationProposal."Account Type"::Customer:
                if CustLedgerEntry.Get(PaymentApplicationProposal."Applies-to Entry No.") then
                    if AppliesToIDHasDifferentPrefixAndItsNotEmpty(CustLedgerEntry."Applies-to ID", AppliesToIDPrefix) then
                        Error(EntryAlreadyHasAnApplicationErr, CustLedgerEntry."Applies-to ID");
            PaymentApplicationProposal."Account Type"::Vendor:
                if VendorLedgerEntry.Get(PaymentApplicationProposal."Applies-to Entry No.") then
                    if AppliesToIDHasDifferentPrefixAndItsNotEmpty(VendorLedgerEntry."Applies-to ID", AppliesToIDPrefix) then
                        Error(EntryAlreadyHasAnApplicationErr, VendorLedgerEntry."Applies-to ID");
            PaymentApplicationProposal."Account Type"::Employee:
                if EmployeeLedgerEntry.Get(PaymentApplicationProposal."Applies-to Entry No.") then
                    if AppliesToIDHasDifferentPrefixAndItsNotEmpty(EmployeeLedgerEntry."Applies-to ID", AppliesToIDPrefix) then
                        Error(EntryAlreadyHasAnApplicationErr, EmployeeLedgerEntry."Applies-to ID");
        end;
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterCreateFromBankStmtMacthingBuffer(var PaymentApplicationProposal: Record "Payment Application Proposal"; TempBankStmtMatchingBuffer: Record "Bank Statement Matching Buffer" temporary; BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; BankAccount: Record "Bank Account")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeUpdateRemainingAmount(var PaymentApplicationProposal: Record "Payment Application Proposal"; BankAccount: Record "Bank Account"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeLoadLedgEntryInfoAndRemainingAmount(var PaymentApplicationProposal: Record "Payment Application Proposal"; BankAccount: Record "Bank Account"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnApplyOnBeforeShowMessage(BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; PostingDate: Date; var ShowMessage: Boolean)
    begin
    end;
}