Skip to content

Features/494919 master mx cfdi rounding e invoice docs#9697

Open
v-mjofre wants to merge 13 commits into
mainfrom
features/494919-master-MX-cfdi-rounding-e-invoice-docs
Open

Features/494919 master mx cfdi rounding e invoice docs#9697
v-mjofre wants to merge 13 commits into
mainfrom
features/494919-master-MX-cfdi-rounding-e-invoice-docs

Conversation

@v-mjofre

@v-mjofre v-mjofre commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

ISSUE

When requesting a stamp (Request Stamp) on sales invoices in the Mexican localization of Business Central, the generated CFDI XML produced rounding mismatches in SubTotal and per-line tax amounts, causing PAC validation errors:

CFDI40108 – SubTotal does not equal the sum of line Importe values
CFDI40167 – Per-line tax Importe does not equal
Round(Base × TasaOCuota, 6)

This occurred primarily when:

The invoice contained multiple lines (e.g., 11+)
Unit prices and quantities produced amounts where standard rounding introduced accumulated differences
Retention lines were present alongside regular lines

CAUSE

The logic responsible for calculating SubTotal in the CFDI XML did not recalculate the value as the sum of individually rounded line Importe values (rounded to 6 decimal places).
Instead, it used the aggregated amount directly, which could differ from the SAT-expected value (Σ Round(Importe, 0.000001)).

Additionally, the rounding fallback mechanism attempted models sequentially after PAC rejection (Model1 → Model2 → Model3 → Model4), which was inefficient and could still fail when no local pre-validation was performed to identify a valid rounding model before calling the PAC.

SOLUTION
Changes implemented in EInvoiceMgt.Codeunit.al:

  1. SubTotal recalculation for CFDI40108 compliance
    Added logic in RequestStamp to recalculate SubTotal as
    Σ Round(Importe, 0.000001) for non-retention lines, ensuring consistency with the values written per line in the XML.

  2. Total recalculation for CFDI40119 consistency
    After recalculating SubTotal, Total is recomputed as:
    SubTotal - Descuento + Trasladados - Retenidos.

  3. Rounding model pre-validation (FindValidRoundingModel)
    Performs local validation across available rounding models (0–3) before sending the document to the PAC, selecting a model that does not trigger CFDI40167 validation errors per line.

  4. Unified fallback mechanism (RequestStampWithRoundingFallback)
    Replaces the sequential call pattern with a single procedure that:

  • Identifies a valid rounding model through local validation
  • Sends the document to the PAC
  • Falls back to remaining models only if the PAC rejects the document with a rounding-related error
  1. Local tax validation (ValidateDocumentAmountsLocal)
    Validates that each line's tax Importe equals:
    Round(Base × VAT% / 100, 0.000001)

  2. State reset before stamp requests
    Added Clear(EInvoiceCommunication) to prevent residual state between retries.

TESTS

Added MX AL tests in codeunit 144000 "MX CFDI Unit Test" to validate the fix and prevent regressions:

RequestStampSalesInv11LinesDoesNotRaiseCFDI40108
Validates that CFDI40108 is not raised during RequestStamp execution for a posted sales invoice with 11 lines (Qty = 1, Unit Prices 90–100, VAT 16%).

RequestStampFallbackToAlternativeRoundingModel
Validates that CFDI40167 is not raised during RequestStamp execution in scenarios with quantities that introduce rounding differences.

RequestStampRaisesNoRelationDocsErrorWhenCFDIRelation04
Regression coverage: validates that non-rounding errors (e.g., missing relation documents for CFDI Relation '04') are raised as expected.

RequestStampRetentionLinesExcludedFromSubTotal
Validates that CFDI40108 is not raised during RequestStamp execution when retention lines (Retention Attached to Line No. <> 0) are present.

SubTotalEqualsRoundedSumOfLineImportes
Unit test validating that CreateTempDocument produces a SubTotal equal to
Σ Round(Amount + Line Discount Amount, 0.000001) for all non-retention lines.

  • All tests pass successfully.

Fixes:
AB#638201

Slice 494919: [RegF][Vendor] MX CFDI: Rounding in electronic invoice documents.

@v-mjofre
v-mjofre requested a review from a team July 23, 2026 13:43
@github-actions github-actions Bot added the Integration GitHub request for Integration area label Jul 23, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Jul 23, 2026
'0000C72', StrSubstNo(StampReqMsg, GetDocTypeTextFromDatabaseId(DocumentHeaderRecordRef.Number)), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', MXElectronicInvoicingTok);
CurrencyDecimalPlaces := GetCurrencyDecimalPlaces(TempDocumentHeader."Currency Code");

// Recalculate SubTotal to match Σ(Importe) as written in XML (CFDI40108 compliance)

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\ —\ Agent}$

The new SubTotal-recalculation comment in RequestStamp contains a corrupted/mojibake byte sequence (“╬u”, hex E2 95 AC C3 BA) where a Sigma summation symbol was clearly intended (“Recalculate SubTotal to match Σ(Importe) as written in XML”). This indicates an encoding mishap when the comment was authored (e.g. a copy-paste through a lossy encoding round-trip) and leaves unreadable garbage characters permanently committed to the source file. It is cosmetic but should be fixed before merge since it will render as mangled text in every editor/diff viewer and could trip encoding-sensitive tooling.

Suggested change
// Recalculate SubTotal to match Σ(Importe) as written in XML (CFDI40108 compliance)
// Recalculate SubTotal to match Σ(Importe) as written in XML (CFDI40108 compliance)

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


RoundingModel := NewRoundingModel;
RequestStamp(DocumentHeaderRecordRef, Prepayment, Reverse);
// Pre-validate locally to find the best rounding model only after default model failed

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\ —\ Telemetry}$

The new rounding fallback loop retries stamping after PAC errors CFDI40108/CFDI40110/CFDI40111/CFDI40119/CFDI40167, but it does not emit any telemetry that a retry happened, which rounding model was selected, or that all fallback models were exhausted. Telemetry will only show the generic request/success or final failure events from RequestStamp, so support cannot distinguish a first-pass success from a recovered fallback path or diagnose which PAC rejection triggered the retry. Add a dedicated retry/fallback event around this branch, including the original error code and chosen rounding model, and consider a terminal event when every fallback model fails.

Agent judgement — not directly backed by a BCQuality knowledge article.

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

PostedTransferShipmentCard.Close();
end;

[Test]

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{🟠\ High\ Severity\ —\ Testing}$

The newly added RequestStamp tests wire RequestStampMenuHandler, but that handler hardcodes Choice := 1 and the tests do not enqueue/dequeue the expected menu interaction or prove the UI was consumed exactly once. As written, any single StrMenu that reaches option 1 can keep these tests green, even if the wrong menu is shown or an unexpected UI interaction occurs.

Knowledge:

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

'0000C72', StrSubstNo(StampReqMsg, GetDocTypeTextFromDatabaseId(DocumentHeaderRecordRef.Number)), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', MXElectronicInvoicingTok);
CurrencyDecimalPlaces := GetCurrencyDecimalPlaces(TempDocumentHeader."Currency Code");

// Recalculate SubTotal to match Σ(Importe) as written in XML (CFDI40108 compliance)

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\ —\ Style}$

The comment introducing the new SubTotal-recalculation block contains a garbled/mojibake character sequence ('Σ(Importe)') instead of the intended sigma/sum symbol. The bytes are not valid round-tripped UTF-8 (likely a copy-paste through a mismatched encoding), so the comment renders as corrupted text in editors and diffs. Replace it with plain ASCII wording (e.g. 'sum of Importe') or the correct UTF-8 character to avoid shipping corrupted text in the source file.

Suggested change
// Recalculate SubTotal to match Σ(Importe) as written in XML (CFDI40108 compliance)
// Recalculate SubTotal to match sum of Importe as written in XML (CFDI40108 compliance)

Agent judgement — not directly backed by a BCQuality knowledge article.

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

exit(DMY2Date(DayInt, MonthInt, YearInt));
end;

local procedure AssertDateWithinOneDayTolerance(ExpectedDate: Date; ActualDateText: Text; ErrorMessage: Text)

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}$

AssertDateWithinOneDayTolerance replaces exact-date assertions with a ±1-day tolerance for Comprobante/@Fecha and pago20:Pago/@FechaPago. The accompanying comments state this is needed because 'timezone conversions in the production code use different paths for Fecha vs FechaPago, which can shift dates by ±1 day depending on the server timezone' — i.e. the test is documenting a real inconsistency between two date-formatting code paths in EInvoiceMgt.Codeunit.al rather than fixing it. Because these fields are CFDI-legal invoice dates, a genuine ±1-day divergence between them is a compliance-relevant defect, and loosening the assertion also weakens these tests' ability to catch a regression that shifts either date by exactly one day (the exact class of bug the SCENARIO exists to verify). Consider root-causing and aligning the timezone conversion used for Fecha and FechaPago instead of tolerating the discrepancy in the test, or if the ±1 day skew is expected/unavoidable platform behavior, capture that as a documented, intentional design note rather than a blanket tolerance.

Agent judgement — not directly backed by a BCQuality knowledge article.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Integration GitHub request for Integration area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants