Codeunit 1395 Cancel Issued Fin. Charge Memo, source in 29
Source29
src/Layers/W1/BaseApp/Sales/FinanceCharge/CancelIssuedFinChargeMemo.Codeunit.al429 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.Sales.FinanceCharge;
using Microsoft.Finance.Dimension;
using Microsoft.Finance.GeneralLedger.Account;
using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Finance.GeneralLedger.Posting;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Foundation.AuditCodes;
using Microsoft.Foundation.NoSeries;
using Microsoft.Sales.Customer;
using Microsoft.Sales.Receivables;
using Microsoft.Sales.Setup;
using System.Environment.Configuration;
using System.Utilities;
/// <summary>
/// Cancels issued finance charge memos by reversing posted entries and updating customer ledger entries.
/// </summary>
codeunit 1395 "Cancel Issued Fin. Charge Memo"
{
Permissions = TableData "Cust. Ledger Entry" = rm,
TableData "Reminder/Fin. Charge Entry" = rm,
TableData "Issued Fin. Charge Memo Header" = rm,
TableData "Issued Fin. Charge Memo Line" = rm;
TableNo = "Issued Fin. Charge Memo Header";
trigger OnRun()
begin
if CheckIssuedFinChargeMemo(Rec) then
CancelIssuedFinChargeMemo(Rec);
end;
var
TempGenJnlLine: Record "Gen. Journal Line" temporary;
GenJnlBatch: Record "Gen. Journal Batch";
SourceCodeSetup: Record "Source Code Setup";
TempErrorMessage: Record "Error Message" temporary;
GLSetup: Record "General Ledger Setup";
DimMgt: Codeunit DimensionManagement;
FinChargeMemoSourceCode: Code[10];
TotalAmount: Decimal;
TotalAmountLCY: Decimal;
CancelAppliedEntryErr: Label 'You must unapply customer ledger entry %1 before canceling issued finance charge memo %2.', Comment = '%1 - entry number, %2 - issued finance charge memo number';
ShowCustomerLedgerEntryTxt: Label 'Show customer ledger entry %1.', Comment = '%1 - entry number.';
SkipShowNotification: Boolean;
UseSameDocumentNo: Boolean;
UseSamePostingDate: Boolean;
NewPostingDate: Date;
MissingFieldNameErr: Label 'Please enter a %1.', Comment = '%1 - field caption';
local procedure CheckIssuedFinChargeMemo(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"): Boolean
begin
IssuedFinChargeMemoHeader.TestField(Canceled, false);
GLSetup.Get();
if GLSetup."Journal Templ. Name Mandatory" then begin
if GenJnlBatch."Journal Template Name" = '' then
Error(MissingFieldNameErr, TempGenJnlLine.FieldCaption("Journal Template Name"));
if GenJnlBatch.Name = '' then
Error(MissingFieldNameErr, TempGenJnlLine.FieldCaption("Journal Batch Name"));
end;
exit(CheckAppliedFinChargeMemoCustLedgerEntry(IssuedFinChargeMemoHeader));
end;
local procedure CancelIssuedFinChargeMemo(var IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header")
var
IssuedFinChargeMemoLine: Record "Issued Fin. Charge Memo Line";
FinanceChargeTerms: Record "Finance Charge Terms";
FeePosted: Boolean;
DocumentNo: Code[20];
PostingDate: Date;
InterestAmount: Decimal;
InterestVATAmount: Decimal;
begin
OnBeforeCancelIssuedFinChargeMemo(IssuedFinChargeMemoHeader);
SourceCodeSetup.Get();
SourceCodeSetup.TestField("Finance Charge Memo");
FinChargeMemoSourceCode := SourceCodeSetup."Finance Charge Memo";
FinanceChargeTerms.Get(IssuedFinChargeMemoHeader."Fin. Charge Terms Code");
FeePosted := IsFeePosted(IssuedFinChargeMemoHeader);
GetDocumentNoAndPostingDate(IssuedFinChargeMemoHeader, DocumentNo, PostingDate);
SetIssuedFinChargeMemoCancelled(IssuedFinChargeMemoHeader, DocumentNo);
IssuedFinChargeMemoLine.SetRange("Finance Charge Memo No.", IssuedFinChargeMemoHeader."No.");
IssuedFinChargeMemoLine.SetRange("Detailed Interest Rates Entry", false);
if IssuedFinChargeMemoLine.FindSet() then
repeat
case IssuedFinChargeMemoLine.Type of
IssuedFinChargeMemoLine.Type::"Customer Ledger Entry":
begin
UpdateCustLedgEntryCalculateInterest(IssuedFinChargeMemoLine."Entry No.");
SetFinChargeMemoEntryCancelled(IssuedFinChargeMemoLine);
InterestAmount := InterestAmount + IssuedFinChargeMemoLine.Amount;
InterestVATAmount := InterestVATAmount + IssuedFinChargeMemoLine."VAT Amount";
end;
IssuedFinChargeMemoLine.Type::"G/L Account":
if FinanceChargeTerms."Post Additional Fee" or (IssuedFinChargeMemoLine."Line Type" = IssuedFinChargeMemoLine."Line Type"::Rounding) then
InsertGenJnlLineForFee(IssuedFinChargeMemoHeader, IssuedFinChargeMemoLine, DocumentNo, PostingDate);
end;
until IssuedFinChargeMemoLine.Next() = 0;
if (InterestAmount <> 0) and FeePosted then
InsertGenJnlLineForInterest(
IssuedFinChargeMemoHeader, DocumentNo, PostingDate, InterestAmount, InterestVATAmount);
if (TotalAmount <> 0) or (TotalAmountLCY <> 0) then begin
InitGenJnlLine(
IssuedFinChargeMemoHeader, TempGenJnlLine, TempGenJnlLine."Account Type"::Customer,
IssuedFinChargeMemoHeader."Customer No.", true, DocumentNo, PostingDate);
TempGenJnlLine.Validate(Amount, TotalAmount);
TempGenJnlLine.Validate("Amount (LCY)", TotalAmountLCY);
TempGenJnlLine.Insert();
end;
if FeePosted then
PostGenJnlLines(IssuedFinChargeMemoHeader);
OnAfterCancelIssuedFinChargeMemo(IssuedFinChargeMemoHeader);
end;
local procedure CheckAppliedFinChargeMemoCustLedgerEntry(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"): Boolean
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetRange("Customer No.", IssuedFinChargeMemoHeader."Customer No.");
CustLedgerEntry.SetRange("Document Type", CustLedgerEntry."Document Type"::"Finance Charge Memo");
CustLedgerEntry.SetRange("Document No.", IssuedFinChargeMemoHeader."No.");
if CustLedgerEntry.FindFirst() then begin
CustLedgerEntry.CalcFields(Amount, "Remaining Amount");
OnCheckAppliedFinChargeMemoCustLedgerEntryOnAfterCalcAmounts(CustLedgerEntry);
if CustLedgerEntry.Amount <> CustLedgerEntry."Remaining Amount" then begin
TempErrorMessage.LogMessage(
IssuedFinChargeMemoHeader,
IssuedFinChargeMemoHeader.FieldNo("No."),
TempErrorMessage."Message Type"::Error,
StrSubstNo(CancelAppliedEntryErr, CustLedgerEntry."Entry No.", IssuedFinChargeMemoHeader."No."));
if not SkipShowNotification then
ShowAppliedCustomerLedgerEntryNotification(CustLedgerEntry."Entry No.", IssuedFinChargeMemoHeader);
exit(false);
end;
end;
exit(true);
end;
local procedure SetFinChargeMemoEntryCancelled(IssuedFinChargeMemoLine: Record "Issued Fin. Charge Memo Line")
var
ReminderFinChargeEntry: Record "Reminder/Fin. Charge Entry";
begin
ReminderFinChargeEntry.SetRange("No.", IssuedFinChargeMemoLine."Finance Charge Memo No.");
ReminderFinChargeEntry.SetRange("Customer Entry No.", IssuedFinChargeMemoLine."Entry No.");
if ReminderFinChargeEntry.FindFirst() then begin
ReminderFinChargeEntry.Canceled := true;
ReminderFinChargeEntry.Modify();
end;
end;
local procedure InsertGenJnlLineForFee(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"; var IssuedFinChargeMemoLine: Record "Issued Fin. Charge Memo Line"; DocumentNo: Code[20]; PostingDate: Date)
begin
if IssuedFinChargeMemoLine.Amount <> 0 then begin
IssuedFinChargeMemoLine.TestField("No.");
InitGenJnlLine(IssuedFinChargeMemoHeader, TempGenJnlLine, TempGenJnlLine."Account Type"::"G/L Account",
IssuedFinChargeMemoLine."No.",
IssuedFinChargeMemoLine."System-Created Entry", DocumentNo, PostingDate);
TempGenJnlLine.CopyFromIssuedFinChargeMemoLine(IssuedFinChargeMemoLine);
if IssuedFinChargeMemoLine."VAT Calculation Type" = IssuedFinChargeMemoLine."VAT Calculation Type"::"Sales Tax" then begin
TempGenJnlLine.Validate("Tax Area Code", IssuedFinChargeMemoHeader."Tax Area Code");
TempGenJnlLine.Validate("Tax Liable", IssuedFinChargeMemoHeader."Tax Liable");
TempGenJnlLine.Validate("Tax Group Code", IssuedFinChargeMemoLine."Tax Group Code");
end;
TempGenJnlLine.UpdateLineBalance();
TotalAmount := TotalAmount - TempGenJnlLine.Amount;
TotalAmountLCY := TotalAmountLCY - TempGenJnlLine."Balance (LCY)";
TempGenJnlLine."Bill-to/Pay-to No." := IssuedFinChargeMemoHeader."Customer No.";
TempGenJnlLine.Insert();
end;
end;
local procedure InsertGenJnlLineForInterest(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"; DocumentNo: Code[20]; PostingDate: Date; InterestAmount: Decimal; InterestVATAmount: Decimal)
var
CustomerPostingGroup: Record "Customer Posting Group";
begin
CustomerPostingGroup.Get(IssuedFinChargeMemoHeader."Customer Posting Group");
InitGenJnlLine(
IssuedFinChargeMemoHeader, TempGenJnlLine, TempGenJnlLine."Account Type"::"G/L Account",
CustomerPostingGroup.GetInterestAccount(), true, DocumentNo, PostingDate);
TempGenJnlLine.Validate("VAT Bus. Posting Group", IssuedFinChargeMemoHeader."VAT Bus. Posting Group");
TempGenJnlLine.Validate(Amount, InterestAmount + InterestVATAmount);
TempGenJnlLine.UpdateLineBalance();
TotalAmount := TotalAmount - TempGenJnlLine.Amount;
TotalAmountLCY := TotalAmountLCY - TempGenJnlLine."Balance (LCY)";
TempGenJnlLine."Bill-to/Pay-to No." := IssuedFinChargeMemoHeader."Customer No.";
TempGenJnlLine.Insert();
end;
local procedure InitGenJnlLine(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"; var GenJnlLine: Record "Gen. Journal Line"; AccountType: Enum "Gen. Journal Account Type"; AccountNo: Code[20]; SystemCreatedEntry: Boolean; DocumentNo: Code[20]; PostingDate: Date)
begin
GenJnlLine.Init();
GenJnlLine."Line No." := GenJnlLine."Line No." + 1;
GenJnlLine."Document Type" := GenJnlLine."Document Type"::" ";
GenJnlLine."Document No." := DocumentNo;
GenJnlLine."Posting Date" := PostingDate;
GenJnlLine."Account Type" := AccountType;
GenJnlLine.Validate("Account No.", AccountNo);
GenJnlLine.CopyFromIssuedFinChargeMemoHeader(IssuedFinChargeMemoHeader);
if GenJnlLine."Account Type" = GenJnlLine."Account Type"::Customer then begin
GenJnlLine.Amount := TotalAmount;
GenJnlLine."Amount (LCY)" := TotalAmountLCY;
GenJnlLine."Due Date" := IssuedFinChargeMemoHeader."Due Date";
GenJnlLine."Applies-to Doc. Type" := GenJnlLine."Applies-to Doc. Type"::"Finance Charge Memo";
GenJnlLine."Applies-to Doc. No." := IssuedFinChargeMemoHeader."No.";
end;
GenJnlLine."Source Code" := FinChargeMemoSourceCode;
GenJnlLine."System-Created Entry" := SystemCreatedEntry;
GenJnlLine."Salespers./Purch. Code" := '';
GLSetup.Get();
if GLSetup."Journal Templ. Name Mandatory" then begin
GenJnlLine."Journal Template Name" := GenJnlBatch."Journal Template Name";
GenJnlLine."Journal Batch Name" := GenJnlBatch.Name;
end;
OnAfterInitGenJnlLine(GenJnlLine, IssuedFinChargeMemoHeader);
end;
/// <summary>
/// Gets the unique identifier for the applied customer ledger entry notification.
/// </summary>
/// <returns>The GUID used to identify the notification for applied customer ledger entries.</returns>
procedure GetAppliedCustomerLedgerEntryNotificationId(): Guid
begin
exit('3C569183-8468-4978-936C-EF8A7C58B050');
end;
/// <summary>
/// Retrieves accumulated error messages from the cancellation process.
/// </summary>
/// <param name="TempErrorMessageResult">Returns the temporary error message records containing any errors encountered during cancellation.</param>
/// <returns>True if there are error messages; otherwise, false.</returns>
procedure GetErrorMessages(var TempErrorMessageResult: Record "Error Message" temporary): Boolean
begin
TempErrorMessageResult.Copy(TempErrorMessage, true);
exit(not TempErrorMessage.IsEmpty);
end;
local procedure GetDocumentNoAndPostingDate(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"; var DocumentNo: Code[20]; var PostingDate: Date)
var
SalesSetup: Record "Sales & Receivables Setup";
NoSeries: Codeunit "No. Series";
begin
if UseSamePostingDate then
PostingDate := IssuedFinChargeMemoHeader."Posting Date"
else
PostingDate := NewPostingDate;
if UseSameDocumentNo then
DocumentNo := IssuedFinChargeMemoHeader."No."
else begin
SalesSetup.Get();
SalesSetup.TestField("Canc. Iss. Fin. Ch. Mem. Nos.");
DocumentNo := NoSeries.GetNextNo(SalesSetup."Canc. Iss. Fin. Ch. Mem. Nos.", PostingDate);
end;
end;
local procedure PostGenJnlLines(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header")
var
GenJnlLine2: Record "Gen. Journal Line";
GenJnlPostLine: Codeunit "Gen. Jnl.-Post Line";
begin
if TempGenJnlLine.FindSet() then
repeat
GenJnlLine2 := TempGenJnlLine;
SetDimensions(GenJnlLine2, IssuedFinChargeMemoHeader);
GenJnlPostLine.RunWithCheck(GenJnlLine2);
until TempGenJnlLine.Next() = 0;
TempGenJnlLine.DeleteAll();
end;
/// <summary>
/// Sets the parameters used when canceling an issued finance charge memo.
/// </summary>
/// <param name="NewUseSameDocumentNo">Specifies whether to use the same document number as the original finance charge memo.</param>
/// <param name="NewUseSamePostingDate">Specifies whether to use the same posting date as the original finance charge memo.</param>
/// <param name="PostingDate">Specifies the new posting date to use if NewUseSamePostingDate is false.</param>
/// <param name="NewSkipShowNotification">Specifies whether to skip showing notifications during the cancellation process.</param>
procedure SetParameters(NewUseSameDocumentNo: Boolean; NewUseSamePostingDate: Boolean; PostingDate: Date; NewSkipShowNotification: Boolean)
begin
UseSameDocumentNo := NewUseSameDocumentNo;
UseSamePostingDate := NewUseSamePostingDate;
NewPostingDate := PostingDate;
SkipShowNotification := NewSkipShowNotification;
end;
/// <summary>
/// Sets the general journal batch to use when posting the cancellation entries.
/// </summary>
/// <param name="NewGenJnlBatch">Specifies the general journal batch for posting cancellation entries.</param>
procedure SetGenJnlBatch(NewGenJnlBatch: Record "Gen. Journal Batch")
begin
GenJnlBatch := NewGenJnlBatch;
end;
local procedure SetIssuedFinChargeMemoCancelled(var IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"; DocumentNo: Code[20])
begin
IssuedFinChargeMemoHeader.Validate(Canceled, true);
IssuedFinChargeMemoHeader."Canceled By" := UserId;
IssuedFinChargeMemoHeader."Canceled Date" := Today;
IssuedFinChargeMemoHeader."Canceled By Document No." := DocumentNo;
IssuedFinChargeMemoHeader.Modify();
end;
local procedure ShowAppliedCustomerLedgerEntryNotification(EntryNo: Integer; IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header")
var
NotificationLifecycleMgt: Codeunit "Notification Lifecycle Mgt.";
AppliedCustomerLedgerNotification: Notification;
begin
AppliedCustomerLedgerNotification.Id := GetAppliedCustomerLedgerEntryNotificationId();
AppliedCustomerLedgerNotification.Message :=
StrSubstNo(CancelAppliedEntryErr, EntryNo, IssuedFinChargeMemoHeader."No.");
AppliedCustomerLedgerNotification.AddAction(
StrSubstNo(ShowCustomerLedgerEntryTxt, EntryNo),
CODEUNIT::"Cancel Issued Fin. Charge Memo",
'ShowCustomerLedgerEntry');
AppliedCustomerLedgerNotification.Scope := NOTIFICATIONSCOPE::LocalScope;
AppliedCustomerLedgerNotification.SetData('EntryNo', Format(EntryNo));
NotificationLifecycleMgt.SendNotificationWithAdditionalContext(
AppliedCustomerLedgerNotification, IssuedFinChargeMemoHeader.RecordId, GetAppliedCustomerLedgerEntryNotificationId());
end;
/// <summary>
/// Opens the Customer Ledger Entries page filtered to the entry specified in the notification.
/// </summary>
/// <param name="Notification">Specifies the notification containing the entry number to display.</param>
procedure ShowCustomerLedgerEntry(Notification: Notification)
var
CustLedgerEntry: Record "Cust. Ledger Entry";
EntryNo: Integer;
begin
Evaluate(EntryNo, Notification.GetData('EntryNo'));
CustLedgerEntry.SetRange("Entry No.", EntryNo);
PAGE.Run(PAGE::"Customer Ledger Entries", CustLedgerEntry);
end;
local procedure UpdateCustLedgEntryCalculateInterest(EntryNo: Integer)
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetCurrentKey("Closed by Entry No.");
CustLedgerEntry.SetRange("Closed by Entry No.", EntryNo);
CustLedgerEntry.SetRange("Closing Interest Calculated", true);
CustLedgerEntry.ModifyAll("Closing Interest Calculated", false);
end;
local procedure IsFeePosted(IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header"): Boolean
var
CustLedgerEntry: Record "Cust. Ledger Entry";
begin
CustLedgerEntry.SetRange("Customer No.", IssuedFinChargeMemoHeader."Customer No.");
CustLedgerEntry.SetRange("Document No.", IssuedFinChargeMemoHeader."No.");
exit(not CustLedgerEntry.IsEmpty);
end;
local procedure SetDimensions(var GenJnlLine: Record "Gen. Journal Line"; var IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header")
var
DefaultDimension: Record "Default Dimension";
DefaultDimSource: List of [Dictionary of [Integer, Code[20]]];
begin
GenJnlLine."Shortcut Dimension 1 Code" := IssuedFinChargeMemoHeader."Shortcut Dimension 1 Code";
GenJnlLine."Shortcut Dimension 2 Code" := IssuedFinChargeMemoHeader."Shortcut Dimension 2 Code";
GenJnlLine."Dimension Set ID" := IssuedFinChargeMemoHeader."Dimension Set ID";
if GenJnlLine."Account Type" = GenJnlLine."Account Type"::"G/L Account" then begin
DimMgt.AddDimSource(DefaultDimSource, Database::"G/L Account", GenJnlLine."Account No.");
DefaultDimension.SetRange("Table ID", Database::"G/L Account");
DefaultDimension.SetRange("No.", GenJnlLine."Account No.");
if not DefaultDimension.IsEmpty() then
GenJnlLine."Dimension Set ID" :=
DimMgt.GetRecDefaultDimID(
GenJnlLine, 0, DefaultDimSource, FinChargeMemoSourceCode, GenJnlLine."Shortcut Dimension 1 Code", GenJnlLine."Shortcut Dimension 2 Code", GenJnlLine."Dimension Set ID", 0);
end;
end;
/// <summary>
/// Raised after a general journal line is initialized for posting the cancellation entry.
/// </summary>
/// <param name="GenJournalLine">Specifies the initialized general journal line.</param>
/// <param name="IssuedFinChargeMemoHeader">Specifies the issued finance charge memo header being canceled.</param>
[IntegrationEvent(false, false)]
local procedure OnAfterInitGenJnlLine(var GenJournalLine: Record "Gen. Journal Line"; IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header")
begin
end;
/// <summary>
/// Raised after the issued finance charge memo cancellation completes successfully.
/// </summary>
/// <param name="IssuedFinChargeMemoHeader">Specifies the canceled issued finance charge memo header.</param>
[IntegrationEvent(false, false)]
local procedure OnAfterCancelIssuedFinChargeMemo(var IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header")
begin
end;
/// <summary>
/// Raised before the issued finance charge memo cancellation process begins.
/// </summary>
/// <param name="IssuedFinChargeMemoHeader">Specifies the issued finance charge memo header to cancel.</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeCancelIssuedFinChargeMemo(var IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header")
begin
end;
/// <summary>
/// Raised after calculating amount fields on the customer ledger entry during applied entry check.
/// </summary>
/// <param name="CustLedgerEntry">Specifies the customer ledger entry with calculated amounts.</param>
[IntegrationEvent(false, false)]
local procedure OnCheckAppliedFinChargeMemoCustLedgerEntryOnAfterCalcAmounts(var CustLedgerEntry: Record "Cust. Ledger Entry")
begin
end;
}