Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/Apps/BE/PeppolBE/App/src/PEPPOL30BEEscompte.Codeunit.al
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Peppol.BE;

using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.VAT.Calculation;
using Microsoft.Sales.Document;

/// <summary>
/// Shared helpers for the Belgian payment-discount (escompte) compensation. In Belgium VAT is kept on the discounted base, even when the invoice is reported with the full amount.
/// To avoid reporting a reduced amount payable (the discount is only conditional) a compensating Exempt (category E) breakdown line is added.
/// </summary>
codeunit 37316 "PEPPOL30 BE Escompte"
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
CompensationChargeReasonTxt: Label 'Payment discount not deducted from the amount payable';
CompensationExemptionReasonTxt: Label 'Conditional early-payment discount, not part of the taxable amount';

/// <summary>
/// The VAT Amount Line "VAT Identifier" used to mark this compensation line. Internal to the buffer, not written to the PEPPOL document.
/// </summary>
procedure GetCompensationVATIdentifier(): Code[20]
begin
exit('ESCOMPTE-COMP');
end;

/// <summary>
/// The PEPPOL VAT category code used for the compensation line and charge.
/// </summary>
procedure GetExemptTaxCategory(): Code[10]
begin
exit('E');
end;

/// <summary>
/// Returns whether the given VAT amount line is the synthetic escompte compensation line.
/// </summary>
procedure IsCompensationLine(VATAmtLine: Record "VAT Amount Line"): Boolean
begin
exit(VATAmtLine."VAT Identifier" = GetCompensationVATIdentifier());
end;

/// <summary>
/// The AllowanceChargeReason used on the compensating Exempt charge.
/// </summary>
procedure GetCompensationChargeReason(): Text
begin
exit(CompensationChargeReasonTxt);
end;

/// <summary>
/// The VAT exemption reason used on the compensating Exempt VAT breakdown.
/// </summary>
procedure GetCompensationExemptionReason(): Text
begin
exit(CompensationExemptionReasonTxt);
end;

/// <summary>
/// Document's currency code (inline with PEPPOL's implementation).
/// </summary>
procedure DocumentCurrencyCode(SalesHeader: Record "Sales Header"): Text
var
GLSetup: Record "General Ledger Setup";
begin
if SalesHeader."Currency Code" <> '' then
exit(SalesHeader."Currency Code");
GLSetup.Get();
GLSetup.TestField("LCY Code");
exit(GLSetup."LCY Code");
end;
}
104 changes: 104 additions & 0 deletions src/Apps/BE/PeppolBE/App/src/PEPPOL30BEMonetaryInfo.Codeunit.al
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Peppol.BE;

using Microsoft.Finance.VAT.Calculation;
using Microsoft.Peppol;
using Microsoft.Sales.Document;

/// <summary>
/// Needed to add into the LegalMonetaryTotal the Belgian escompte compensation (if applicable). We are storing the compensation in the VAT Amount Line records.
/// </summary>
codeunit 37318 "PEPPOL30 BE Monetary Info" implements "PEPPOL Monetary Info Provider"
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
PEPPOL30: Codeunit "PEPPOL30";
Escompte: Codeunit "PEPPOL30 BE Escompte";

procedure GetLegalMonetaryInfo(SalesHeader: Record "Sales Header"; var TempSalesLine: Record "Sales Line" temporary; var VATAmtLine: Record "VAT Amount Line"; var LineExtensionAmount: Text; var LegalMonetaryTotalCurrencyID: Text; var TaxExclusiveAmount: Text; var TaxExclusiveAmountCurrencyID: Text; var TaxInclusiveAmount: Text; var TaxInclusiveAmountCurrencyID: Text; var AllowanceTotalAmount: Text; var AllowanceTotalAmountCurrencyID: Text; var ChargeTotalAmount: Text; var ChargeTotalAmountCurrencyID: Text; var PrepaidAmount: Text; var PrepaidCurrencyID: Text; var PayableRoundingAmount: Text; var PayableRndingAmountCurrencyID: Text; var PayableAmount: Text; var PayableAmountCurrencyID: Text)
var
CompensationAmount: Decimal;
RealVATBase: Decimal;
RealInvDiscount: Decimal;
RealPmtDiscount: Decimal;
RealAmtInclVAT: Decimal;
CurrencyId: Text;
begin
if not HasCompensationLine(VATAmtLine) then begin
PEPPOL30.GetLegalMonetaryInfo(SalesHeader, TempSalesLine, VATAmtLine, LineExtensionAmount, LegalMonetaryTotalCurrencyID, TaxExclusiveAmount, TaxExclusiveAmountCurrencyID, TaxInclusiveAmount, TaxInclusiveAmountCurrencyID, AllowanceTotalAmount, AllowanceTotalAmountCurrencyID, ChargeTotalAmount, ChargeTotalAmountCurrencyID, PrepaidAmount, PrepaidCurrencyID, PayableRoundingAmount, PayableRndingAmountCurrencyID, PayableAmount, PayableAmountCurrencyID);
exit;
end;

VATAmtLine.Reset();
if VATAmtLine.FindSet() then
repeat
if Escompte.IsCompensationLine(VATAmtLine) then
CompensationAmount += VATAmtLine."VAT Base"
else begin
RealVATBase += VATAmtLine."VAT Base";
RealInvDiscount += VATAmtLine."Invoice Discount Amount";
RealPmtDiscount += VATAmtLine."Pmt. Discount Amount";
RealAmtInclVAT += VATAmtLine."Amount Including VAT";
end;
until VATAmtLine.Next() = 0;

CurrencyId := Escompte.DocumentCurrencyCode(SalesHeader);

LineExtensionAmount := Format(Round(RealVATBase, 0.01) + Round(RealInvDiscount, 0.01), 0, 9);
LegalMonetaryTotalCurrencyID := CurrencyId;

TaxExclusiveAmount := Format(Round(RealVATBase - RealPmtDiscount + CompensationAmount, 0.01), 0, 9);
TaxExclusiveAmountCurrencyID := CurrencyId;

TaxInclusiveAmount := Format(Round(RealAmtInclVAT - RealPmtDiscount + CompensationAmount, 0.01, '>'), 0, 9);
TaxInclusiveAmountCurrencyID := CurrencyId;

AllowanceTotalAmount := Format(Round(RealInvDiscount + RealPmtDiscount, 0.01), 0, 9);
AllowanceTotalAmountCurrencyID := CurrencyId;

ChargeTotalAmount := Format(Round(CompensationAmount, 0.01), 0, 9);
ChargeTotalAmountCurrencyID := CurrencyId;

PrepaidAmount := '0.00';
PrepaidCurrencyID := CurrencyId;

if TempSalesLine."Line No." = 0 then begin
PayableRoundingAmount := Format(RealAmtInclVAT - Round(RealAmtInclVAT, 0.01), 0, 9);
PayableRndingAmountCurrencyID := CurrencyId;
PayableAmount := Format(Round(RealAmtInclVAT - RealPmtDiscount + CompensationAmount, 0.01), 0, 9);
PayableAmountCurrencyID := CurrencyId;
end else begin
PayableRoundingAmount := Format(TempSalesLine."Amount Including VAT", 0, 9);
PayableRndingAmountCurrencyID := CurrencyId;
PayableAmount := Format(Round(RealAmtInclVAT + TempSalesLine."Amount Including VAT" - RealPmtDiscount + CompensationAmount, 0.01), 0, 9);
PayableAmountCurrencyID := CurrencyId;
end;
end;

procedure GetLegalMonetaryDocAmounts(SalesHeader: Record "Sales Header"; var VATAmtLine: Record "VAT Amount Line"; var LineExtensionAmount: Text; var LegalMonetaryTotalCurrencyID: Text; var TaxExclusiveAmount: Text; var TaxExclusiveAmountCurrencyID: Text; var TaxInclusiveAmount: Text; var TaxInclusiveAmountCurrencyID: Text; var AllowanceTotalAmount: Text; var AllowanceTotalAmountCurrencyID: Text; var ChargeTotalAmount: Text; var ChargeTotalAmountCurrencyID: Text)
begin
PEPPOL30.GetLegalMonetaryDocAmounts(SalesHeader, VATAmtLine, LineExtensionAmount, LegalMonetaryTotalCurrencyID, TaxExclusiveAmount, TaxExclusiveAmountCurrencyID, TaxInclusiveAmount, TaxInclusiveAmountCurrencyID, AllowanceTotalAmount, AllowanceTotalAmountCurrencyID, ChargeTotalAmount, ChargeTotalAmountCurrencyID);
end;

procedure GetInvoiceRoundingLine(var TempSalesLine: Record "Sales Line" temporary; SalesLine: Record "Sales Line")
begin
PEPPOL30.GetInvoiceRoundingLine(TempSalesLine, SalesLine);
end;

local procedure HasCompensationLine(var VATAmtLine: Record "VAT Amount Line"): Boolean
var
Found: Boolean;
begin
VATAmtLine.Reset();
VATAmtLine.SetRange("VAT Identifier", Escompte.GetCompensationVATIdentifier());
Found := not VATAmtLine.IsEmpty();
VATAmtLine.Reset();
exit(Found);
end;
}
67 changes: 67 additions & 0 deletions src/Apps/BE/PeppolBE/App/src/PEPPOL30BEPaymentInfo.Codeunit.al
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Peppol.BE;

using Microsoft.Finance.VAT.Calculation;
using Microsoft.Peppol;
using Microsoft.Sales.Document;

/// <summary>
/// Belgian PEPPOL payment info provider. Delegates every method to the default PEPPOL30 implementation, except that it renders an extra escompte compensation line (see "PEPPOL30 BE Escompte")
/// </summary>
codeunit 37317 "PEPPOL30 BE Payment Info" implements "PEPPOL Payment Info Provider"
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
PEPPOL30: Codeunit "PEPPOL30";
Escompte: Codeunit "PEPPOL30 BE Escompte";

procedure GetPaymentMeansInfo(SalesHeader: Record "Sales Header"; var PaymentMeansCode: Text; var PaymentMeansListID: Text; var PaymentDueDate: Text; var PaymentChannelCode: Text; var PaymentID: Text; var PrimaryAccountNumberID: Text; var NetworkID: Text)
begin
PEPPOL30.GetPaymentMeansInfo(SalesHeader, PaymentMeansCode, PaymentMeansListID, PaymentDueDate, PaymentChannelCode, PaymentID, PrimaryAccountNumberID, NetworkID);
end;

procedure GetPaymentMeansPayeeFinancialAcc(var PayeeFinancialAccountID: Text; var PaymentMeansSchemeID: Text; var FinancialInstitutionBranchID: Text; var FinancialInstitutionID: Text; var FinancialInstitutionSchemeID: Text; var FinancialInstitutionName: Text)
begin
PEPPOL30.GetPaymentMeansPayeeFinancialAcc(PayeeFinancialAccountID, PaymentMeansSchemeID, FinancialInstitutionBranchID, FinancialInstitutionID, FinancialInstitutionSchemeID, FinancialInstitutionName);
end;

procedure GetPaymentMeansPayeeFinancialAccBIS(SalesHeader: Record "Sales Header"; var PayeeFinancialAccountID: Text; var FinancialInstitutionBranchID: Text)
begin
PEPPOL30.GetPaymentMeansPayeeFinancialAccBIS(SalesHeader, PayeeFinancialAccountID, FinancialInstitutionBranchID);
end;

procedure GetPaymentMeansFinancialInstitutionAddr(var FinancialInstitutionStreetName: Text; var AdditionalStreetName: Text; var FinancialInstitutionCityName: Text; var FinancialInstitutionPostalZone: Text; var FinancialInstCountrySubentity: Text; var FinancialInstCountryIdCode: Text; var FinancialInstCountryListID: Text)
begin
PEPPOL30.GetPaymentMeansFinancialInstitutionAddr(FinancialInstitutionStreetName, AdditionalStreetName, FinancialInstitutionCityName, FinancialInstitutionPostalZone, FinancialInstCountrySubentity, FinancialInstCountryIdCode, FinancialInstCountryListID);
end;

procedure GetPaymentTermsInfo(SalesHeader: Record "Sales Header"; var PaymentTermsNote: Text)
begin
PEPPOL30.GetPaymentTermsInfo(SalesHeader, PaymentTermsNote);
end;

procedure GetAllowanceChargeInfoPaymentDiscount(VATAmtLine: Record "VAT Amount Line"; SalesHeader: Record "Sales Header"; var ChargeIndicator: Text; var AllowanceChargeReasonCode: Text; var AllowanceChargeListID: Text; var AllowanceChargeReason: Text; var Amount: Text; var AllowanceChargeCurrencyID: Text; var TaxCategoryID: Text; var TaxCategorySchemeID: Text; var Percent: Text; var AllowanceChargeTaxSchemeID: Text)
begin
if Escompte.IsCompensationLine(VATAmtLine) then begin
ChargeIndicator := 'true';
AllowanceChargeReasonCode := '';
AllowanceChargeListID := '';
AllowanceChargeReason := Escompte.GetCompensationChargeReason();
Amount := Format(VATAmtLine."VAT Base", 0, 9);
AllowanceChargeCurrencyID := Escompte.DocumentCurrencyCode(SalesHeader);
TaxCategoryID := VATAmtLine."Tax Category";
TaxCategorySchemeID := '';
Percent := Format(VATAmtLine."VAT %", 0, 9);
AllowanceChargeTaxSchemeID := 'VAT';
exit;
end;

PEPPOL30.GetAllowanceChargeInfoPaymentDiscount(VATAmtLine, SalesHeader, ChargeIndicator, AllowanceChargeReasonCode, AllowanceChargeListID, AllowanceChargeReason, Amount, AllowanceChargeCurrencyID, TaxCategoryID, TaxCategorySchemeID, Percent, AllowanceChargeTaxSchemeID);
end;
}
47 changes: 39 additions & 8 deletions src/Apps/BE/PeppolBE/App/src/PEPPOL30BETaxInfo.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ using Microsoft.Peppol;
using Microsoft.Sales.Document;

/// <summary>
/// Belgian PEPPOL tax info provider. Delegates every method to the default PEPPOL30 implementation,
/// except that it excludes the payment discount from the tax totals so that the PEPPOL document totals
/// (TaxableAmount, TaxExclusiveAmount, TaxInclusiveAmount, PayableAmount) match the invoice printout.
/// Belgian PEPPOL tax info provider. Delegates every method to the default PEPPOL30 implementation and,
/// via FinalizeTaxTotals, appends a compensating Exempt (category E) VAT breakdown line for the
/// conditional payment discount (escompte). This keeps VAT on the discounted base (as required in
/// Belgium) while the amount payable stays whole.
/// </summary>
codeunit 37315 "PEPPOL30 BE Tax Info" implements "PEPPOL Tax Info Provider"
{
Expand All @@ -22,6 +23,7 @@ codeunit 37315 "PEPPOL30 BE Tax Info" implements "PEPPOL Tax Info Provider"

var
PEPPOL30: Codeunit "PEPPOL30";
Escompte: Codeunit "PEPPOL30 BE Escompte";

procedure GetAllowanceChargeInfo(VATAmtLine: Record "VAT Amount Line"; SalesHeader: Record "Sales Header"; var ChargeIndicator: Text; var AllowanceChargeReasonCode: Text; var AllowanceChargeListID: Text; var AllowanceChargeReason: Text; var Amount: Text; var AllowanceChargeCurrencyID: Text; var TaxCategoryID: Text; var TaxCategorySchemeID: Text; var Percent: Text; var AllowanceChargeTaxSchemeID: Text)
begin
Expand Down Expand Up @@ -55,14 +57,33 @@ codeunit 37315 "PEPPOL30 BE Tax Info" implements "PEPPOL Tax Info Provider"

procedure GetTaxTotals(SalesLine: Record "Sales Line"; var VATAmtLine: Record "VAT Amount Line")
begin
// In Belgium the payment discount must not reduce the PEPPOL document totals. Zeroing the payment
// discount on the by-value sales line before accumulation keeps TaxableAmount, TaxExclusiveAmount,
// TaxInclusiveAmount and PayableAmount aligned with the invoice printout, and the payment discount
// AllowanceCharge is skipped by its existing zero-amount guard.
SalesLine."Pmt. Discount Amount" := 0;
PEPPOL30.GetTaxTotals(SalesLine, VATAmtLine);
end;

procedure FinalizeTaxTotals(var VATAmtLine: Record "VAT Amount Line")
var
TotalPmtDiscount: Decimal;
begin
VATAmtLine.Reset();
VATAmtLine.CalcSums("Pmt. Discount Amount");
TotalPmtDiscount := VATAmtLine."Pmt. Discount Amount";
if TotalPmtDiscount = 0 then
exit;

VATAmtLine.Init();
VATAmtLine."VAT Identifier" := Escompte.GetCompensationVATIdentifier();
VATAmtLine."VAT Calculation Type" := VATAmtLine."VAT Calculation Type"::"Normal VAT";
VATAmtLine.Positive := true;
VATAmtLine."Tax Category" := Escompte.GetExemptTaxCategory();
VATAmtLine."VAT %" := 0;
VATAmtLine."VAT Base" := TotalPmtDiscount;
VATAmtLine."Amount Including VAT" := TotalPmtDiscount;
VATAmtLine."VAT Amount" := 0;
VATAmtLine."Pmt. Discount Amount" := 0;
VATAmtLine."Invoice Discount Amount" := 0;
VATAmtLine.Insert();
end;

procedure GetTaxCategories(SalesLine: Record "Sales Line"; var VATProductPostingGroupCategory: Record "VAT Product Posting Group")
begin
PEPPOL30.GetTaxCategories(SalesLine, VATProductPostingGroupCategory);
Expand All @@ -73,6 +94,16 @@ codeunit 37315 "PEPPOL30 BE Tax Info" implements "PEPPOL Tax Info Provider"
PEPPOL30.GetTaxExemptionReason(VATProductPostingGroupCategory, TaxExemptionReasonTxt, TaxCategoryID);
end;

procedure GetTaxExemptionReason(VATAmtLine: Record "VAT Amount Line"; var VATProductPostingGroupCategory: Record "VAT Product Posting Group"; var TaxExemptionReasonTxt: Text; TaxCategoryID: Text)
begin
if Escompte.IsCompensationLine(VATAmtLine) then begin
TaxExemptionReasonTxt := Escompte.GetCompensationExemptionReason();
exit;
end;

GetTaxExemptionReason(VATProductPostingGroupCategory, TaxExemptionReasonTxt, TaxCategoryID);
end;

procedure IsZeroVatCategory(TaxCategory: Code[10]): Boolean
begin
exit(PEPPOL30.IsZeroVatCategory(TaxCategory));
Expand Down
8 changes: 6 additions & 2 deletions src/Apps/BE/PeppolBE/App/src/PEPPOL30FormatBE.EnumExt.al
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ enumextension 37310 "PEPPOL 3.0 Format BE" extends "PEPPOL 3.0 Format"
Caption = 'PEPPOL 3.0 - Belgium Sales Format';
Implementation = "PEPPOL30 Validation" = "PEPPOL30 BE Sales Validation",
"PEPPOL Posted Document Iterator" = "PEPPOL30 Sales Iterator",
"PEPPOL Tax Info Provider" = "PEPPOL30 BE Tax Info";
"PEPPOL Tax Info Provider" = "PEPPOL30 BE Tax Info",
"PEPPOL Payment Info Provider" = "PEPPOL30 BE Payment Info",
"PEPPOL Monetary Info Provider" = "PEPPOL30 BE Monetary Info";
}
value(37311; "PEPPOL 3.0 - BE Service")
{
Caption = 'PEPPOL 3.0 - Belgium Service Format';
Implementation = "PEPPOL30 Validation" = "PEPPOL30 BE Service Validation",
"PEPPOL Posted Document Iterator" = "PEPPOL30 Services Iterator",
"PEPPOL Tax Info Provider" = "PEPPOL30 BE Tax Info";
"PEPPOL Tax Info Provider" = "PEPPOL30 BE Tax Info",
"PEPPOL Payment Info Provider" = "PEPPOL30 BE Payment Info",
"PEPPOL Monetary Info Provider" = "PEPPOL30 BE Monetary Info";
}
}
Loading
Loading