Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,39 @@ codeunit 10692 "Generate SAF-T 1.3 File"
until SAFTSourceCode.Next() = 0;
end;

internal procedure CalculateDebitCreditTotals(var SAFTExportHeader: Record "SAF-T Export Header")
var
SAFTSourceCode: Record "SAF-T Source Code";
TempSourceCode: Record "Source Code" temporary;
SAFTGLEntryTotal: Query "SAF-T G/L Entry Total";
SourceCodeFilter: Text;
begin
SAFTExportHeader."Total G/L Entry Debit" := 0;
SAFTExportHeader."Total G/L Entry Credit" := 0;
if not SAFTSourceCode.FindSet() then
SAFTSourceCode.Init();
repeat
PopulateSourceCodeBuffer(TempSourceCode, SAFTSourceCode);
SourceCodeFilter := GetSourceCodeFilter(TempSourceCode);
if SourceCodeFilter <> '' then begin
Clear(SAFTGLEntryTotal);
SAFTGLEntryTotal.SetRange(PostingDate, SAFTExportHeader."Starting Date", SAFTExportHeader."Ending Date");
SAFTGLEntryTotal.SetFilter(SourceCode, SourceCodeFilter);
SAFTGLEntryTotal.SetFilter(AmountFilter, '>%1', 0);
SAFTGLEntryTotal.Open();
if SAFTGLEntryTotal.Read() then
SAFTExportHeader."Total G/L Entry Debit" += SAFTGLEntryTotal.TotalAmount;
SAFTGLEntryTotal.Close();

SAFTGLEntryTotal.SetFilter(AmountFilter, '<%1', 0);
SAFTGLEntryTotal.Open();
if SAFTGLEntryTotal.Read() then
SAFTExportHeader."Total G/L Entry Credit" -= SAFTGLEntryTotal.TotalAmount;
SAFTGLEntryTotal.Close();
end;
until SAFTSourceCode.Next() = 0;
end;

local procedure PopulateSourceCodeBuffer(var TempSourceCode: Record "Source Code" temporary; var SAFTSourceCode: Record "SAF-T Source Code")
var
SourceCode: Record "Source Code";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,18 @@ codeunit 10675 "SAF-T Export Mgt."
GenerateSAFT13File: Codeunit "Generate SAF-T 1.3 File";
SAFTGLEntryByTrans: Query "SAF-T G/L Entry By Trans.";
begin
GLEntry.SetCurrentKey("Transaction No.");
GLEntry.SetRange("Posting Date", SAFTExportHeader."Starting Date", SAFTExportHeader."Ending Date");
GLEntry.CalcSums("Debit Amount", "Credit Amount");
SAFTExportHeader."Total G/L Entry Debit" := GLEntry."Debit Amount";
SAFTExportHeader."Total G/L Entry Credit" := GLEntry."Credit Amount";
SAFTExportHeader."Number of G/L Entries" := 0;

if SAFTExportHeader.Version = SAFTExportHeader.Version::"1.30" then
SAFTExportHeader."Number of G/L Entries" := GenerateSAFT13File.CountTransactions(SAFTExportHeader)
if SAFTExportHeader.Version = SAFTExportHeader.Version::"1.30" then begin
GenerateSAFT13File.CalculateDebitCreditTotals(SAFTExportHeader);
SAFTExportHeader."Number of G/L Entries" := GenerateSAFT13File.CountTransactions(SAFTExportHeader);
end
else begin
GLEntry.SetCurrentKey("Transaction No.");
GLEntry.SetRange("Posting Date", SAFTExportHeader."Starting Date", SAFTExportHeader."Ending Date");
GLEntry.CalcSums("Debit Amount", "Credit Amount");
SAFTExportHeader."Total G/L Entry Debit" := GLEntry."Debit Amount";
SAFTExportHeader."Total G/L Entry Credit" := GLEntry."Credit Amount";
SAFTGLEntryByTrans.SetRange(Posting_Date, SAFTExportHeader."Starting Date", SAFTExportHeader."Ending Date");
if SAFTGLEntryByTrans.Open() then begin
while SAFTGLEntryByTrans.Read() do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.AuditFileExport;

using Microsoft.Finance.GeneralLedger.Ledger;

query 10672 "SAF-T G/L Entry Total"
{
DataAccessIntent = ReadOnly;

elements
{
dataitem(GLEntry; "G/L Entry")
{
filter(PostingDate; "Posting Date")
{
}
filter(SourceCode; "Source Code")
{
}
filter(AmountFilter; Amount)
{
}
column(TotalAmount; Amount)
{
Method = Sum;
}
}
}
}
4 changes: 4 additions & 0 deletions src/Apps/NO/NorwegianSAFT/test/src/SAFTXMLTests.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,10 @@ codeunit 148103 "SAF-T XML Tests"
TempXMLBuffer.Next();
VerifyChildElementsCount(TempChildXMLBuffer, TempXMLBuffer, 1);
SAFTTestHelper.AssertCurrentElementValue(TempChildXMLBuffer, 'n1:Amount', SAFTTestHelper.FormatAmount(PaymentAmount));

// [THEN] SAF-T 1.20 retains its signed debit/credit column totals after reversal
SAFTTestHelper.AssertCurrentValue(TempXMLBuffer, '/n1:AuditFile/n1:GeneralLedgerEntries/n1:TotalDebit', '0');
SAFTTestHelper.AssertCurrentValue(TempXMLBuffer, '/n1:AuditFile/n1:GeneralLedgerEntries/n1:TotalCredit', '0');
end;

[Test]
Expand Down
171 changes: 170 additions & 1 deletion src/Apps/NO/NorwegianSAFT/test/src/SAFTXMLTests13.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -1693,7 +1693,7 @@ codeunit 148110 "SAF-T XML Tests 1.3"
Amount[2] := LibraryRandom.RandDec(100, 2);
SAFTTestHelper.MockGLEntry(
SAFTExportHeader."Ending Date", LibraryUtility.GenerateGUID(), '',
LastUsedTransactionNo + 2, 0, 0, '', '', 0, '', '', 0, Amount[2]);
LastUsedTransactionNo + 2, 0, 0, '', '', 0, '', '', 0, -Amount[2]);

// [WHEN] Export G/L Entries to the XML file
LibraryVariableStorage.Enqueue(GenerateSAFTFileImmediatelyQst);
Expand Down Expand Up @@ -1979,6 +1979,9 @@ codeunit 148110 "SAF-T XML Tests 1.3"
TempXMLBuffer.Next();
VerifyChildElementsCount(TempChildXMLBuffer, TempXMLBuffer, 1);
SAFTTestHelper.AssertCurrentElementValue(TempChildXMLBuffer, 'n1:Amount', SAFTTestHelper.FormatAmount(PaymentAmount));

// [THEN] Reversals contribute positive amounts on the opposite XML side, including in the control totals
VerifySelectionAmountTotals(SAFTExportHeader.ID, 1, 2 * PaymentAmount, 2 * PaymentAmount);
end;

[Test]
Expand Down Expand Up @@ -2058,6 +2061,7 @@ codeunit 148110 "SAF-T XML Tests 1.3"
VerifyChildElementsCount(TempChildXMLBuffer, TempXMLBuffer, 1);
SAFTTestHelper.AssertCurrentElementValue(
TempChildXMLBuffer, 'n1:Amount', SAFTTestHelper.FormatAmount(Abs(GenJournalLine."Amount (LCY)")));
VerifySelectionAmountTotals(SAFTExportHeader.ID, 1, Abs(GenJournalLine."Amount (LCY)"), Abs(GenJournalLine."Amount (LCY)"));
LibraryVariableStorage.AssertEmpty();
end;

Expand Down Expand Up @@ -2388,6 +2392,7 @@ codeunit 148110 "SAF-T XML Tests 1.3"
// [THEN] The fallback journal preserves all groups and lines
Assert.ExpectedMessage(GenerateSAFTFileImmediatelyQst, LibraryVariableStorage.DequeueText());
VerifySelectionTransactionCount(SAFTExportHeader, 1, 3, 4);
VerifySelectionAmountTotals(SAFTExportHeader.ID, 1, 400, 0);
LibraryVariableStorage.AssertEmpty();
end;

Expand All @@ -2412,6 +2417,7 @@ codeunit 148110 "SAF-T XML Tests 1.3"
// [THEN] The generated file contains no transactions or lines and reports zero
Assert.ExpectedMessage(GenerateSAFTFileImmediatelyQst, LibraryVariableStorage.DequeueText());
VerifySelectionTransactionCount(SAFTExportHeader, 1, 0, 0);
VerifySelectionAmountTotals(SAFTExportHeader.ID, 1, 0, 0);
LibraryVariableStorage.AssertEmpty();
end;

Expand Down Expand Up @@ -2488,6 +2494,7 @@ codeunit 148110 "SAF-T XML Tests 1.3"

// [THEN] Both G/L files retain exact header values, positive credit magnitudes and metadata, and no excluded analysis
VerifySelectionTransactionCount(SAFTExportHeader, 2, 3, 3);
VerifySelectionAmountTotals(SAFTExportHeader.ID, 2, 100, 200);
SAFTExportLine.SetRange("Master Data", false);
SAFTTestHelper.FindSAFTExportLine(SAFTExportLine, SAFTExportHeader.ID);
repeat
Expand Down Expand Up @@ -2521,6 +2528,87 @@ codeunit 148110 "SAF-T XML Tests 1.3"
LibraryVariableStorage.AssertEmpty();
end;

[Test]
[HandlerFunctions('CaptureConfirmationHandler')]
procedure AmountTotalsIncludeNegativeDebitAndCreditEntries()
var
SAFTExportHeader: Record "SAF-T Export Header";
GLAccount: Record "G/L Account";
SourceCode: Record "Source Code";
begin
// [FEATURE] [AI test 1.0] [Totals]
// [SCENARIO] Negative debit and credit entries contribute to the opposite XML side across journals.
Initialize();

// [GIVEN] Journals "J1", "J2" and "J3" contain ordinary and correction entries, including a blank source code
CreateTransactionCountSetup(SAFTExportHeader, GLAccount, SourceCode);
CreateAmountTotalsEntries(SAFTExportHeader, GLAccount."No.", SourceCode);

// [WHEN] Export a single transaction file
SAFTTestHelper.RunSAFTExport(SAFTExportHeader);

// [THEN] The control totals equal the debit and credit amounts in every journal
Assert.ExpectedMessage(GenerateSAFTFileImmediatelyQst, LibraryVariableStorage.DequeueText());
VerifySelectionAmountTotals(SAFTExportHeader.ID, 1, 120, 110);
LibraryVariableStorage.AssertEmpty();
end;

[Test]
[HandlerFunctions('CaptureConfirmationHandler')]
procedure AmountTotalsRepeatAcrossMonthlyFiles()
var
SAFTExportHeader: Record "SAF-T Export Header";
GLAccount: Record "G/L Account";
SourceCode: Record "Source Code";
begin
// [FEATURE] [AI test 1.0] [Totals]
// [SCENARIO] Each monthly file repeats the debit and credit sums of all transaction files in the selection.
Initialize();

// [GIVEN] Ordinary and correction entries in two months, plus entries outside the selection
CreateTransactionCountSetup(SAFTExportHeader, GLAccount, SourceCode);
SAFTExportHeader.Validate("Ending Date", CalcDate('<1M+CM>', SAFTExportHeader."Starting Date"));
SAFTExportHeader.Validate("Split By Month", true);
SAFTExportHeader.Modify(true);
CreateAmountTotalsEntries(SAFTExportHeader, GLAccount."No.", SourceCode);

// [WHEN] Export monthly transaction files
SAFTTestHelper.RunSAFTExport(SAFTExportHeader);

// [THEN] Both files repeat the entire selection totals without including outside-period entries
Assert.ExpectedMessage(GenerateSAFTFileImmediatelyQst, LibraryVariableStorage.DequeueText());
VerifySelectionAmountTotals(SAFTExportHeader.ID, 2, 120, 110);
LibraryVariableStorage.AssertEmpty();
end;

[Test]
[HandlerFunctions('CaptureConfirmationHandler')]
procedure AmountTotalsRepeatAcrossDailyFiles()
var
SAFTExportHeader: Record "SAF-T Export Header";
GLAccount: Record "G/L Account";
SourceCode: Record "Source Code";
begin
// [FEATURE] [AI test 1.0] [Totals]
// [SCENARIO] Each daily file repeats the debit and credit sums of the complete selection.
Initialize();

// [GIVEN] Ordinary and correction entries on two days separated by an empty day
CreateTransactionCountSetup(SAFTExportHeader, GLAccount, SourceCode);
SAFTExportHeader.Validate("Ending Date", SAFTExportHeader."Starting Date" + 2);
SAFTExportHeader.Validate("Split By Date", true);
SAFTExportHeader.Modify(true);
CreateAmountTotalsEntries(SAFTExportHeader, GLAccount."No.", SourceCode);

// [WHEN] Export daily transaction files
SAFTTestHelper.RunSAFTExport(SAFTExportHeader);

// [THEN] Both files repeat the entire selection totals, not just their own line amounts
Assert.ExpectedMessage(GenerateSAFTFileImmediatelyQst, LibraryVariableStorage.DequeueText());
VerifySelectionAmountTotals(SAFTExportHeader.ID, 2, 120, 110);
LibraryVariableStorage.AssertEmpty();
end;

local procedure Initialize()
begin
LibraryTestInitialize.OnTestInitialize(CODEUNIT::"SAF-T XML Tests 1.3");
Expand Down Expand Up @@ -2678,6 +2766,42 @@ codeunit 148110 "SAF-T XML Tests 1.3"
CashReceiptJournal.Post.Invoke();
end;

local procedure CreateAmountTotalsEntries(SAFTExportHeader: Record "SAF-T Export Header"; GLAccountNo: Code[20]; SourceCode: Record "Source Code")
var
SAFTSourceCode: Record "SAF-T Source Code";
SecondSourceCode: Record "Source Code";
begin
SAFTSourceCode.ModifyAll("Includes No Source Code", false);
CreateTransactionCountJournal(SAFTSourceCode, false);
LibraryERM.CreateSourceCode(SecondSourceCode);
SecondSourceCode.Validate("SAF-T Source Code", SAFTSourceCode.Code);
SecondSourceCode.Modify(true);
CreateTransactionCountJournal(SAFTSourceCode, true);
CreateTransactionCountJournal(SAFTSourceCode, false);

CreateAmountTotalsEntry(SAFTExportHeader."Starting Date", GLAccountNo, SourceCode.Code, 100, false);
CreateAmountTotalsEntry(SAFTExportHeader."Starting Date", GLAccountNo, SourceCode.Code, -70, false);
CreateAmountTotalsEntry(SAFTExportHeader."Ending Date", GLAccountNo, SecondSourceCode.Code, -40, true);
CreateAmountTotalsEntry(SAFTExportHeader."Ending Date", GLAccountNo, '', 20, true);
CreateAmountTotalsEntry(SAFTExportHeader."Ending Date", GLAccountNo, SourceCode.Code, 0, false);
CreateAmountTotalsEntry(SAFTExportHeader."Starting Date" - 1, GLAccountNo, SourceCode.Code, 900, false);
CreateAmountTotalsEntry(SAFTExportHeader."Ending Date" + 1, GLAccountNo, SecondSourceCode.Code, -800, false);
end;

local procedure CreateAmountTotalsEntry(PostingDate: Date; GLAccountNo: Code[20]; SourceCode: Code[10]; Amount: Decimal; IsCorrection: Boolean)
var
GLEntry: Record "G/L Entry";
begin
GLEntry.Get(
SAFTTestHelper.MockGLEntry(
PostingDate, LibraryUtility.GenerateGUID(), GLAccountNo, GetLastUsedTransactionNo() + 1,
0, '', '', 0, '', SourceCode, Amount, 0));
GLEntry.UpdateDebitCredit(IsCorrection);
#pragma warning disable AA0214
GLEntry.Modify(true);
#pragma warning restore AA0214
end;

local procedure LoadAnalysisExportFiles(SAFTExportHeader: Record "SAF-T Export Header"; var TempMasterXMLBuffer: Record "XML Buffer" temporary; var TempGLEntryXMLBuffer: Record "XML Buffer" temporary)
var
SAFTExportLine: Record "SAF-T Export Line";
Expand Down Expand Up @@ -3490,6 +3614,51 @@ codeunit 148110 "SAF-T XML Tests 1.3"
VerifyCurrencyAmountInfo(TempChildXMLBuffer, CurrencyCode, Amount, AmountLCY, ExchangeRate);
end;

local procedure VerifySelectionAmountTotals(ExportID: Integer; ExpectedFiles: Integer; ExpectedDebit: Decimal; ExpectedCredit: Decimal)
var
SAFTExportLine: Record "SAF-T Export Line";
TempXMLBuffer: Record "XML Buffer" temporary;
TotalDebit: Decimal;
TotalCredit: Decimal;
begin
#pragma warning disable AA0210
SAFTExportLine.SetRange("Master Data", false);
#pragma warning restore AA0210
SAFTTestHelper.FindSAFTExportLine(SAFTExportLine, ExportID);
Assert.RecordCount(SAFTExportLine, ExpectedFiles);
repeat
SAFTTestHelper.LoadXMLBufferFromSAFTExportLine(TempXMLBuffer, SAFTExportLine);
TotalDebit += SumXMLLineAmounts(TempXMLBuffer, 'DebitAmount');
TotalCredit += SumXMLLineAmounts(TempXMLBuffer, 'CreditAmount');
until SAFTExportLine.Next() = 0;
Assert.AreEqual(ExpectedDebit, TotalDebit, 'Unexpected sum of exported debit amounts.');
Assert.AreEqual(ExpectedCredit, TotalCredit, 'Unexpected sum of exported credit amounts.');

SAFTExportLine.FindSet();
repeat
SAFTTestHelper.LoadXMLBufferFromSAFTExportLine(TempXMLBuffer, SAFTExportLine);
SAFTTestHelper.AssertCurrentValue(
TempXMLBuffer, '/n1:AuditFile/n1:GeneralLedgerEntries/n1:TotalDebit', SAFTTestHelper.FormatAmount(TotalDebit));
SAFTTestHelper.AssertCurrentValue(
TempXMLBuffer, '/n1:AuditFile/n1:GeneralLedgerEntries/n1:TotalCredit', SAFTTestHelper.FormatAmount(TotalCredit));
until SAFTExportLine.Next() = 0;
end;

local procedure SumXMLLineAmounts(var TempXMLBuffer: Record "XML Buffer" temporary; AmountNode: Text) TotalAmount: Decimal
var
Amount: Decimal;
begin
TempXMLBuffer.Reset();
TempXMLBuffer.FindNodesByXPath(
TempXMLBuffer, '/n1:AuditFile/n1:GeneralLedgerEntries/n1:Journal/n1:Transaction/n1:Line/n1:' + AmountNode + '/n1:Amount');
if TempXMLBuffer.FindSet() then
repeat
Evaluate(Amount, TempXMLBuffer.Value, 9);
TotalAmount += Amount;
until TempXMLBuffer.Next() = 0;
TempXMLBuffer.Reset();
end;

local procedure CreateAndPostPaymentJnl(
var GenJournalLine: array[3] of Record "Gen. Journal Line";
SAFTExportHeader: Record "SAF-T Export Header")
Expand Down
Loading