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;
}