Skip to content
Draft
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
32 changes: 32 additions & 0 deletions src/Layers/BE/BaseApp/Utilities/DocumentTotals.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.Utilities;
using Microsoft.Finance.Currency;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.SalesTax;
using Microsoft.Finance.VAT.Calculation;
using Microsoft.Purchases.Document;
using Microsoft.Purchases.History;
using Microsoft.Purchases.Posting;
Expand Down Expand Up @@ -671,6 +672,8 @@ codeunit 57 "Document Totals"
TotalPurchaseLine2: Record "Purchase Line";
PurchaseLineWithReverseChargeVAT: Record "Purchase Line";
VATAmountOfLinesWithRevChargeVAT: Decimal;
NonDeductibleVATAmount: Decimal;
GroupedVATAmount: Decimal;
IsHandled: Boolean;
begin
IsHandled := false;
Expand Down Expand Up @@ -747,14 +750,43 @@ codeunit 57 "Document Totals"
repeat
TotalPurchaseLine2.Amount += PurchaseLine2.GetNonDeductibleVATAmount();
VATAmount -= PurchaseLine2.GetNonDeductibleVATAmount();
NonDeductibleVATAmount += PurchaseLine2.GetNonDeductibleVATAmount();
until PurchaseLine2.Next() = 0;

// Reconcile with grouped (statistics) VAT only when no BE-specific reverse charge or non-deductible VAT adjustments apply
if (VATAmountOfLinesWithRevChargeVAT = 0) and (NonDeductibleVATAmount = 0) then
if GetGroupedVATAmount(TotalPurchaseHeader, GroupedVATAmount) and (GroupedVATAmount <> VATAmount) then begin
Comment thread
DeepsShukla marked this conversation as resolved.
VATAmount := GroupedVATAmount;
TotalPurchaseLine2."Amount Including VAT" := TotalPurchaseLine2.Amount + VATAmount;
TotalPurchaseLine."Amount Including VAT" := TotalPurchaseLine2."Amount Including VAT";
end;

OnAfterCalculatePurchaseSubPageTotals(
TotalPurchaseHeader, TotalPurchaseLine, VATAmount, InvoiceDiscountAmount, InvoiceDiscountPct, TotalPurchaseLine2);

TotalPurchaseLine := TotalPurchaseLine2;
end;

local procedure GetGroupedVATAmount(var PurchHeader: Record "Purchase Header"; var GroupedVATAmount: Decimal): Boolean
var
PurchLine: Record "Purchase Line";
TempVATAmountLine: Record "VAT Amount Line" temporary;
begin
if PurchHeader."No." = '' then
exit(false);

PurchasesPayablesSetup.Get();
if not PurchasesPayablesSetup."Allow VAT Difference" then
exit(false);

if PurchHeader."Tax Liable" then
exit(false);

PurchLine.CalcVATAmountLines(0, PurchHeader, PurchLine, TempVATAmountLine);
GroupedVATAmount := TempVATAmountLine.GetTotalVATAmount();
exit(true);
end;

procedure CalculatePostedPurchInvoiceTotals(var PurchInvHeader: Record "Purch. Inv. Header"; var VATAmount: Decimal; PurchInvLine: Record "Purch. Inv. Line")
var
CurrPurchInvLine: Record "Purch. Inv. Line";
Expand Down
37 changes: 37 additions & 0 deletions src/Layers/NA/BaseApp/Utilities/DocumentTotals.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.Utilities;
using Microsoft.Finance.Currency;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.SalesTax;
using Microsoft.Finance.VAT.Calculation;
using Microsoft.Purchases.Document;
using Microsoft.Purchases.History;
using Microsoft.Purchases.Posting;
Expand Down Expand Up @@ -614,6 +615,8 @@ codeunit 57 "Document Totals"

procedure PurchaseDeltaUpdateTotals(var PurchaseLine: Record "Purchase Line"; var xPurchaseLine: Record "Purchase Line"; var TotalPurchaseLine: Record "Purchase Line"; var VATAmount: Decimal; var InvoiceDiscountAmount: Decimal; var InvoiceDiscountPct: Decimal)
var
PurchHeader: Record "Purchase Header";
GroupedVATAmount: Decimal;
InvDiscountBaseAmount: Decimal;
IsHandled: Boolean;
begin
Expand All @@ -638,6 +641,13 @@ codeunit 57 "Document Totals"
InvoiceDiscountPct := Round(100 * InvoiceDiscountAmount / InvDiscountBaseAmount, 0.00001);
end;

if PurchHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.") then
Comment thread
DeepsShukla marked this conversation as resolved.
if GetGroupedVATAmount(PurchHeader, GroupedVATAmount) then
if GroupedVATAmount <> VATAmount then begin
VATAmount := GroupedVATAmount;
TotalPurchaseLine."Amount Including VAT" := TotalPurchaseLine.Amount + VATAmount;
end;

OnAfterPurchDeltaUpdateTotals(PurchaseLine, xPurchaseLine, TotalPurchaseLine, VATAmount, InvoiceDiscountAmount, InvoiceDiscountPct);
end;

Expand Down Expand Up @@ -666,6 +676,7 @@ codeunit 57 "Document Totals"
PurchaseHeader: Record "Purchase Header";
PurchaseLine2: Record "Purchase Line";
TotalPurchaseLine2: Record "Purchase Line";
GroupedVATAmount: Decimal;
IsHandled: Boolean;
begin
IsHandled := false;
Expand Down Expand Up @@ -723,12 +734,38 @@ codeunit 57 "Document Totals"
end;
end;

if GetGroupedVATAmount(TotalPurchaseHeader, GroupedVATAmount) and (GroupedVATAmount <> VATAmount) then begin
Comment thread
DeepsShukla marked this conversation as resolved.
VATAmount := GroupedVATAmount;
TotalPurchaseLine2."Amount Including VAT" := TotalPurchaseLine2.Amount + VATAmount;
TotalPurchaseLine."Amount Including VAT" := TotalPurchaseLine2."Amount Including VAT";
end;

OnAfterCalculatePurchaseSubPageTotals(
TotalPurchaseHeader, TotalPurchaseLine, VATAmount, InvoiceDiscountAmount, InvoiceDiscountPct, TotalPurchaseLine2);

TotalPurchaseLine := TotalPurchaseLine2;
end;

local procedure GetGroupedVATAmount(var PurchHeader: Record "Purchase Header"; var GroupedVATAmount: Decimal): Boolean
var
PurchLine: Record "Purchase Line";
TempVATAmountLine: Record "VAT Amount Line" temporary;
begin
if PurchHeader."No." = '' then
exit(false);

PurchasesPayablesSetup.Get();
if not PurchasesPayablesSetup."Allow VAT Difference" then
exit(false);

if PurchHeader."Tax Liable" then
exit(false);

PurchLine.CalcVATAmountLines(0, PurchHeader, PurchLine, TempVATAmountLine);
GroupedVATAmount := TempVATAmountLine.GetTotalVATAmount();
exit(true);
end;

procedure CalculatePostedPurchInvoiceTotals(var PurchInvHeader: Record "Purch. Inv. Header"; var VATAmount: Decimal; PurchInvLine: Record "Purch. Inv. Line")
var
IsHandled: Boolean;
Expand Down
37 changes: 37 additions & 0 deletions src/Layers/W1/BaseApp/Utilities/DocumentTotals.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.Utilities;
using Microsoft.Finance.Currency;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.SalesTax;
using Microsoft.Finance.VAT.Calculation;
using Microsoft.Purchases.Document;
using Microsoft.Purchases.History;
using Microsoft.Purchases.Posting;
Expand Down Expand Up @@ -599,6 +600,8 @@ codeunit 57 "Document Totals"

procedure PurchaseDeltaUpdateTotals(var PurchaseLine: Record "Purchase Line"; var xPurchaseLine: Record "Purchase Line"; var TotalPurchaseLine: Record "Purchase Line"; var VATAmount: Decimal; var InvoiceDiscountAmount: Decimal; var InvoiceDiscountPct: Decimal)
var
PurchHeader: Record "Purchase Header";
GroupedVATAmount: Decimal;
InvDiscountBaseAmount: Decimal;
IsHandled: Boolean;
begin
Expand All @@ -623,6 +626,13 @@ codeunit 57 "Document Totals"
InvoiceDiscountPct := Round(100 * InvoiceDiscountAmount / InvDiscountBaseAmount, 0.00001);
end;

if PurchHeader.Get(PurchaseLine."Document Type", PurchaseLine."Document No.") then
Comment thread
DeepsShukla marked this conversation as resolved.
if GetGroupedVATAmount(PurchHeader, GroupedVATAmount) then
if GroupedVATAmount <> VATAmount then begin
VATAmount := GroupedVATAmount;
TotalPurchaseLine."Amount Including VAT" := TotalPurchaseLine.Amount + VATAmount;
end;

OnAfterPurchDeltaUpdateTotals(PurchaseLine, xPurchaseLine, TotalPurchaseLine, VATAmount, InvoiceDiscountAmount, InvoiceDiscountPct);
end;

Expand Down Expand Up @@ -651,6 +661,7 @@ codeunit 57 "Document Totals"
PurchaseHeader: Record "Purchase Header";
PurchaseLine2: Record "Purchase Line";
TotalPurchaseLine2: Record "Purchase Line";
GroupedVATAmount: Decimal;
IsHandled: Boolean;
begin
IsHandled := false;
Expand Down Expand Up @@ -708,12 +719,38 @@ codeunit 57 "Document Totals"
end;
end;

if GetGroupedVATAmount(TotalPurchaseHeader, GroupedVATAmount) and (GroupedVATAmount <> VATAmount) then begin
Comment thread
DeepsShukla marked this conversation as resolved.
VATAmount := GroupedVATAmount;
TotalPurchaseLine2."Amount Including VAT" := TotalPurchaseLine2.Amount + VATAmount;
TotalPurchaseLine."Amount Including VAT" := TotalPurchaseLine2."Amount Including VAT";
end;

OnAfterCalculatePurchaseSubPageTotals(
TotalPurchaseHeader, TotalPurchaseLine, VATAmount, InvoiceDiscountAmount, InvoiceDiscountPct, TotalPurchaseLine2);

TotalPurchaseLine := TotalPurchaseLine2;
end;

local procedure GetGroupedVATAmount(var PurchHeader: Record "Purchase Header"; var GroupedVATAmount: Decimal): Boolean
var
PurchLine: Record "Purchase Line";
TempVATAmountLine: Record "VAT Amount Line" temporary;
begin
if PurchHeader."No." = '' then
exit(false);

PurchasesPayablesSetup.Get();
if not PurchasesPayablesSetup."Allow VAT Difference" then
exit(false);

if PurchHeader."Tax Liable" then
exit(false);

PurchLine.CalcVATAmountLines(0, PurchHeader, PurchLine, TempVATAmountLine);
GroupedVATAmount := TempVATAmountLine.GetTotalVATAmount();
exit(true);
end;

procedure CalculatePostedPurchInvoiceTotals(var PurchInvHeader: Record "Purch. Inv. Header"; var VATAmount: Decimal; PurchInvLine: Record "Purch. Inv. Line")
var
IsHandled: Boolean;
Expand Down
86 changes: 86 additions & 0 deletions src/Layers/W1/Tests/ERM/DocumentTotalsPages.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,82 @@ codeunit 134344 "Document Totals Pages"
Assert.AreEqual(NewDescription, SalesOrder.SalesLines.Description.Value(), LineDescriptionRevertedErr);
end;

[Test]
[HandlerFunctions('PurchaseInvoiceStatisticsUpdateVATAmountPageHandler')]
procedure PurchInvTotalInclVATMatchesStatisticsAfterVATAdjMixedVATGroupsSameAccount()
var
PurchaseHeader: Record "Purchase Header";
PurchaseLine: Record "Purchase Line";
VATPostingSetup: array[2] of Record "VAT Posting Setup";
VATProductPostingGroup: Record "VAT Product Posting Group";
PurchaseInvoicePage: TestPage "Purchase Invoice";
GLAccountNo: Code[20];
MaxVATDifference: Decimal;
TotalAmountInclVATBefore: Decimal;
TotalVATAmountBefore: Decimal;
VATAdjustment: Decimal;
begin
// [FEATURE] [UI] [VAT] [Purchase] [VAT Difference]
// [SCENARIO 637288] "Total VAT Amount" and "Total Amount Incl. VAT" on Purchase Invoice subform must update
// after VAT adjustment when invoice has mixed VAT groups on the same G/L Account with negative lines.
Initialize();
Comment thread
DeepsShukla marked this conversation as resolved.

// [GIVEN] "VAT Difference" is allowed with random Max VAT Difference
MaxVATDifference := LibraryRandom.RandDecInRange(1, 5, 2);
LibraryERM.SetMaxVATDifferenceAllowed(MaxVATDifference);
LibraryPurchase.SetAllowVATDifference(true);

// [GIVEN] Two VAT Posting Setups with random VAT%, same VAT Bus. Posting Group
Comment thread
DeepsShukla marked this conversation as resolved.
LibraryERM.CreateVATPostingSetupWithAccounts(
VATPostingSetup[1], VATPostingSetup[1]."VAT Calculation Type"::"Normal VAT", LibraryRandom.RandIntInRange(5, 15));
LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup);
LibraryERM.CreateVATPostingSetup(VATPostingSetup[2], VATPostingSetup[1]."VAT Bus. Posting Group", VATProductPostingGroup.Code);
VATPostingSetup[2].Validate("VAT Calculation Type", VATPostingSetup[2]."VAT Calculation Type"::"Normal VAT");
VATPostingSetup[2].Validate("VAT %", LibraryRandom.RandIntInRange(16, 25));
VATPostingSetup[2].Validate("VAT Identifier", CopyStr(VATProductPostingGroup.Code, 1, MaxStrLen(VATPostingSetup[2]."VAT Identifier")));
VATPostingSetup[2]."Purchase VAT Account" := LibraryERM.CreateGLAccountNo();
VATPostingSetup[2].Modify(true);

// [GIVEN] Purchase Invoice with mixed VAT lines on the same G/L Account, including negative adjustment lines
GLAccountNo := LibraryERM.CreateGLAccountWithPurchSetup();
LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, LibraryPurchase.CreateVendorNo());
CreatePurchLineWithGLAccAndVATSetup(PurchaseLine, PurchaseHeader, VATPostingSetup[1], GLAccountNo, LibraryRandom.RandDecInRange(1000, 5000, 2));
CreatePurchLineWithGLAccAndVATSetup(PurchaseLine, PurchaseHeader, VATPostingSetup[1], GLAccountNo, -LibraryRandom.RandDecInRange(100, 500, 2));
CreatePurchLineWithGLAccAndVATSetup(PurchaseLine, PurchaseHeader, VATPostingSetup[2], GLAccountNo, LibraryRandom.RandDecInRange(1000, 5000, 2));
CreatePurchLineWithGLAccAndVATSetup(PurchaseLine, PurchaseHeader, VATPostingSetup[2], GLAccountNo, -LibraryRandom.RandDecInRange(100, 900, 2));

// [GIVEN] Open Purchase Invoice page and capture totals before adjustment
PurchaseInvoicePage.OpenEdit();
PurchaseInvoicePage.Filter.SetFilter("No.", PurchaseHeader."No.");
PurchaseInvoicePage.PurchLines.Last();
TotalVATAmountBefore := PurchaseInvoicePage.PurchLines."Total VAT Amount".AsDecimal();
TotalAmountInclVATBefore := PurchaseInvoicePage.PurchLines."Total Amount Incl. VAT".AsDecimal();

// [WHEN] VAT Amount adjusted on Statistics page (within allowed VAT difference)
VATAdjustment := -LibraryRandom.RandDecInRange(0, MaxVATDifference, 2);
LibraryVariableStorage.Enqueue(VATAdjustment);
PurchaseInvoicePage.PurchaseStatistics.Invoke();
Comment thread
DeepsShukla marked this conversation as resolved.
PurchaseInvoicePage.Close();

// [THEN] "Total VAT Amount" on the subform reflects the VAT adjustment
PurchaseInvoicePage.OpenEdit();
PurchaseInvoicePage.Filter.SetFilter("No.", PurchaseHeader."No.");
PurchaseInvoicePage.PurchLines.Last();
Assert.AreEqual(
TotalVATAmountBefore + VATAdjustment,
PurchaseInvoicePage.PurchLines."Total VAT Amount".AsDecimal(),
StrSubstNo(VATAmountErr, PurchaseInvoicePage.PurchLines."Total VAT Amount".Caption, 'expected Total VAT Amount'));

// [THEN] "Total Amount Incl. VAT" on the subform reflects the VAT adjustment
Assert.AreEqual(
TotalAmountInclVATBefore + VATAdjustment,
PurchaseInvoicePage.PurchLines."Total Amount Incl. VAT".AsDecimal(),
StrSubstNo(VATAmountErr, PurchaseInvoicePage.PurchLines."Total Amount Incl. VAT".Caption, 'expected Total Amount Incl. VAT'));

PurchaseInvoicePage.Close();
LibraryVariableStorage.AssertEmpty();
end;

local procedure Initialize()
begin
LibrarySetupStorage.Restore();
Expand Down Expand Up @@ -2461,6 +2537,16 @@ codeunit 134344 "Document Totals Pages"
SalesLine.Modify();
end;

local procedure CreatePurchLineWithGLAccAndVATSetup(var PurchaseLine: Record "Purchase Line"; PurchaseHeader: Record "Purchase Header"; VATPostingSetup: Record "VAT Posting Setup"; GLAccountNo: Code[20]; DirectUnitCost: Decimal)
begin
LibraryPurchase.CreatePurchaseLine(
PurchaseLine, PurchaseHeader, PurchaseLine.Type::"G/L Account", GLAccountNo, 1);
PurchaseLine."VAT Bus. Posting Group" := VATPostingSetup."VAT Bus. Posting Group";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Testing}$

CreatePurchLineWithGLAccAndVATSetup assigns PurchaseLine."VAT Bus. Posting Group" directly while building the fixture. That bypasses the field's TableRelation and OnValidate logic, so the test data can drift from production behavior when VAT setup validation changes.

Suggested fix (apply manually — could not be anchored as a one-click suggestion):

        PurchaseLine.Validate("VAT Bus. Posting Group", VATPostingSetup."VAT Bus. Posting Group");

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.32.4

PurchaseLine.Validate("VAT Prod. Posting Group", VATPostingSetup."VAT Prod. Posting Group");
PurchaseLine.Validate("Direct Unit Cost", DirectUnitCost);
PurchaseLine.Modify(true);
end;

[ConfirmHandler]
[Scope('OnPrem')]
procedure ConfirmHandlerYes(Question: Text[1024]; var Reply: Boolean)
Expand Down
Loading