[E-Document Formats] Migrate NAV PRs 247170 and 247176 into BCApps#9426
[E-Document Formats] Migrate NAV PRs 247170 and 247176 into BCApps#9426Groenbech96 wants to merge 7 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot PR ReviewIteration 6 · Outcome: completed Knowledge source: https://github.com/microsoft/BCQuality@186d8a131465475c79244d994acb872cd5c0d4bf Findings by domainFindings split into Knowledge-backed (cite a BCQuality article) and Agent (the agent's own judgement, no matching BCQuality rule).
Totals: 4 knowledge-backed · 1 agent findings. Orchestrator pre-filter (2 file(s) excluded)
Findings produced by the AL review agent v1.7.3. Reply 👎 on any inline comment to flag false positives. |
…e-nav-pr-247170-247176
Resolves Pull Request Build failures AL0185 (missing 'EDocument XML Helper') and AL0297 (Factura-E object id range). [bcapps-fix-loop attempt 1] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Follow-up on the work-item check: I could not infer a valid ADO work item from the mirrored source PRs, so this needs a human-provided work item ID. Once an ID is provided, add AB# (or Fixes AB#) to the PR description and the check should pass. |
- Change codeunit ID 6410 -> 6430 (6410 clashes with another object in the repo) - Fix inverted #pragma warning disable/restore AA0139 in PINT A-NZ handler - Move procedure-local Label vars to codeunit scope in both handlers [bcapps-fix-loop attempt 2] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| @@ -0,0 +1,1044 @@ | |||
| codeunit 148003 "Library - E-Document" | |||
| { | |||
| EventSubscriberInstance = Manual; | |||
There was a problem hiding this comment.
This test helper declares EventSubscriberInstance = Manual, but there is no BindSubscription for this codeunit anywhere in the Factura-E test app, so OnAfterCreateEDocument stays inactive and never enqueues the created E-Document.
Either bind the subscriber for the test scope or make the subscriber static if it is meant to be always on in these tests.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| internal procedure AssertPurchaseDocument(VendorNo: Code[20]; PurchaseHeader: Record "Purchase Header"; Item: Record Item) | ||
| var | ||
| PurchaseLine: Record "Purchase Line"; | ||
| Item1NoTok: Label 'GL00000001', Locked = true; |
There was a problem hiding this comment.
Item1NoTok and Item2NoTok are declared inside the local var block of AssertPurchaseDocument, and the same pattern is introduced in src/Apps/ES/EDocumentFormats/FacturaE/test/src/FacturaEStructValidations.Codeunit.al.
Procedure-local Labels are fragile in the XLIFF/localization pipeline; move these Labels to the codeunit's top-level var block so translation tooling sees stable object-scoped declarations.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| InherentEntitlements = X; | ||
| InherentPermissions = X; | ||
|
|
||
| procedure SetStringValueInField(XMLDocument: XmlDocument; XMLNamespaces: XmlNamespaceManager; Path: Text; MaxLength: Integer; var Field: Variant) |
There was a problem hiding this comment.
EDocument XML Helper is introduced as an Access = Public helper codeunit, but its public procedures (SetStringValueInField, SetDateValueInField, SetNumberValueInField, SetCurrencyValueInField, and GetNodeValue) have no XML documentation comments.
On a public library surface this leaves callers guessing about path expectations, truncation behavior, and return semantics; add meaningful /// <summary>, /// <param>, and /// <returns> docs for each public procedure.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| // ------------------------------------------------------------------------------------------------ | ||
| enumextension 148003 "E-Doc. Format Mock" extends "E-Document Format" | ||
| { | ||
| value(6160; "Mock") |
There was a problem hiding this comment.
This test app depends on "E-Document Format for PINT A-NZ", which adds value 28005 to "E-Document Format" in the same installation graph.
Adding "Mock" at ordinal 6160 inserts a new enum member before that existing value, so persisted rows can resolve to a different member after install or upgrade. Per the referenced guidance, assign a new ordinal greater than every existing value on the enum.
| value(6160; "Mock") | |
| value(148003; "Mock") |
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| @@ -0,0 +1,7 @@ | |||
| enumextension 148001 "E-Doc. Format Mock" extends "E-Document Format" | |||
| { | |||
| value(6160; "Mock") | |||
There was a problem hiding this comment.
This test app depends on "E-Document Format for Factura-E", which adds value 10772 to "E-Document Format" in the same installation graph.
Adding "Mock" at ordinal 6160 inserts a new enum member before that existing value, so persisted rows can resolve to a different member after install or upgrade. Per the referenced guidance, assign a new ordinal greater than every existing value on the enum.
| value(6160; "Mock") | |
| value(148001; "Mock") |
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
- Move XML helper APIs into codeunit 6401 (E-Document PEPPOL Utility) - Switch Factura-E and PINT A-NZ handlers to use PEPPOL utility - Remove temporary EDocument XML Helper codeunit and unnecessary internalsVisibleTo entries - Reassign Factura-E handler from conflicting 10776 to 10766 and update idRanges [bcapps-fix-loop attempt 3] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| begin | ||
| EDocumentPurchaseHeader.InsertForEDocument(EDocument); | ||
|
|
||
| XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), PINTANZXml); |
There was a problem hiding this comment.
XmlDocument.ReadFrom is invoked as a statement, ignoring its Boolean success return.
If the received e-document blob is not well-formed XML, ReadFrom returns false and PINTANZXml stays as the default (empty) document; the handler then continues, calls GetRoot on the empty document, runs the case with no LocalName match, and silently produces an empty E-Document Purchase Header. The error surfaces later as a confusing downstream failure or a blank draft.
Recommendation:
- capture the return value and raise a controlled error labeled with the E-Document number when parsing fails.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
if not XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), PINTANZXml) then
Error(XmlParseErr, EDocument."Entry No");👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| begin | ||
| EDocumentPurchaseHeader.InsertForEDocument(EDocument); | ||
|
|
||
| XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), FacturaEXML); |
There was a problem hiding this comment.
XmlDocument.ReadFrom is invoked as a statement, ignoring its Boolean success return.
Malformed Factura-E XML leaves FacturaEXML as an empty XmlDocument; the handler then calls GetRoot and runs the population routines, silently producing an empty E-Document Purchase Header instead of surfacing a parse failure to the operator.
Recommendation:
- check the return value and raise a controlled error identifying the E-Document when the payload cannot be parsed.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
if not XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), FacturaEXML) then
Error(XmlParseErr, EDocument."Entry No");👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Clear(EDocumentPurchaseLine); | ||
| EDocumentPurchaseLine.Validate("E-Document Entry No.", EDocumentEntryNo); | ||
| EDocumentPurchaseLine."Line No." := EDocumentPurchaseLine.GetNextLineNo(EDocumentEntryNo); | ||
| NewLineXML.ReplaceNodes(LineXMLNode); |
There was a problem hiding this comment.
NewLineXML is declared as a local XmlDocument but never instantiated with XmlDocument.Create() before NewLineXML.ReplaceNodes(LineXMLNode) is called inside the foreach.
Because the variable is reused across iterations without a Clear, the operation either fails or leaks state between lines. Compare the sibling PINT A-NZ handler, which correctly does Clear(NewLineXML); NewLineXML := XmlDocument.Create(); NewLineXML.Add(LineXMLNode.AsXmlElement()); before populating.
Recommendation:
- mirror the PINT A-NZ pattern so every iteration constructs a fresh, single-element document before PopulateFacturaEPurchaseLine walks it.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
foreach LineXMLNode in LineXMLList do begin
Clear(EDocumentPurchaseLine);
EDocumentPurchaseLine.Validate("E-Document Entry No.", EDocumentEntryNo);
EDocumentPurchaseLine."Line No." := EDocumentPurchaseLine.GetNextLineNo(EDocumentEntryNo);
Clear(NewLineXML);
NewLineXML := XmlDocument.Create();
NewLineXML.Add(LineXMLNode.AsXmlElement());
PopulateFacturaEPurchaseLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine);
EDocumentPurchaseLine.Insert(false);
end;👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Address := XMLNode.AsXmlElement().InnerText(); | ||
| end; | ||
|
|
||
| local procedure TryGetUOMCodeFromInternationalCode(ImportedUOMCode: Text) UOMCode: Code[10] |
There was a problem hiding this comment.
TryGetUOMCodeFromInternationalCode is named with the Try prefix and clearly intends best-effort semantics (the caller has no fallback path), but it is not decorated with [TryFunction].
Its body calls Evaluate(Code, ImportedUOMCode, 9) without checking the Boolean return, then indexes Enum::"Factura-E Units of Measure".Names().Get(Code) with the unvalidated integer. A malformed UnitOfMeasure element or an out-of-range value from a legitimate but new Factura-E code will raise an uncaught runtime error and abort the entire import instead of leaving "[BC] Unit of Measure" blank.
Recommendation:
- either mark the procedure [TryFunction] and let callers ignore the failure, or guard Evaluate and range-check Code before Get.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
local procedure TryGetUOMCodeFromInternationalCode(ImportedUOMCode: Text) UOMCode: Code[10]
var
UnitOfMeasure: Record "Unit of Measure";
Names: List of [Text];
Code: Integer;
begin
if ImportedUOMCode = '' then
exit;
if not Evaluate(Code, ImportedUOMCode, 9) then
exit;
Names := Enum::"Factura-E Units of Measure".Names();
if (Code < 1) or (Code > Names.Count()) then
exit;
UnitOfMeasure.SetRange("International Standard Code", Names.Get(Code));
if UnitOfMeasure.FindFirst() then
UOMCode := UnitOfMeasure.Code;
end;👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Resolve compile issues from migrated handlers by using E-Document document type, replacing inaccessible View implementation, and switching to TryGetStringValue-based field extraction. [bcapps-fix-loop attempt 4] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove unused 'Microsoft.eServices.EDocument.Helpers' using directive (AL0792) from both handlers, and add the 'Microsoft.eServices.EDocument.Format' using directive to the FacturaE handler so the 'Factura-E Units of Measure' enum resolves (AL0118). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| exit(false); | ||
| end; | ||
|
|
||
| procedure SetStringValueInField(XMLDocument: XmlDocument; XMLNamespaces: XmlNamespaceManager; Path: Text; MaxLength: Integer; var Field: Variant) |
There was a problem hiding this comment.
This PR adds SetStringValueInField and SetCurrencyValueInField to the shared "E-Document PEPPOL Utility" codeunit (src/Apps/W1/EDocument/App/src/Processing/Import/StructureReceivedEDocument/EDocumentPEPPOLUtility.Codeunit.al), evidently intended for reuse by the new structured-format handlers.
However, neither the new PINT A-NZ handler nor the new Factura-E handler calls them: both instead define their own local SetTextValueFromNode/SetCurrencyFromNode wrappers that re-implement the identical TryGetStringValue + CopyStr (and SetCurrencyIfForeign) logic. No other caller in the repository invokes the new shared procedures either. The result is dead code added to a shared utility plus duplicated logic across two new format handlers in the same PR, increasing maintenance surface for what should be a single reused implementation.
Recommendation:
- have EDocumentPINTANZHandler and EDocumentFacturaEHandler call EDocumentPEPPOLUtility.SetStringValueInField/SetCurrencyValueInField directly and remove the local duplicate wrapper procedures, or drop the new shared procedures if they are not meant to be reused yet.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| foreach LineXMLNode in LineXMLList do begin | ||
| Clear(EDocumentPurchaseLine); | ||
| EDocumentPurchaseLine.Validate("E-Document Entry No.", EDocumentEntryNo); | ||
| EDocumentPurchaseLine."Line No." := EDocumentPurchaseLine.GetNextLineNo(EDocumentEntryNo); |
There was a problem hiding this comment.
Inside InsertFacturaEPurchaseInvoiceLines, each XML line calls EDocumentPurchaseLine.GetNextLineNo(EDocumentEntryNo).
That helper performs a FindLast() on the staging table, so importing N lines adds N extra reads against a growing table. Keep the next line number in a local counter (initialized once and incremented by 10000) instead of re-querying the table for every row.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| LibraryFinChargeMemo: Codeunit "Library - Finance Charge Memo"; | ||
| LibraryInventory: Codeunit "Library - Inventory"; | ||
|
|
||
| procedure SetupStandardVAT() |
There was a problem hiding this comment.
The new Library - E-Document codeunit exposes public helper procedures such as SetupStandardVAT() without any XML documentation.
On a library codeunit, the public procedure surface should carry ///
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Migrate formats to new import processing.
AB#580191