Codeunit 56 Sales - Calc Discount By Type, source in 29
Source29
src/Layers/W1/BaseApp/Sales/Document/SalesCalcDiscountByType.Codeunit.al345 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.Document;
using Microsoft.Finance.VAT.Calculation;
using Microsoft.Sales.Pricing;
using Microsoft.Sales.Setup;
using Microsoft.Utilities;
using System.Environment.Configuration;
/// <summary>
/// Applies default invoice discounts to sales documents based on discount type configuration.
/// </summary>
codeunit 56 "Sales - Calc Discount By Type"
{
TableNo = "Sales Line";
trigger OnRun()
var
SalesLine: Record "Sales Line";
SalesHeader: Record "Sales Header";
begin
SalesLine.Copy(Rec);
if SalesHeader.Get(Rec."Document Type", Rec."Document No.") then begin
ApplyDefaultInvoiceDiscount(SalesHeader."Invoice Discount Value", SalesHeader);
// on new order might be no line
if Rec.Get(SalesLine."Document Type", SalesLine."Document No.", SalesLine."Line No.") then;
end;
end;
var
InvDiscBaseAmountIsZeroErr: Label 'Cannot apply an invoice discount because the document does not include lines where the Allow Invoice Disc. field is selected. To add a discount, specify a line discount in the Line Discount % field for the relevant lines, or add a line of type Item where the Allow Invoice Disc. field is selected.';
CalcInvoiceDiscountOnSalesLine: Boolean;
/// <summary>
/// Applies the default invoice discount to the sales header.
/// </summary>
/// <param name="InvoiceDiscountAmount">The invoice discount amount to apply.</param>
/// <param name="SalesHeader">The sales header to apply the discount to.</param>
procedure ApplyDefaultInvoiceDiscount(InvoiceDiscountAmount: Decimal; var SalesHeader: Record "Sales Header")
begin
ApplyDefaultInvoiceDiscount(InvoiceDiscountAmount, SalesHeader, false);
end;
internal procedure ApplyDefaultInvoiceDiscount(InvoiceDiscountAmount: Decimal; var SalesHeader: Record "Sales Header"; ModifyBeforeApplying: Boolean)
var
IsHandled: Boolean;
begin
if not ShouldRedistributeInvoiceDiscountAmount(SalesHeader) then
exit;
IsHandled := false;
OnBeforeApplyDefaultInvoiceDiscount(SalesHeader, IsHandled, InvoiceDiscountAmount);
if not IsHandled then begin
if ModifyBeforeApplying then
SalesHeader.Modify();
if SalesHeader."Invoice Discount Calculation" = SalesHeader."Invoice Discount Calculation"::Amount then
ApplyInvDiscBasedOnAmt(InvoiceDiscountAmount, SalesHeader)
else
ApplyInvDiscBasedOnPct(SalesHeader);
end;
ResetRecalculateInvoiceDisc(SalesHeader);
end;
/// <summary>
/// Applies invoice discount based on a specific amount.
/// </summary>
/// <param name="InvoiceDiscountAmount">The invoice discount amount to apply.</param>
/// <param name="SalesHeader">The sales header to apply the discount to.</param>
procedure ApplyInvDiscBasedOnAmt(InvoiceDiscountAmount: Decimal; var SalesHeader: Record "Sales Header")
var
TempVATAmountLine: Record "VAT Amount Line" temporary;
SalesLine: Record "Sales Line";
SalesSetup: Record "Sales & Receivables Setup";
DiscountNotificationMgt: Codeunit "Discount Notification Mgt.";
InvDiscBaseAmount: Decimal;
begin
OnBeforeApplyInvDiscBasedOnAmt(InvoiceDiscountAmount, SalesHeader);
SalesSetup.Get();
DiscountNotificationMgt.NotifyAboutMissingSetup(
SalesSetup.RecordId, SalesHeader."Gen. Bus. Posting Group",
SalesSetup."Discount Posting", SalesSetup."Discount Posting"::"Line Discounts");
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.CalcVATAmountLines(0, SalesHeader, SalesLine, TempVATAmountLine);
InvDiscBaseAmount := TempVATAmountLine.GetTotalInvDiscBaseAmount(false, SalesHeader."Currency Code");
if (InvDiscBaseAmount = 0) and (InvoiceDiscountAmount > 0) then
Error(InvDiscBaseAmountIsZeroErr);
TempVATAmountLine.SetInvoiceDiscountAmount(InvoiceDiscountAmount, SalesHeader."Currency Code",
SalesHeader."Prices Including VAT", SalesHeader."VAT Base Discount %");
SalesLine.UpdateVATOnLines(0, SalesHeader, SalesLine, TempVATAmountLine);
SalesHeader."Invoice Discount Calculation" := SalesHeader."Invoice Discount Calculation"::Amount;
SalesHeader."Invoice Discount Value" := InvoiceDiscountAmount;
ResetRecalculateInvoiceDisc(SalesHeader);
SalesHeader.Modify();
end;
local procedure ApplyInvDiscBasedOnPct(var SalesHeader: Record "Sales Header")
var
SalesLine: Record "Sales Line";
SalesCalcDiscount: Codeunit "Sales-Calc. Discount";
begin
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
if SalesLine.FindFirst() then begin
if CalcInvoiceDiscountOnSalesLine then
SalesCalcDiscount.CalculateInvoiceDiscountOnLine(SalesLine)
else
CODEUNIT.Run(CODEUNIT::"Sales-Calc. Discount", SalesLine);
SalesHeader.Get(SalesHeader."Document Type", SalesHeader."No.");
end;
end;
/// <summary>
/// Gets the customer invoice discount percentage for a sales line.
/// </summary>
/// <param name="SalesLine">The sales line to get the discount for.</param>
/// <returns>The invoice discount percentage.</returns>
procedure GetCustInvoiceDiscountPct(SalesLine: Record "Sales Line"): Decimal
var
SalesHeader: Record "Sales Header";
InvoiceDiscountValue: Decimal;
AmountIncludingVATDiscountAllowed: Decimal;
AmountDiscountAllowed: Decimal;
SkipCustInvDiscCheck: Boolean;
begin
if not SalesHeader.Get(SalesLine."Document Type", SalesLine."Document No.") then
exit(0);
SalesHeader.CalcFields("Invoice Discount Amount");
if SalesHeader."Invoice Discount Amount" = 0 then
exit(0);
case SalesHeader."Invoice Discount Calculation" of
SalesHeader."Invoice Discount Calculation"::"%":
begin
SkipCustInvDiscCheck := false;
OnGetCustInvoiceDiscountPctOnCaseInvDiscCalcPercent(SkipCustInvDiscCheck);
if not SkipCustInvDiscCheck then
// Only if CustInvDisc table is empty header is not updated
if not CustInvDiscRecExists(SalesHeader."Invoice Disc. Code") then
exit(0);
exit(SalesHeader."Invoice Discount Value");
end;
SalesHeader."Invoice Discount Calculation"::None,
SalesHeader."Invoice Discount Calculation"::Amount:
begin
InvoiceDiscountValue := SalesHeader."Invoice Discount Amount";
CalcAmountWithDiscountAllowed(SalesHeader, AmountIncludingVATDiscountAllowed, AmountDiscountAllowed);
if AmountDiscountAllowed + InvoiceDiscountValue = 0 then
exit(0);
if SalesHeader."Prices Including VAT" then
exit(Round(InvoiceDiscountValue / (AmountIncludingVATDiscountAllowed + InvoiceDiscountValue) * 100, 0.01));
exit(Round(InvoiceDiscountValue / AmountDiscountAllowed * 100, 0.01));
end;
end;
exit(0);
end;
/// <summary>
/// Determines whether the invoice discount amount should be redistributed.
/// </summary>
/// <param name="SalesHeader">The sales header to check.</param>
/// <returns>True if the discount should be redistributed.</returns>
procedure ShouldRedistributeInvoiceDiscountAmount(var SalesHeader: Record "Sales Header"): Boolean
var
ApplicationAreaMgmtFacade: Codeunit "Application Area Mgmt. Facade";
IsHandled: Boolean;
ShouldRedistributeInvDiscAmt: Boolean;
begin
IsHandled := false;
OnBeforeShouldRedistributeInvoiceDiscountAmount(SalesHeader, IsHandled);
if IsHandled then
exit(true);
SalesHeader.CalcFields("Recalculate Invoice Disc.");
if not SalesHeader."Recalculate Invoice Disc." then
exit(false);
case SalesHeader."Invoice Discount Calculation" of
SalesHeader."Invoice Discount Calculation"::Amount:
exit(SalesHeader."Invoice Discount Value" <> 0);
SalesHeader."Invoice Discount Calculation"::"%":
begin
if not InvoiceDiscServiceChargeIsAllowed(SalesHeader."Invoice Disc. Code") then
exit(false);
exit(true);
end;
SalesHeader."Invoice Discount Calculation"::None:
begin
if not InvoiceDiscServiceChargeIsAllowed(SalesHeader."Invoice Disc. Code") then
exit(false);
if ApplicationAreaMgmtFacade.IsFoundationEnabled() then
exit(true);
ShouldRedistributeInvDiscAmt := not InvoiceDiscIsAllowed(SalesHeader."Invoice Disc. Code");
OnShouldRedistributeInvoiceDiscountAmountOnCaseInvDiscCalculationNone(SalesHeader, ShouldRedistributeInvDiscAmt);
exit(ShouldRedistributeInvDiscAmt);
end;
else
exit(true);
end;
end;
/// <summary>
/// Resets the recalculate invoice discount flag on all sales lines.
/// </summary>
/// <param name="SalesHeader">The sales header whose lines should be reset.</param>
procedure ResetRecalculateInvoiceDisc(SalesHeader: Record "Sales Header")
var
SalesLine: Record "Sales Line";
begin
SalesLine.LockTable(); // ModifyAll would previously trigger LockTable if there are subscribers
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange("Recalculate Invoice Disc.", true);
SalesLine.ModifyAll("Recalculate Invoice Disc.", false);
OnAfterResetRecalculateInvoiceDisc(SalesHeader);
end;
/// <summary>
/// Checks if a customer invoice discount record exists for the given code.
/// </summary>
/// <param name="InvDiscCode">The invoice discount code to check.</param>
/// <returns>True if a record exists.</returns>
procedure CustInvDiscRecExists(InvDiscCode: Code[20]): Boolean
var
CustInvDisc: Record "Cust. Invoice Disc.";
begin
CustInvDisc.SetRange(Code, InvDiscCode);
exit(not CustInvDisc.IsEmpty);
end;
/// <summary>
/// Checks if invoice discount is allowed for the given discount code.
/// </summary>
/// <param name="InvDiscCode">The invoice discount code to check.</param>
/// <returns>True if invoice discount is allowed.</returns>
procedure InvoiceDiscIsAllowed(InvDiscCode: Code[20]): Boolean
var
SalesReceivablesSetup: Record "Sales & Receivables Setup";
begin
SalesReceivablesSetup.Get();
if not SalesReceivablesSetup."Calc. Inv. Discount" then
exit(true);
exit(not CustInvDiscRecExists(InvDiscCode));
end;
local procedure CalcAmountWithDiscountAllowed(SalesHeader: Record "Sales Header"; var AmountIncludingVATDiscountAllowed: Decimal; var AmountDiscountAllowed: Decimal)
var
SalesLine: Record "Sales Line";
begin
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
SalesLine.SetRange("Allow Invoice Disc.", true);
SalesLine.CalcSums(Amount, "Amount Including VAT", "Inv. Discount Amount");
AmountIncludingVATDiscountAllowed := SalesLine."Amount Including VAT";
AmountDiscountAllowed := SalesLine.Amount + SalesLine."Inv. Discount Amount";
end;
local procedure InvoiceDiscServiceChargeIsAllowed(InvDiscCode: Code[20]): Boolean
var
SalesReceivablesSetup: Record "Sales & Receivables Setup";
begin
SalesReceivablesSetup.Get();
if not SalesReceivablesSetup."Calc. Inv. Discount" then
if CustInvDiscServiceChargeExists(InvDiscCode) then
exit(false);
exit(true);
end;
local procedure CustInvDiscServiceChargeExists(InvDiscCode: Code[20]): Boolean
var
CustInvDisc: Record "Cust. Invoice Disc.";
begin
CustInvDisc.SetRange(Code, InvDiscCode);
CustInvDisc.SetFilter("Service Charge", '<>%1', 0);
exit(not CustInvDisc.IsEmpty());
end;
[IntegrationEvent(false, false)]
local procedure OnAfterResetRecalculateInvoiceDisc(var SalesHeader: Record "Sales Header")
begin
end;
/// <summary>
/// Sets the flag to calculate invoice discount on a specific line.
/// </summary>
/// <param name="CalcInvoiceDiscountOnLine">Whether to calculate on a specific line.</param>
procedure CalcInvoiceDiscOnLine(CalcInvoiceDiscountOnLine: Boolean)
begin
CalcInvoiceDiscountOnSalesLine := CalcInvoiceDiscountOnLine;
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeApplyDefaultInvoiceDiscount(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean; InvoiceDiscountAmount: Decimal)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeApplyInvDiscBasedOnAmt(InvoiceDiscountAmount: Decimal; var SalesHeader: Record "Sales Header")
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeShouldRedistributeInvoiceDiscountAmount(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnGetCustInvoiceDiscountPctOnCaseInvDiscCalcPercent(var SkipCustInvDiscCheck: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnShouldRedistributeInvoiceDiscountAmountOnCaseInvDiscCalculationNone(SalesHeader: Record "Sales Header"; var ShouldRedistributeInvDiscAmt: Boolean)
begin
end;
}