From c741a99de310fc9ede840e192d54d95bfce059dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 30 Jul 2025 15:57:19 +0300 Subject: [PATCH 01/16] Adds Factura-E format support for e-document processing Implements Spanish Factura-E electronic document format handler to enable reading and processing of incoming purchase invoices and credit memos. Provides XML parsing capabilities for Factura-E documents with automatic vendor lookup using VAT registration numbers, service participants, or name/address matching. Includes support for multi-currency transactions, tax calculations, and unit of measure conversions from international standard codes. Enables viewing of imported documents through dedicated purchase document reader interface. --- .../Core/EDocumentFacturaEHandler.Codeunit.al | 268 ++++++++++++++++++ .../Core/FacturaEEDocReadintoDraft.EnumExt.al | 13 + Apps/W1/EDocument/app/app.json | 5 + 3 files changed, 286 insertions(+) create mode 100644 Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al create mode 100644 Apps/ES/EDocumentFormats/FacturaE/app/src/Core/FacturaEEDocReadintoDraft.EnumExt.al diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al new file mode 100644 index 0000000000..f3e60bdd81 --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -0,0 +1,268 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ +namespace Microsoft.eServices.EDocument.Format.FacturaE; + +using Microsoft.eServices.EDocument.Helpers; +using Microsoft.eServices.EDocument; +using Microsoft.eServices.EDocument.Format; +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.eServices.EDocument.Processing.Interfaces; +using Microsoft.eServices.EDocument.Service.Participant; +using Microsoft.Purchases.Vendor; +using Microsoft.Foundation.UOM; +using System.Utilities; +using System.IO; + +codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + var + EDocumentImportHelper: Codeunit "E-Document Import Helper"; + + procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + DocStream: InStream; + FacturaEXML: XmlDocument; + XmlNamespaces: XmlNamespaceManager; + XmlElement: XmlElement; + FacturaENamespaceLbl: Label 'http://www.facturae.gob.es/formato/Versiones/Facturaev3_2_2.xml'; + DigitalSignatureNamespaceLbl: Label 'http://www.w3.org/2000/09/xmldsig#'; + ETSINamespaceLbl: Label 'http://uri.etsi.org/01903/v1.2.2#'; + XMLNode: XmlNode; + begin + EDocumentPurchaseHeader.InsertForEDocument(EDocument); + + TempBlob.CreateInStream(DocStream, TextEncoding::UTF8); + XmlDocument.ReadFrom(DocStream, FacturaEXML); + XmlNamespaces.AddNamespace('namespace', FacturaENamespaceLbl); + XmlNamespaces.AddNamespace('ds', DigitalSignatureNamespaceLbl); + XmlNamespaces.AddNamespace('etsi', ETSINamespaceLbl); + + FacturaEXML.GetRoot(XmlElement); + EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Invoice"; + if FacturaEXML.SelectSingleNode('/namespace:Facturae/Invoices/Invoice/InvoiceHeader/Corrective', XmlNamespaces, XMLNode) then + EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Credit Memo"; + + PopulateFacturaEPurchaseInvoiceHeader(FacturaEXML, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + InsertFacturaEPurchaseInvoiceLines(FacturaEXML, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No."); + EDocumentPurchaseHeader.Modify(); + EDocument.Direction := EDocument.Direction::Incoming; + exit(Enum::"E-Doc. Process Draft"::"Purchase Document"); + end; + + procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") + var + EDocPurchaseHeader: Record "E-Document Purchase Header"; + EDocPurchaseLine: Record "E-Document Purchase Line"; + EDocReadablePurchaseDoc: Page "E-Doc. Readable Purchase Doc."; + begin + EDocPurchaseHeader.GetFromEDocument(EDocument); + EDocPurchaseLine.SetRange("E-Document Entry No.", EDocPurchaseHeader."E-Document Entry No."); + EDocReadablePurchaseDoc.SetBuffer(EDocPurchaseHeader, EDocPurchaseLine); + EDocReadablePurchaseDoc.Run(); + end; + +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + local procedure PopulateFacturaEPurchaseInvoiceHeader(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + VendorNo: Code[20]; + begin + EDocumentXMLHelper.SetStringValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceHeader/InvoiceNumber', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); + EDocumentXMLHelper.SetDateValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceIssueData/IssueDate', EDocumentPurchaseHeader."Document Date"); + EDocumentXMLHelper.SetCurrencyValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceIssueData/InvoiceCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); + VendorNo := ParseSellerParty(FacturaEXML, XmlNamespaces, EDocument, EDocumentPurchaseHeader); + ParseBuyerParty(FacturaEXML, XmlNamespaces, EDocumentPurchaseHeader); + EDocumentXMLHelper.SetNumberValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceTotals/TotalGrossAmountBeforeTaxes', EDocumentPurchaseHeader."Sub Total"); + EDocumentXMLHelper.SetNumberValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceTotals/TotalTaxOutputs', EDocumentPurchaseHeader."Total VAT"); + EDocumentXMLHelper.SetNumberValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceTotals/InvoiceTotal', EDocumentPurchaseHeader.Total); + EDocumentXMLHelper.SetNumberValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceTotals/TotalOutstandingAmount', EDocumentPurchaseHeader."Amount Due"); + EDocumentXMLHelper.SetDateValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceHeader/InvoiceDocumentReference/ReferencedDocumentDate', EDocumentPurchaseHeader."Due Date"); + EDocumentXMLHelper.SetStringValueInField(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Invoices/Invoice/InvoiceHeader/Corrective/InvoiceNumber', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); + + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + end; +#pragma warning restore AA0139 + + local procedure InsertFacturaEPurchaseInvoiceLines(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; EDocumentEntryNo: Integer) + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + NewLineXML: XmlDocument; + LineXMLList: XmlNodeList; + LineXMLNode: XmlNode; + InvoiceLinePathLbl: Label '/namespace:Facturae/Invoices/Invoice/Items/InvoiceLine'; + begin + if not FacturaEXML.SelectNodes(InvoiceLinePathLbl, XmlNamespaces, LineXMLList) then + exit; + + foreach LineXMLNode in LineXMLList do begin + Clear(EDocumentPurchaseLine); + EDocumentPurchaseLine.Validate("E-Document Entry No.", EDocumentEntryNo); + EDocumentPurchaseLine."Line No." := EDocumentPurchaseLine.GetNextLineNo(EDocumentEntryNo); + NewLineXML.ReplaceNodes(LineXMLNode); + PopulateFacturaEPurchaseLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine); + EDocumentPurchaseLine.Insert(); + end; + end; + +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + local procedure PopulateFacturaEPurchaseLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + UOMCode: Text; + XMLNode: XmlNode; + begin + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'InvoiceLine/ArticleCode', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'InvoiceLine/ItemDescription', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'InvoiceLine/Quantity', EDocumentPurchaseLine.Quantity); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'InvoiceLine/UnitPriceWithoutTax', EDocumentPurchaseLine."Unit Price"); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'InvoiceLine/TotalCost', EDocumentPurchaseLine."Sub Total"); + EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'InvoiceLine/TotalCost/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); + + // Handle Unit of Measure like in legacy system + if LineXML.SelectSingleNode('InvoiceLine/UnitOfMeasure', XmlNamespaces, XMLNode) then begin + UOMCode := XMLNode.AsXmlElement().InnerText(); + EDocumentPurchaseLine."Unit of Measure" := CopyStr(UOMCode, 1, MaxStrLen(EDocumentPurchaseLine."Unit of Measure")); + EDocumentPurchaseLine."[BC] Unit of Measure" := TryGetUOMCodeFromInternationalCode(UOMCode); + end; + + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'InvoiceLine/TaxesOutputs/Tax/TaxRate', EDocumentPurchaseLine."VAT Rate"); + end; +#pragma warning restore AA0139 + + local procedure ParseSellerParty(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocument: Record "E-Document"; var EDocumentPurchaseHeader: Record "E-Document Purchase Header") VendorNo: Code[20] + var + Vendor: Record Vendor; + ServiceParticipant: Record "Service Participant"; + EDocumentService: Record "E-Document Service"; + EDocumentHelper: Codeunit "E-Document Helper"; + VendorName, VendorAddress : Text; + VATRegistrationNo: Text[20]; + VendorId: Text; + XMLNode: XmlNode; + begin + // Extract VAT registration number + if FacturaEXML.SelectSingleNode('/namespace:Facturae/Parties/SellerParty/TaxIdentification/TaxIdentificationNumber', XmlNamespaces, XMLNode) then + VATRegistrationNo := CopyStr(XMLNode.AsXmlElement().InnerText(), 1, MaxStrLen(VATRegistrationNo)); + + // Try to find vendor by VAT registration number first + VendorNo := EDocumentImportHelper.FindVendor('', '', VATRegistrationNo); + + // If vendor not found, try to find by Service Participant + if VendorNo = '' then begin + if FacturaEXML.SelectSingleNode('/namespace:Facturae/Parties/SellerParty/PartyIdentification', XmlNamespaces, XMLNode) then + VendorId := XMLNode.AsXmlElement().InnerText(); + + if VendorId <> '' then begin + EDocumentHelper.GetEdocumentService(EDocument, EDocumentService); + ServiceParticipant.SetRange("Participant Type", ServiceParticipant."Participant Type"::Vendor); + ServiceParticipant.SetRange("Participant Identifier", VendorId); + ServiceParticipant.SetRange(Service, EDocumentService.Code); + if not ServiceParticipant.FindFirst() then begin + ServiceParticipant.SetRange(Service); + if ServiceParticipant.FindFirst() then; + end; + end; + + VendorNo := ServiceParticipant.Participant; + end; + + // If vendor still not found, try to find by name and address + if VendorNo = '' then begin + VendorName := GetNameDependingOnType(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Parties/SellerParty/'); + VendorAddress := GetAddressDependingOnType(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Parties/SellerParty/'); + VendorNo := EDocumentImportHelper.FindVendorByNameAndAddress(VendorName, VendorAddress); + EDocumentPurchaseHeader."Vendor Company Name" := CopyStr(VendorName, 1, MaxStrLen(EDocumentPurchaseHeader."Vendor Company Name")); + end; + + // Set vendor information in E-Document Purchase Header + Vendor := EDocumentImportHelper.GetVendor(EDocument, VendorNo); + if Vendor."No." <> '' then begin + EDocumentPurchaseHeader."Vendor Company Name" := Vendor.Name; + EDocumentPurchaseHeader."Vendor VAT Id" := VATRegistrationNo; + EDocumentPurchaseHeader."Vendor Address" := CopyStr(GetAddressDependingOnType(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Parties/SellerParty/'), 1, MaxStrLen(EDocumentPurchaseHeader."Vendor Address")); + end else begin + // Set extracted information even if vendor not found + VendorName := GetNameDependingOnType(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Parties/SellerParty/'); + if VendorName <> '' then + EDocumentPurchaseHeader."Vendor Company Name" := CopyStr(VendorName, 1, MaxStrLen(EDocumentPurchaseHeader."Vendor Company Name")); + EDocumentPurchaseHeader."Vendor VAT Id" := VATRegistrationNo; + EDocumentPurchaseHeader."Vendor Address" := CopyStr(GetAddressDependingOnType(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Parties/SellerParty/'), 1, MaxStrLen(EDocumentPurchaseHeader."Vendor Address")); + end; + end; + + local procedure ParseBuyerParty(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + BuyerName, BuyerAddress : Text; + XMLNode: XmlNode; + begin + BuyerName := GetNameDependingOnType(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Parties/BuyerParty/'); + BuyerAddress := GetAddressDependingOnType(FacturaEXML, XmlNamespaces, '/namespace:Facturae/Parties/BuyerParty/'); + + EDocumentPurchaseHeader."Customer Company Name" := CopyStr(BuyerName, 1, MaxStrLen(EDocumentPurchaseHeader."Customer Company Name")); + EDocumentPurchaseHeader."Customer Address" := CopyStr(BuyerAddress, 1, MaxStrLen(EDocumentPurchaseHeader."Customer Address")); + + if FacturaEXML.SelectSingleNode('/namespace:Facturae/Parties/BuyerParty/TaxIdentification/TaxIdentificationNumber', XmlNamespaces, XMLNode) then + EDocumentPurchaseHeader."Customer VAT Id" := CopyStr(XMLNode.AsXmlElement().InnerText(), 1, MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id")); + end; + + local procedure GetNameDependingOnType(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; PathPrefix: Text) Name: Text + var + XMLNode: XmlNode; + PersonTypeCode: Text; + IndividualTok: Label 'F', Locked = true; + begin + if FacturaEXML.SelectSingleNode(PathPrefix + 'TaxIdentification/PersonTypeCode', XmlNamespaces, XMLNode) then + PersonTypeCode := XMLNode.AsXmlElement().InnerText(); + + if PersonTypeCode = IndividualTok then begin + if FacturaEXML.SelectSingleNode(PathPrefix + 'LegalEntity/Name', XmlNamespaces, XMLNode) then + Name := XMLNode.AsXmlElement().InnerText(); + if FacturaEXML.SelectSingleNode(PathPrefix + 'LegalEntity/FirstSurname', XmlNamespaces, XMLNode) then + Name += ' ' + XMLNode.AsXmlElement().InnerText(); + if FacturaEXML.SelectSingleNode(PathPrefix + 'LegalEntity/SecondSurname', XmlNamespaces, XMLNode) then + Name += ' ' + XMLNode.AsXmlElement().InnerText(); + end else + if FacturaEXML.SelectSingleNode(PathPrefix + 'LegalEntity/CorporateName', XmlNamespaces, XMLNode) then + Name := XMLNode.AsXmlElement().InnerText(); + end; + + local procedure GetAddressDependingOnType(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; PathPrefix: Text) Address: Text + var + XMLNode: XmlNode; + ResidenceTypeCode: Text; + ResidenceTok: Label 'R', Locked = true; + begin + if FacturaEXML.SelectSingleNode(PathPrefix + 'TaxIdentification/ResidenceTypeCode', XmlNamespaces, XMLNode) then + ResidenceTypeCode := XMLNode.AsXmlElement().InnerText(); + + if ResidenceTypeCode = ResidenceTok then begin + if FacturaEXML.SelectSingleNode(PathPrefix + 'LegalEntity/AddressInSpain/Address', XmlNamespaces, XMLNode) then + Address := XMLNode.AsXmlElement().InnerText(); + end else + if FacturaEXML.SelectSingleNode(PathPrefix + 'LegalEntity/OverseasAddress/Address', XmlNamespaces, XMLNode) then + Address := XMLNode.AsXmlElement().InnerText(); + end; + + local procedure TryGetUOMCodeFromInternationalCode(TextValue: Text): Code[10] + var + UnitOfMeasure: Record "Unit of Measure"; + Code: Integer; + begin + if TextValue = '' then + exit(''); + + Evaluate(Code, TextValue, 9); + UnitOfMeasure.SetRange("International Standard Code", Enum::"Factura-E Units of Measure".Names().Get(Code)); + if UnitOfMeasure.FindFirst() then + exit(UnitOfMeasure.Code); + end; +} diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/FacturaEEDocReadintoDraft.EnumExt.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/FacturaEEDocReadintoDraft.EnumExt.al new file mode 100644 index 0000000000..0e5859cff6 --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/FacturaEEDocReadintoDraft.EnumExt.al @@ -0,0 +1,13 @@ +namespace Microsoft.eServices.EDocument.Format.FacturaE; + +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Interfaces; + +enumextension 10773 "Factura-E EDoc Read into Draft" extends "E-Doc. Read into Draft" +{ + value(10773; "Factura-E") + { + Caption = 'Factura-E'; + Implementation = IStructuredFormatReader = "E-Document Factura-E Handler"; + } +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/app.json b/Apps/W1/EDocument/app/app.json index 4342742da3..88e2150d21 100644 --- a/Apps/W1/EDocument/app/app.json +++ b/Apps/W1/EDocument/app/app.json @@ -47,6 +47,11 @@ "id": "9767f7d7-43ce-4416-a2b7-93e3f2d57eeb", "name": "ADI Json Content", "publisher": "Microsoft" + }, + { + "id": "8238f99b-cbe5-4b9c-b247-7771145b7470", + "name": "E-Document Format for Factura-E", + "publisher": "Microsoft" } ], "screenshots": [], From 39ac06a8f96a4b68c7478c83f401d8a2bde980dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 30 Jul 2025 16:06:02 +0300 Subject: [PATCH 02/16] Fix E-Document Purchase Header modification to prevent unintended changes --- .../FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index f3e60bdd81..5614e71e94 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -52,7 +52,7 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader PopulateFacturaEPurchaseInvoiceHeader(FacturaEXML, XmlNamespaces, EDocumentPurchaseHeader, EDocument); InsertFacturaEPurchaseInvoiceLines(FacturaEXML, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No."); - EDocumentPurchaseHeader.Modify(); + EDocumentPurchaseHeader.Modify(false); EDocument.Direction := EDocument.Direction::Incoming; exit(Enum::"E-Doc. Process Draft"::"Purchase Document"); end; From 2abb83813df6ff7cd42e9deb85d23bf42345c77b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 30 Jul 2025 17:47:51 +0300 Subject: [PATCH 03/16] Improves Factura-E handler with documentation and code optimization Adds comprehensive XML documentation comments to all public and internal procedures to improve code maintainability and developer experience. Optimizes XML stream handling by eliminating intermediate variable and directly using CreateInStream method call. Enhances unit of measure conversion logic with better variable naming and clearer control flow structure. Updates database insert operation to use non-validated insert for better performance during line processing. --- .../Core/EDocumentFacturaEHandler.Codeunit.al | 88 ++++++++++++++++--- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index 5614e71e94..67e13818b5 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -25,10 +25,15 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader var EDocumentImportHelper: Codeunit "E-Document Import Helper"; - procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" + /// + /// Reads a Factura-E XML document and converts it into a draft purchase document. + /// + /// The E-Document record containing the document information. + /// The temporary blob containing the XML content to be processed. + /// Returns the type of draft document that was created (Purchase Document). + internal procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - DocStream: InStream; FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; XmlElement: XmlElement; @@ -39,8 +44,7 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader begin EDocumentPurchaseHeader.InsertForEDocument(EDocument); - TempBlob.CreateInStream(DocStream, TextEncoding::UTF8); - XmlDocument.ReadFrom(DocStream, FacturaEXML); + XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), FacturaEXML); XmlNamespaces.AddNamespace('namespace', FacturaENamespaceLbl); XmlNamespaces.AddNamespace('ds', DigitalSignatureNamespaceLbl); XmlNamespaces.AddNamespace('etsi', ETSINamespaceLbl); @@ -57,7 +61,12 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader exit(Enum::"E-Doc. Process Draft"::"Purchase Document"); end; - procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") + /// + /// Displays a readable view of the E-Document by opening the purchase document page. + /// + /// The E-Document record to be displayed. + /// The temporary blob containing the document content (not used in this implementation). + internal procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") var EDocPurchaseHeader: Record "E-Document Purchase Header"; EDocPurchaseLine: Record "E-Document Purchase Line"; @@ -70,6 +79,13 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + /// + /// Populates the purchase invoice header with data extracted from the Factura-E XML document. + /// + /// The Factura-E XML document containing the invoice data. + /// The XML namespace manager for handling XML namespaces. + /// The E-Document Purchase Header record to be populated. + /// The E-Document record containing document information. local procedure PopulateFacturaEPurchaseInvoiceHeader(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") var EDocumentXMLHelper: Codeunit "EDocument XML Helper"; @@ -92,6 +108,12 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; #pragma warning restore AA0139 + /// + /// Inserts purchase invoice lines extracted from the Factura-E XML document. + /// + /// The Factura-E XML document containing the invoice line data. + /// The XML namespace manager for handling XML namespaces. + /// The E-Document entry number to associate with the lines. local procedure InsertFacturaEPurchaseInvoiceLines(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; EDocumentEntryNo: Integer) var EDocumentPurchaseLine: Record "E-Document Purchase Line"; @@ -109,11 +131,17 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader EDocumentPurchaseLine."Line No." := EDocumentPurchaseLine.GetNextLineNo(EDocumentEntryNo); NewLineXML.ReplaceNodes(LineXMLNode); PopulateFacturaEPurchaseLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine); - EDocumentPurchaseLine.Insert(); + EDocumentPurchaseLine.Insert(false); end; end; #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + /// + /// Populates a single purchase line with data extracted from the Factura-E XML line. + /// + /// The XML document containing a single invoice line. + /// The XML namespace manager for handling XML namespaces. + /// The E-Document Purchase Line record to be populated. local procedure PopulateFacturaEPurchaseLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line") var EDocumentXMLHelper: Codeunit "EDocument XML Helper"; @@ -138,6 +166,14 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; #pragma warning restore AA0139 + /// + /// Parses the seller party information from the Factura-E XML and attempts to find the corresponding vendor. + /// + /// The Factura-E XML document containing the seller party data. + /// The XML namespace manager for handling XML namespaces. + /// The E-Document record containing document information. + /// The E-Document Purchase Header record to be updated with vendor information. + /// Returns the vendor number if found, otherwise returns empty string. local procedure ParseSellerParty(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocument: Record "E-Document"; var EDocumentPurchaseHeader: Record "E-Document Purchase Header") VendorNo: Code[20] var Vendor: Record Vendor; @@ -199,6 +235,12 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; end; + /// + /// Parses the buyer party information from the Factura-E XML and updates the purchase header with customer details. + /// + /// The Factura-E XML document containing the buyer party data. + /// The XML namespace manager for handling XML namespaces. + /// The E-Document Purchase Header record to be updated with customer information. local procedure ParseBuyerParty(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header") var BuyerName, BuyerAddress : Text; @@ -214,6 +256,13 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader EDocumentPurchaseHeader."Customer VAT Id" := CopyStr(XMLNode.AsXmlElement().InnerText(), 1, MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id")); end; + /// + /// Extracts the name from a party (seller or buyer) depending on the person type (individual or corporate). + /// + /// The Factura-E XML document containing the party data. + /// The XML namespace manager for handling XML namespaces. + /// The XML path prefix to the party node (e.g., '/namespace:Facturae/Parties/SellerParty/'). + /// Returns the formatted name based on the person type. local procedure GetNameDependingOnType(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; PathPrefix: Text) Name: Text var XMLNode: XmlNode; @@ -235,6 +284,13 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader Name := XMLNode.AsXmlElement().InnerText(); end; + /// + /// Extracts the address from a party (seller or buyer) depending on the residence type (Spain or overseas). + /// + /// The Factura-E XML document containing the party data. + /// The XML namespace manager for handling XML namespaces. + /// The XML path prefix to the party node (e.g., '/namespace:Facturae/Parties/SellerParty/'). + /// Returns the address based on the residence type. local procedure GetAddressDependingOnType(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; PathPrefix: Text) Address: Text var XMLNode: XmlNode; @@ -252,17 +308,21 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader Address := XMLNode.AsXmlElement().InnerText(); end; - local procedure TryGetUOMCodeFromInternationalCode(TextValue: Text): Code[10] + /// + /// Attempts to convert an international unit of measure code to the corresponding Business Central unit of measure code. + /// + /// The international unit of measure code as text. + /// Returns the Business Central unit of measure code if found, otherwise returns empty string. + local procedure TryGetUOMCodeFromInternationalCode(ImportedUOMCode: Text) UOMCode: Code[10] var UnitOfMeasure: Record "Unit of Measure"; Code: Integer; begin - if TextValue = '' then - exit(''); - - Evaluate(Code, TextValue, 9); - UnitOfMeasure.SetRange("International Standard Code", Enum::"Factura-E Units of Measure".Names().Get(Code)); - if UnitOfMeasure.FindFirst() then - exit(UnitOfMeasure.Code); + if ImportedUOMCode <> '' then begin + Evaluate(Code, ImportedUOMCode, 9); + UnitOfMeasure.SetRange("International Standard Code", Enum::"Factura-E Units of Measure".Names().Get(Code)); + if UnitOfMeasure.FindFirst() then + UOMCode := UnitOfMeasure.Code; + end; end; } From e02cb79ef16d656706118dd0fb3f4463aecb32b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Thu, 24 Jul 2025 16:56:07 +0300 Subject: [PATCH 04/16] Add PINT A-NZ E-Document Read Draft Handler and Enum Extension --- .../Core/PINTANZEDocReadintoDraft.EnumExt.al | 13 + .../Core/PINTANZReadDraftHandler.Codeunit.al | 279 ++++++++++++++++++ Apps/W1/EDocument/app/app.json | 5 + 3 files changed, 297 insertions(+) create mode 100644 Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al create mode 100644 Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al diff --git a/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al b/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al new file mode 100644 index 0000000000..f6d2ed8165 --- /dev/null +++ b/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al @@ -0,0 +1,13 @@ +namespace Microsoft.eServices.EDocument.Format; + +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Interfaces; + +enumextension 28006 "PINT A-NZ EDoc Read into Draft" extends "E-Doc. Read into Draft" +{ + value(28005; "PINT A-NZ") + { + Caption = 'PINT A-NZ'; + Implementation = IStructuredFormatReader = "PINT A-NZ Read Draft Handler"; + } +} \ No newline at end of file diff --git a/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al b/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al new file mode 100644 index 0000000000..643e538aee --- /dev/null +++ b/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al @@ -0,0 +1,279 @@ +namespace Microsoft.eServices.EDocument.Format; + +using Microsoft.eServices.EDocument.Processing.Interfaces; +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.eServices.EDocument.Service.Participant; +using Microsoft.eServices.EDocument.Helpers; +using Microsoft.Purchases.Vendor; +using Microsoft.Finance.GeneralLedger.Setup; +using System.Utilities; +using Microsoft.eServices.EDocument; + +codeunit 28008 "PINT A-NZ Read Draft Handler" implements IStructuredFormatReader +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + var + EDocumentImportHelper: Codeunit "E-Document Import Helper"; + + procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + DocStream: InStream; + PINTANZXml: XmlDocument; + XmlNamespaces: XmlNamespaceManager; + XmlElement: XmlElement; + CommonAggregateComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'; + CommonBasicComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'; + DefaultInvoiceLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'; + DefaultCreditNoteLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'; + begin + EDocumentPurchaseHeader.InsertForEDocument(EDocument); + + TempBlob.CreateInStream(DocStream, TextEncoding::UTF8); + XmlDocument.ReadFrom(DocStream, PINTANZXml); + XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsLbl); + XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsLbl); + XmlNamespaces.AddNamespace('inv', DefaultInvoiceLbl); + XmlNamespaces.AddNamespace('cn', DefaultCreditNoteLbl); + + PINTANZXml.GetRoot(XmlElement); + case UpperCase(XmlElement.LocalName()) of + 'INVOICE': + PopulateEDocumentForInvoice(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + 'CREDITNOTE': + PopulateEDocumentForCreditNote(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + end; + + EDocumentPurchaseHeader.Modify(); + EDocument.Direction := EDocument.Direction::Incoming; + exit(Enum::"E-Doc. Process Draft"::"Purchase Document"); + end; + + procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") + var + EDocPurchaseHeader: Record "E-Document Purchase Header"; + EDocPurchaseLine: Record "E-Document Purchase Line"; + EDocReadablePurchaseDoc: Page "E-Doc. Readable Purchase Doc."; + begin + EDocPurchaseHeader.GetFromEDocument(EDocument); + EDocPurchaseLine.SetRange("E-Document Entry No.", EDocPurchaseHeader."E-Document Entry No."); + EDocReadablePurchaseDoc.SetBuffer(EDocPurchaseHeader, EDocPurchaseLine); + EDocReadablePurchaseDoc.Run(); + end; + + #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + local procedure PopulateEDocumentForInvoice(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + VendorNo: Code[20]; + begin + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); + EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); + EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:DueDate', EDocumentPurchaseHeader."Due Date"); + EDocumentXMLHelper.SetCurrencyValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); + EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); + EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader.Total); + EDocumentPurchaseHeader."Amount Due" := EDocumentPurchaseHeader.Total; + VendorNo := ParseAccountingSupplierPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'inv:Invoice'); + ParseAccountingCustomerPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, 'inv:Invoice'); + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + + InsertPINTANZPurchaseInvoiceLines(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No."); + end; + + local procedure PopulateEDocumentForCreditNote(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + VendorNo: Code[20]; + begin + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); + EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); + EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:PaymentMeans/cbc:PaymentDueDate', EDocumentPurchaseHeader."Due Date"); + EDocumentXMLHelper.SetCurrencyValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); + EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); + EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader.Total); + EDocumentPurchaseHeader."Amount Due" := EDocumentPurchaseHeader.Total; + VendorNo := ParseAccountingSupplierPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'cn:CreditNote'); + ParseAccountingCustomerPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, 'cn:CreditNote'); + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + InsertPINTANZPurchaseInvoiceLines(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No."); + end; + #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + local procedure ParseAccountingSupplierPartyForPurchaseHeader(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document"; DocumentType: Text) VendorNo: Code[20] + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + VendorName, VendorAddress, VendorParticipantId : Text; + VATRegistrationNo: Text[20]; + GLN: Code[13]; + ABN: Code[11]; + XMLNode: XmlNode; + ABNSchemeIdTok: Label '0151', Locked = true; + GLNSchemeIdTok: Label '0088', Locked = true; + BasePathTxt: Text; + begin + BasePathTxt := '/' + DocumentType + '/cac:AccountingSupplierParty/cac:Party'; + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Company Name"), EDocumentPurchaseHeader."Vendor Company Name"); + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Vendor Address"), EDocumentPurchaseHeader."Vendor Address"); + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Vendor VAT Id"), EDocumentPurchaseHeader."Vendor VAT Id"); + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:Contact/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Contact Name"), EDocumentPurchaseHeader."Vendor Contact Name"); + if PINTANZXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin + if XMLNode.AsXmlAttribute().Value() = ABNSchemeIdTok then + ABN := CopyStr(GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'), 1, MaxStrLen(ABN)); + if XMLNode.AsXmlAttribute().Value() = GLNSchemeIdTok then begin + GLN := CopyStr(GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'), 1, MaxStrLen(GLN)); + EDocumentPurchaseHeader."Vendor GLN" := GLN; + end; + + VendorParticipantId := XMLNode.AsXmlAttribute().Value() + ':' + GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); + end; + VATRegistrationNo := EDocumentPurchaseHeader."Vendor VAT Id"; + VendorName := EDocumentPurchaseHeader."Vendor Company Name"; + VendorAddress := EDocumentPurchaseHeader."Vendor Address"; + if not FindVendorByABN(VendorNo, ABN) then + if not FindVendorByVATRegNoOrGLN(VendorNo, VATRegistrationNo, GLN) then + if not FindVendorByParticipantId(VendorNo, EDocument, VendorParticipantId) then + VendorNo := EDocumentImportHelper.FindVendorByNameAndAddress(VendorName, VendorAddress); + end; + + local procedure ParseAccountingCustomerPartyForPurchaseHeader(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; DocumentType: Text) + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + ReceivingId: Text[250]; + SchemaId, CompanyIdentifierValue : Text; + BasePathTxt: Text; + XMLNode: XmlNode; + begin + BasePathTxt := '/' + DocumentType + '/cac:AccountingCustomerParty/cac:Party'; + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Customer Company Name"), EDocumentPurchaseHeader."Customer Company Name"); + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Customer Address"), EDocumentPurchaseHeader."Customer Address"); + EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id"), EDocumentPurchaseHeader."Customer VAT Id"); + if PINTANZXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin + SchemaId := XMLNode.AsXmlAttribute().Value(); + CompanyIdentifierValue := GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); + if SchemaId = '0088' then + EDocumentPurchaseHeader."Customer GLN" := CopyStr(CompanyIdentifierValue, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN")); + ReceivingId := CopyStr(SchemaId, 1, (MaxStrLen(EDocumentPurchaseHeader."Customer Company Id") - 1)) + ':'; + ReceivingId += CopyStr(CompanyIdentifierValue, 1, MaxStrLen(EDocumentPurchaseHeader."Customer Company Id") - StrLen(ReceivingId)); + EDocumentPurchaseHeader."Customer Company Id" := ReceivingId; + end; + if (EDocumentPurchaseHeader."Customer GLN" = '') and PINTANZXml.SelectSingleNode(BasePathTxt + '/cac:PartyIdentification/cbc:ID/@schemeID', XmlNamespaces, XMLNode) then begin + SchemaId := XMLNode.AsXmlAttribute().Value(); + CompanyIdentifierValue := GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyIdentification/cbc:ID'); + if SchemaId = '0088' then + EDocumentPurchaseHeader."Customer GLN" := CopyStr(CompanyIdentifierValue, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN")); + end; + end; + + local procedure InsertPINTANZPurchaseInvoiceLines(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; EDocumentEntryNo: Integer) + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + NewLineXML: XmlDocument; + LineXMLList: XmlNodeList; + LineXMLNode: XmlNode; + i: Integer; + InvoiceLinePathLbl: Label '/inv:Invoice/cac:InvoiceLine'; + CreditNoteLinePathLbl: Label '/cn:CreditNote/cac:CreditNoteLine'; + begin + if not PINTANZXml.SelectNodes(InvoiceLinePathLbl, XmlNamespaces, LineXMLList) then + if not PINTANZXml.SelectNodes(CreditNoteLinePathLbl, XmlNamespaces, LineXMLList) then + exit; + + for i := 1 to LineXMLList.Count do begin + LineXMLList.Get(i, LineXMLNode); + Clear(EDocumentPurchaseLine); + EDocumentPurchaseLine.Init(); + EDocumentPurchaseLine."E-Document Entry No." := EDocumentEntryNo; + EDocumentPurchaseLine."Line No." := i * 10000; + Clear(NewLineXML); + NewLineXML := XmlDocument.Create(); + NewLineXML.Add(LineXMLNode.AsXmlElement()); + PopulatePINTANZPurchaseLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine); + EDocumentPurchaseLine.Insert(); + end; + end; + + local procedure PopulatePINTANZPurchaseLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + begin + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cac:Item/cac:SellersItemIdentification/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); + if EDocumentPurchaseLine."Product Code" = '' then + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cac:Item/cac:SellersItemIdentification/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cac:Item/cbc:Name', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); + if EDocumentPurchaseLine.Description = '' then + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cac:Item/cbc:Name', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:InvoicedQuantity', EDocumentPurchaseLine.Quantity); + if EDocumentPurchaseLine.Quantity = 0 then + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:CreditedQuantity', EDocumentPurchaseLine.Quantity); + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:InvoicedQuantity/@unitCode', MaxStrLen(EDocumentPurchaseLine."Unit of Measure"), EDocumentPurchaseLine."Unit of Measure"); + if EDocumentPurchaseLine."Unit of Measure" = '' then + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:CreditedQuantity/@unitCode', MaxStrLen(EDocumentPurchaseLine."Unit of Measure"), EDocumentPurchaseLine."Unit of Measure"); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cac:Price/cbc:PriceAmount', EDocumentPurchaseLine."Unit Price"); + if EDocumentPurchaseLine."Unit Price" = 0 then + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cac:Price/cbc:PriceAmount', EDocumentPurchaseLine."Unit Price"); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:LineExtensionAmount', EDocumentPurchaseLine."Sub Total"); + if EDocumentPurchaseLine."Sub Total" = 0 then + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:LineExtensionAmount', EDocumentPurchaseLine."Sub Total"); + EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:LineExtensionAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); + if EDocumentPurchaseLine."Currency Code" = '' then + EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:LineExtensionAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); + end; + + local procedure GetNodeValue(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; XPath: Text): Text + var + XMLNode: XmlNode; + begin + if PINTANZXml.SelectSingleNode(XPath, XmlNamespaces, XMLNode) then + exit(XMLNode.AsXmlElement().InnerText()); + exit(''); + end; + + local procedure FindVendorByABN(var VendorNo: Code[20]; InputABN: Code[11]): Boolean + var + Vendor: Record Vendor; + begin + if InputABN = '' then + exit(false); + + Vendor.SetRange(ABN, InputABN); + if Vendor.FindFirst() then + VendorNo := Vendor."No."; + exit(VendorNo <> ''); + end; + + local procedure FindVendorByVATRegNoOrGLN(var VendorNo: Code[20]; VATRegistrationNo: Text[20]; InputGLN: Code[13]): Boolean + begin + VendorNo := EDocumentImportHelper.FindVendor('', InputGLN, VATRegistrationNo); + exit(VendorNo <> ''); + end; + + local procedure FindVendorByParticipantId(var VendorNo: Code[20]; EDocument: Record "E-Document"; VendorParticipantId: Text): Boolean + var + EDocumentService: Record "E-Document Service"; + ServiceParticipant: Record "Service Participant"; + EDocumentHelper: Codeunit "E-Document Helper"; + begin + if VendorParticipantId = '' then + exit(false); + + EDocumentHelper.GetEdocumentService(EDocument, EDocumentService); + ServiceParticipant.SetRange("Participant Type", ServiceParticipant."Participant Type"::Vendor); + ServiceParticipant.SetRange("Participant Identifier", VendorParticipantId); + ServiceParticipant.SetRange(Service, EDocumentService.Code); + if not ServiceParticipant.FindFirst() then begin + ServiceParticipant.SetRange(Service); + if ServiceParticipant.FindFirst() then; + end; + + VendorNo := ServiceParticipant.Participant; + exit(VendorNo <> ''); + end; +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/app.json b/Apps/W1/EDocument/app/app.json index 88e2150d21..fd7f03a417 100644 --- a/Apps/W1/EDocument/app/app.json +++ b/Apps/W1/EDocument/app/app.json @@ -52,6 +52,11 @@ "id": "8238f99b-cbe5-4b9c-b247-7771145b7470", "name": "E-Document Format for Factura-E", "publisher": "Microsoft" + }, + { + "id": "8238f99b-cbe5-4b9c-1234-7771145b7470", + "name": "E-Document Format for PINT A-NZ", + "publisher": "Microsoft" } ], "screenshots": [], From e9b40b1a0fac8443ad15f695314d989dff3a1980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:45:55 +0300 Subject: [PATCH 05/16] Update EDocumentFacturaEHandler.Codeunit.al Remove documentation from local procedures --- .../Core/EDocumentFacturaEHandler.Codeunit.al | 54 +------------------ 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index 67e13818b5..06fc4900b0 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -79,13 +79,7 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - /// - /// Populates the purchase invoice header with data extracted from the Factura-E XML document. - /// - /// The Factura-E XML document containing the invoice data. - /// The XML namespace manager for handling XML namespaces. - /// The E-Document Purchase Header record to be populated. - /// The E-Document record containing document information. + local procedure PopulateFacturaEPurchaseInvoiceHeader(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") var EDocumentXMLHelper: Codeunit "EDocument XML Helper"; @@ -107,13 +101,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; end; #pragma warning restore AA0139 - - /// - /// Inserts purchase invoice lines extracted from the Factura-E XML document. - /// - /// The Factura-E XML document containing the invoice line data. - /// The XML namespace manager for handling XML namespaces. - /// The E-Document entry number to associate with the lines. local procedure InsertFacturaEPurchaseInvoiceLines(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; EDocumentEntryNo: Integer) var EDocumentPurchaseLine: Record "E-Document Purchase Line"; @@ -136,12 +123,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - /// - /// Populates a single purchase line with data extracted from the Factura-E XML line. - /// - /// The XML document containing a single invoice line. - /// The XML namespace manager for handling XML namespaces. - /// The E-Document Purchase Line record to be populated. local procedure PopulateFacturaEPurchaseLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line") var EDocumentXMLHelper: Codeunit "EDocument XML Helper"; @@ -166,14 +147,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; #pragma warning restore AA0139 - /// - /// Parses the seller party information from the Factura-E XML and attempts to find the corresponding vendor. - /// - /// The Factura-E XML document containing the seller party data. - /// The XML namespace manager for handling XML namespaces. - /// The E-Document record containing document information. - /// The E-Document Purchase Header record to be updated with vendor information. - /// Returns the vendor number if found, otherwise returns empty string. local procedure ParseSellerParty(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocument: Record "E-Document"; var EDocumentPurchaseHeader: Record "E-Document Purchase Header") VendorNo: Code[20] var Vendor: Record Vendor; @@ -235,12 +208,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; end; - /// - /// Parses the buyer party information from the Factura-E XML and updates the purchase header with customer details. - /// - /// The Factura-E XML document containing the buyer party data. - /// The XML namespace manager for handling XML namespaces. - /// The E-Document Purchase Header record to be updated with customer information. local procedure ParseBuyerParty(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header") var BuyerName, BuyerAddress : Text; @@ -256,13 +223,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader EDocumentPurchaseHeader."Customer VAT Id" := CopyStr(XMLNode.AsXmlElement().InnerText(), 1, MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id")); end; - /// - /// Extracts the name from a party (seller or buyer) depending on the person type (individual or corporate). - /// - /// The Factura-E XML document containing the party data. - /// The XML namespace manager for handling XML namespaces. - /// The XML path prefix to the party node (e.g., '/namespace:Facturae/Parties/SellerParty/'). - /// Returns the formatted name based on the person type. local procedure GetNameDependingOnType(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; PathPrefix: Text) Name: Text var XMLNode: XmlNode; @@ -284,13 +244,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader Name := XMLNode.AsXmlElement().InnerText(); end; - /// - /// Extracts the address from a party (seller or buyer) depending on the residence type (Spain or overseas). - /// - /// The Factura-E XML document containing the party data. - /// The XML namespace manager for handling XML namespaces. - /// The XML path prefix to the party node (e.g., '/namespace:Facturae/Parties/SellerParty/'). - /// Returns the address based on the residence type. local procedure GetAddressDependingOnType(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; PathPrefix: Text) Address: Text var XMLNode: XmlNode; @@ -308,11 +261,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader Address := XMLNode.AsXmlElement().InnerText(); end; - /// - /// Attempts to convert an international unit of measure code to the corresponding Business Central unit of measure code. - /// - /// The international unit of measure code as text. - /// Returns the Business Central unit of measure code if found, otherwise returns empty string. local procedure TryGetUOMCodeFromInternationalCode(ImportedUOMCode: Text) UOMCode: Code[10] var UnitOfMeasure: Record "Unit of Measure"; From f8f605848d0db0fbb48feca68cae63ed08ddfd24 Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:53:28 +0300 Subject: [PATCH 06/16] Delete Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al --- .../Core/PINTANZReadDraftHandler.Codeunit.al | 279 ------------------ 1 file changed, 279 deletions(-) delete mode 100644 Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al diff --git a/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al b/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al deleted file mode 100644 index 643e538aee..0000000000 --- a/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZReadDraftHandler.Codeunit.al +++ /dev/null @@ -1,279 +0,0 @@ -namespace Microsoft.eServices.EDocument.Format; - -using Microsoft.eServices.EDocument.Processing.Interfaces; -using Microsoft.eServices.EDocument.Processing.Import; -using Microsoft.eServices.EDocument.Processing.Import.Purchase; -using Microsoft.eServices.EDocument.Service.Participant; -using Microsoft.eServices.EDocument.Helpers; -using Microsoft.Purchases.Vendor; -using Microsoft.Finance.GeneralLedger.Setup; -using System.Utilities; -using Microsoft.eServices.EDocument; - -codeunit 28008 "PINT A-NZ Read Draft Handler" implements IStructuredFormatReader -{ - Access = Internal; - InherentEntitlements = X; - InherentPermissions = X; - - var - EDocumentImportHelper: Codeunit "E-Document Import Helper"; - - procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" - var - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - DocStream: InStream; - PINTANZXml: XmlDocument; - XmlNamespaces: XmlNamespaceManager; - XmlElement: XmlElement; - CommonAggregateComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'; - CommonBasicComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'; - DefaultInvoiceLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'; - DefaultCreditNoteLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'; - begin - EDocumentPurchaseHeader.InsertForEDocument(EDocument); - - TempBlob.CreateInStream(DocStream, TextEncoding::UTF8); - XmlDocument.ReadFrom(DocStream, PINTANZXml); - XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsLbl); - XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsLbl); - XmlNamespaces.AddNamespace('inv', DefaultInvoiceLbl); - XmlNamespaces.AddNamespace('cn', DefaultCreditNoteLbl); - - PINTANZXml.GetRoot(XmlElement); - case UpperCase(XmlElement.LocalName()) of - 'INVOICE': - PopulateEDocumentForInvoice(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); - 'CREDITNOTE': - PopulateEDocumentForCreditNote(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); - end; - - EDocumentPurchaseHeader.Modify(); - EDocument.Direction := EDocument.Direction::Incoming; - exit(Enum::"E-Doc. Process Draft"::"Purchase Document"); - end; - - procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") - var - EDocPurchaseHeader: Record "E-Document Purchase Header"; - EDocPurchaseLine: Record "E-Document Purchase Line"; - EDocReadablePurchaseDoc: Page "E-Doc. Readable Purchase Doc."; - begin - EDocPurchaseHeader.GetFromEDocument(EDocument); - EDocPurchaseLine.SetRange("E-Document Entry No.", EDocPurchaseHeader."E-Document Entry No."); - EDocReadablePurchaseDoc.SetBuffer(EDocPurchaseHeader, EDocPurchaseLine); - EDocReadablePurchaseDoc.Run(); - end; - - #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - local procedure PopulateEDocumentForInvoice(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") - var - EDocumentXMLHelper: Codeunit "EDocument XML Helper"; - VendorNo: Code[20]; - begin - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); - EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); - EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:DueDate', EDocumentPurchaseHeader."Due Date"); - EDocumentXMLHelper.SetCurrencyValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); - EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); - EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader.Total); - EDocumentPurchaseHeader."Amount Due" := EDocumentPurchaseHeader.Total; - VendorNo := ParseAccountingSupplierPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'inv:Invoice'); - ParseAccountingCustomerPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, 'inv:Invoice'); - if VendorNo <> '' then - EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; - - InsertPINTANZPurchaseInvoiceLines(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No."); - end; - - local procedure PopulateEDocumentForCreditNote(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") - var - EDocumentXMLHelper: Codeunit "EDocument XML Helper"; - VendorNo: Code[20]; - begin - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); - EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); - EDocumentXMLHelper.SetDateValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:PaymentMeans/cbc:PaymentDueDate', EDocumentPurchaseHeader."Due Date"); - EDocumentXMLHelper.SetCurrencyValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); - EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); - EDocumentXMLHelper.SetNumberValueInField(PINTANZXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader.Total); - EDocumentPurchaseHeader."Amount Due" := EDocumentPurchaseHeader.Total; - VendorNo := ParseAccountingSupplierPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'cn:CreditNote'); - ParseAccountingCustomerPartyForPurchaseHeader(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader, 'cn:CreditNote'); - if VendorNo <> '' then - EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; - InsertPINTANZPurchaseInvoiceLines(PINTANZXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No."); - end; - #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - local procedure ParseAccountingSupplierPartyForPurchaseHeader(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document"; DocumentType: Text) VendorNo: Code[20] - var - EDocumentXMLHelper: Codeunit "EDocument XML Helper"; - VendorName, VendorAddress, VendorParticipantId : Text; - VATRegistrationNo: Text[20]; - GLN: Code[13]; - ABN: Code[11]; - XMLNode: XmlNode; - ABNSchemeIdTok: Label '0151', Locked = true; - GLNSchemeIdTok: Label '0088', Locked = true; - BasePathTxt: Text; - begin - BasePathTxt := '/' + DocumentType + '/cac:AccountingSupplierParty/cac:Party'; - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Company Name"), EDocumentPurchaseHeader."Vendor Company Name"); - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Vendor Address"), EDocumentPurchaseHeader."Vendor Address"); - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Vendor VAT Id"), EDocumentPurchaseHeader."Vendor VAT Id"); - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:Contact/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Contact Name"), EDocumentPurchaseHeader."Vendor Contact Name"); - if PINTANZXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin - if XMLNode.AsXmlAttribute().Value() = ABNSchemeIdTok then - ABN := CopyStr(GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'), 1, MaxStrLen(ABN)); - if XMLNode.AsXmlAttribute().Value() = GLNSchemeIdTok then begin - GLN := CopyStr(GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'), 1, MaxStrLen(GLN)); - EDocumentPurchaseHeader."Vendor GLN" := GLN; - end; - - VendorParticipantId := XMLNode.AsXmlAttribute().Value() + ':' + GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); - end; - VATRegistrationNo := EDocumentPurchaseHeader."Vendor VAT Id"; - VendorName := EDocumentPurchaseHeader."Vendor Company Name"; - VendorAddress := EDocumentPurchaseHeader."Vendor Address"; - if not FindVendorByABN(VendorNo, ABN) then - if not FindVendorByVATRegNoOrGLN(VendorNo, VATRegistrationNo, GLN) then - if not FindVendorByParticipantId(VendorNo, EDocument, VendorParticipantId) then - VendorNo := EDocumentImportHelper.FindVendorByNameAndAddress(VendorName, VendorAddress); - end; - - local procedure ParseAccountingCustomerPartyForPurchaseHeader(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; DocumentType: Text) - var - EDocumentXMLHelper: Codeunit "EDocument XML Helper"; - ReceivingId: Text[250]; - SchemaId, CompanyIdentifierValue : Text; - BasePathTxt: Text; - XMLNode: XmlNode; - begin - BasePathTxt := '/' + DocumentType + '/cac:AccountingCustomerParty/cac:Party'; - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Customer Company Name"), EDocumentPurchaseHeader."Customer Company Name"); - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Customer Address"), EDocumentPurchaseHeader."Customer Address"); - EDocumentXMLHelper.SetStringValueInField(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id"), EDocumentPurchaseHeader."Customer VAT Id"); - if PINTANZXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin - SchemaId := XMLNode.AsXmlAttribute().Value(); - CompanyIdentifierValue := GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); - if SchemaId = '0088' then - EDocumentPurchaseHeader."Customer GLN" := CopyStr(CompanyIdentifierValue, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN")); - ReceivingId := CopyStr(SchemaId, 1, (MaxStrLen(EDocumentPurchaseHeader."Customer Company Id") - 1)) + ':'; - ReceivingId += CopyStr(CompanyIdentifierValue, 1, MaxStrLen(EDocumentPurchaseHeader."Customer Company Id") - StrLen(ReceivingId)); - EDocumentPurchaseHeader."Customer Company Id" := ReceivingId; - end; - if (EDocumentPurchaseHeader."Customer GLN" = '') and PINTANZXml.SelectSingleNode(BasePathTxt + '/cac:PartyIdentification/cbc:ID/@schemeID', XmlNamespaces, XMLNode) then begin - SchemaId := XMLNode.AsXmlAttribute().Value(); - CompanyIdentifierValue := GetNodeValue(PINTANZXml, XmlNamespaces, BasePathTxt + '/cac:PartyIdentification/cbc:ID'); - if SchemaId = '0088' then - EDocumentPurchaseHeader."Customer GLN" := CopyStr(CompanyIdentifierValue, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN")); - end; - end; - - local procedure InsertPINTANZPurchaseInvoiceLines(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; EDocumentEntryNo: Integer) - var - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - NewLineXML: XmlDocument; - LineXMLList: XmlNodeList; - LineXMLNode: XmlNode; - i: Integer; - InvoiceLinePathLbl: Label '/inv:Invoice/cac:InvoiceLine'; - CreditNoteLinePathLbl: Label '/cn:CreditNote/cac:CreditNoteLine'; - begin - if not PINTANZXml.SelectNodes(InvoiceLinePathLbl, XmlNamespaces, LineXMLList) then - if not PINTANZXml.SelectNodes(CreditNoteLinePathLbl, XmlNamespaces, LineXMLList) then - exit; - - for i := 1 to LineXMLList.Count do begin - LineXMLList.Get(i, LineXMLNode); - Clear(EDocumentPurchaseLine); - EDocumentPurchaseLine.Init(); - EDocumentPurchaseLine."E-Document Entry No." := EDocumentEntryNo; - EDocumentPurchaseLine."Line No." := i * 10000; - Clear(NewLineXML); - NewLineXML := XmlDocument.Create(); - NewLineXML.Add(LineXMLNode.AsXmlElement()); - PopulatePINTANZPurchaseLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine); - EDocumentPurchaseLine.Insert(); - end; - end; - - local procedure PopulatePINTANZPurchaseLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line") - var - EDocumentXMLHelper: Codeunit "EDocument XML Helper"; - begin - EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cac:Item/cac:SellersItemIdentification/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); - if EDocumentPurchaseLine."Product Code" = '' then - EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cac:Item/cac:SellersItemIdentification/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); - EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cac:Item/cbc:Name', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); - if EDocumentPurchaseLine.Description = '' then - EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cac:Item/cbc:Name', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); - EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:InvoicedQuantity', EDocumentPurchaseLine.Quantity); - if EDocumentPurchaseLine.Quantity = 0 then - EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:CreditedQuantity', EDocumentPurchaseLine.Quantity); - EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:InvoicedQuantity/@unitCode', MaxStrLen(EDocumentPurchaseLine."Unit of Measure"), EDocumentPurchaseLine."Unit of Measure"); - if EDocumentPurchaseLine."Unit of Measure" = '' then - EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:CreditedQuantity/@unitCode', MaxStrLen(EDocumentPurchaseLine."Unit of Measure"), EDocumentPurchaseLine."Unit of Measure"); - EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cac:Price/cbc:PriceAmount', EDocumentPurchaseLine."Unit Price"); - if EDocumentPurchaseLine."Unit Price" = 0 then - EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cac:Price/cbc:PriceAmount', EDocumentPurchaseLine."Unit Price"); - EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:LineExtensionAmount', EDocumentPurchaseLine."Sub Total"); - if EDocumentPurchaseLine."Sub Total" = 0 then - EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:LineExtensionAmount', EDocumentPurchaseLine."Sub Total"); - EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:InvoiceLine/cbc:LineExtensionAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); - if EDocumentPurchaseLine."Currency Code" = '' then - EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:CreditNoteLine/cbc:LineExtensionAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); - end; - - local procedure GetNodeValue(PINTANZXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; XPath: Text): Text - var - XMLNode: XmlNode; - begin - if PINTANZXml.SelectSingleNode(XPath, XmlNamespaces, XMLNode) then - exit(XMLNode.AsXmlElement().InnerText()); - exit(''); - end; - - local procedure FindVendorByABN(var VendorNo: Code[20]; InputABN: Code[11]): Boolean - var - Vendor: Record Vendor; - begin - if InputABN = '' then - exit(false); - - Vendor.SetRange(ABN, InputABN); - if Vendor.FindFirst() then - VendorNo := Vendor."No."; - exit(VendorNo <> ''); - end; - - local procedure FindVendorByVATRegNoOrGLN(var VendorNo: Code[20]; VATRegistrationNo: Text[20]; InputGLN: Code[13]): Boolean - begin - VendorNo := EDocumentImportHelper.FindVendor('', InputGLN, VATRegistrationNo); - exit(VendorNo <> ''); - end; - - local procedure FindVendorByParticipantId(var VendorNo: Code[20]; EDocument: Record "E-Document"; VendorParticipantId: Text): Boolean - var - EDocumentService: Record "E-Document Service"; - ServiceParticipant: Record "Service Participant"; - EDocumentHelper: Codeunit "E-Document Helper"; - begin - if VendorParticipantId = '' then - exit(false); - - EDocumentHelper.GetEdocumentService(EDocument, EDocumentService); - ServiceParticipant.SetRange("Participant Type", ServiceParticipant."Participant Type"::Vendor); - ServiceParticipant.SetRange("Participant Identifier", VendorParticipantId); - ServiceParticipant.SetRange(Service, EDocumentService.Code); - if not ServiceParticipant.FindFirst() then begin - ServiceParticipant.SetRange(Service); - if ServiceParticipant.FindFirst() then; - end; - - VendorNo := ServiceParticipant.Participant; - exit(VendorNo <> ''); - end; -} \ No newline at end of file From fd96311bc8fe0d45b9132704414b8f92f19c2f7a Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:53:42 +0300 Subject: [PATCH 07/16] Delete Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al --- .../src/Core/PINTANZEDocReadintoDraft.EnumExt.al | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al diff --git a/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al b/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al deleted file mode 100644 index f6d2ed8165..0000000000 --- a/Apps/APAC/EDocumentFormats/PINT A-NZ/app/src/Core/PINTANZEDocReadintoDraft.EnumExt.al +++ /dev/null @@ -1,13 +0,0 @@ -namespace Microsoft.eServices.EDocument.Format; - -using Microsoft.eServices.EDocument.Processing.Import; -using Microsoft.eServices.EDocument.Processing.Interfaces; - -enumextension 28006 "PINT A-NZ EDoc Read into Draft" extends "E-Doc. Read into Draft" -{ - value(28005; "PINT A-NZ") - { - Caption = 'PINT A-NZ'; - Implementation = IStructuredFormatReader = "PINT A-NZ Read Draft Handler"; - } -} \ No newline at end of file From a20245bae61ed935c7859fabad288007b8c26ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 6 Aug 2025 10:17:20 +0300 Subject: [PATCH 08/16] Removes PINT A-NZ format dependency from app manifest Cleans up the dependencies section by removing the unused E-Document Format for PINT A-NZ extension reference. Streamlines the app configuration and eliminates unnecessary dependency declarations. --- Apps/W1/EDocument/app/app.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Apps/W1/EDocument/app/app.json b/Apps/W1/EDocument/app/app.json index 3f7706f36d..15e978da0d 100644 --- a/Apps/W1/EDocument/app/app.json +++ b/Apps/W1/EDocument/app/app.json @@ -42,11 +42,6 @@ "id": "8238f99b-cbe5-4b9c-b247-7771145b7470", "name": "E-Document Format for Factura-E", "publisher": "Microsoft" - }, - { - "id": "8238f99b-cbe5-4b9c-1234-7771145b7470", - "name": "E-Document Format for PINT A-NZ", - "publisher": "Microsoft" } ], "screenshots": [], From cd214372981953ebdb27e37765af380c228be83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:23:51 +0300 Subject: [PATCH 09/16] Update Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index 06fc4900b0..8c63ea2e0a 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -79,7 +79,6 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader end; #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - local procedure PopulateFacturaEPurchaseInvoiceHeader(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") var EDocumentXMLHelper: Codeunit "EDocument XML Helper"; From 7953ae7ca2a8cd5157a35bcfbc972e69876bd543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:24:09 +0300 Subject: [PATCH 10/16] Update Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al | 1 + 1 file changed, 1 insertion(+) diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index 8c63ea2e0a..5f6171cec4 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -100,6 +100,7 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; end; #pragma warning restore AA0139 + local procedure InsertFacturaEPurchaseInvoiceLines(FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; EDocumentEntryNo: Integer) var EDocumentPurchaseLine: Record "E-Document Purchase Line"; From 985cc1740a586fc2f067a21e88efb0718163af67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 6 Aug 2025 10:28:18 +0300 Subject: [PATCH 11/16] Reorganizes imports and locks namespace labels Reorders using statements alphabetically for better code organization. Adds Locked = true attribute to namespace labels to prevent translation, ensuring XML namespace URIs remain constant across different language configurations. --- .../src/Core/EDocumentFacturaEHandler.Codeunit.al | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index 5f6171cec4..bbd0d252b1 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -4,17 +4,17 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.eServices.EDocument.Format.FacturaE; -using Microsoft.eServices.EDocument.Helpers; using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Format; +using Microsoft.eServices.EDocument.Helpers; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.eServices.EDocument.Processing.Interfaces; using Microsoft.eServices.EDocument.Service.Participant; -using Microsoft.Purchases.Vendor; using Microsoft.Foundation.UOM; -using System.Utilities; +using Microsoft.Purchases.Vendor; using System.IO; +using System.Utilities; codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader { @@ -37,9 +37,9 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; XmlElement: XmlElement; - FacturaENamespaceLbl: Label 'http://www.facturae.gob.es/formato/Versiones/Facturaev3_2_2.xml'; - DigitalSignatureNamespaceLbl: Label 'http://www.w3.org/2000/09/xmldsig#'; - ETSINamespaceLbl: Label 'http://uri.etsi.org/01903/v1.2.2#'; + FacturaENamespaceLbl: Label 'http://www.facturae.gob.es/formato/Versiones/Facturaev3_2_2.xml', Locked = true; + DigitalSignatureNamespaceLbl: Label 'http://www.w3.org/2000/09/xmldsig#', Locked = true; + ETSINamespaceLbl: Label 'http://uri.etsi.org/01903/v1.2.2#', Locked = true; XMLNode: XmlNode; begin EDocumentPurchaseHeader.InsertForEDocument(EDocument); @@ -107,7 +107,7 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader NewLineXML: XmlDocument; LineXMLList: XmlNodeList; LineXMLNode: XmlNode; - InvoiceLinePathLbl: Label '/namespace:Facturae/Invoices/Invoice/Items/InvoiceLine'; + InvoiceLinePathLbl: Label '/namespace:Facturae/Invoices/Invoice/Items/InvoiceLine', Locked = true; begin if not FacturaEXML.SelectNodes(InvoiceLinePathLbl, XmlNamespaces, LineXMLList) then exit; From 1085d7e87e4241995024d41cf72292d460369b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 6 Aug 2025 10:29:25 +0300 Subject: [PATCH 12/16] Renames label constants to use 'Tok' suffix convention Updates variable naming to follow AL coding standards where token constants use 'Tok' suffix instead of 'Lbl' suffix. Changes apply to namespace URI constants and XPath expression constants used in XML processing. --- .../Core/EDocumentFacturaEHandler.Codeunit.al | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index bbd0d252b1..f790bbdbda 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -37,17 +37,17 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader FacturaEXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; XmlElement: XmlElement; - FacturaENamespaceLbl: Label 'http://www.facturae.gob.es/formato/Versiones/Facturaev3_2_2.xml', Locked = true; - DigitalSignatureNamespaceLbl: Label 'http://www.w3.org/2000/09/xmldsig#', Locked = true; - ETSINamespaceLbl: Label 'http://uri.etsi.org/01903/v1.2.2#', Locked = true; + FacturaENamespaceTok: Label 'http://www.facturae.gob.es/formato/Versiones/Facturaev3_2_2.xml', Locked = true; + DigitalSignatureNamespaceTok: Label 'http://www.w3.org/2000/09/xmldsig#', Locked = true; + ETSINamespaceTok: Label 'http://uri.etsi.org/01903/v1.2.2#', Locked = true; XMLNode: XmlNode; begin EDocumentPurchaseHeader.InsertForEDocument(EDocument); XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), FacturaEXML); - XmlNamespaces.AddNamespace('namespace', FacturaENamespaceLbl); - XmlNamespaces.AddNamespace('ds', DigitalSignatureNamespaceLbl); - XmlNamespaces.AddNamespace('etsi', ETSINamespaceLbl); + XmlNamespaces.AddNamespace('namespace', FacturaENamespaceTok); + XmlNamespaces.AddNamespace('ds', DigitalSignatureNamespaceTok); + XmlNamespaces.AddNamespace('etsi', ETSINamespaceTok); FacturaEXML.GetRoot(XmlElement); EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Invoice"; @@ -107,9 +107,9 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader NewLineXML: XmlDocument; LineXMLList: XmlNodeList; LineXMLNode: XmlNode; - InvoiceLinePathLbl: Label '/namespace:Facturae/Invoices/Invoice/Items/InvoiceLine', Locked = true; + InvoiceLinePathTok: Label '/namespace:Facturae/Invoices/Invoice/Items/InvoiceLine', Locked = true; begin - if not FacturaEXML.SelectNodes(InvoiceLinePathLbl, XmlNamespaces, LineXMLList) then + if not FacturaEXML.SelectNodes(InvoiceLinePathTok, XmlNamespaces, LineXMLList) then exit; foreach LineXMLNode in LineXMLList do begin From 84d65399b8aa9eb29ea3c6ebfdb12fd7a8427bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Tue, 26 Aug 2025 11:28:26 +0300 Subject: [PATCH 13/16] Add Factura-E test suite and draft reset support Introduces a mock e-document format, validation helpers, and a sample Facturae XML to enable end-to-end tests for incoming invoice parsing and purchase document creation. Adds a reusable test library to set up services, workflows, master data, and mappings, and to drive inbound/import scenarios. Updates the test app manifest (ID range, target, resource folders) and includes a test dependency. Adds a routine to reset drafts by deleting the generated purchase draft, enabling clean test reruns and easier state management. --- .../Core/EDocumentFacturaEHandler.Codeunit.al | 8 + .../factura-e/facturae-invoice-0.xml | 146 +++ .../EDocumentFormats/FacturaE/test/app.json | 8 +- .../test/src/EDocFormatMock.Codeunit.al | 56 + .../test/src/EDocFormatMock.EnumExt.al | 7 + .../src/FacturaEStructValidations.Codeunit.al | 102 ++ .../test/src/LibraryEDocument.Codeunit.al | 1045 +++++++++++++++++ Apps/W1/EDocument/app/app.json | 5 + 8 files changed, 1375 insertions(+), 2 deletions(-) create mode 100644 Apps/ES/EDocumentFormats/FacturaE/test/.resources/factura-e/facturae-invoice-0.xml create mode 100644 Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al create mode 100644 Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.EnumExt.al create mode 100644 Apps/ES/EDocumentFormats/FacturaE/test/src/FacturaEStructValidations.Codeunit.al create mode 100644 Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al diff --git a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al index f790bbdbda..a82b692994 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/app/src/Core/EDocumentFacturaEHandler.Codeunit.al @@ -273,4 +273,12 @@ codeunit 10776 "E-Document Factura-E Handler" implements IStructuredFormatReader UOMCode := UnitOfMeasure.Code; end; end; + + procedure ResetDraft(EDocument: Record "E-Document") + var + EDocPurchaseHeader: Record "E-Document Purchase Header"; + begin + EDocPurchaseHeader.GetFromEDocument(EDocument); + EDocPurchaseHeader.Delete(true); + end; } diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/.resources/factura-e/facturae-invoice-0.xml b/Apps/ES/EDocumentFormats/FacturaE/test/.resources/factura-e/facturae-invoice-0.xml new file mode 100644 index 0000000000..9d879559fb --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/test/.resources/factura-e/facturae-invoice-0.xml @@ -0,0 +1,146 @@ + + + + 3.2.2 + I + EM + + 103033 + 1 + + 14140 + + + 14140 + + + 14140 + + XYZ + + + + + + J + R + GB123456789 + + + CRONUS International + +
Main Street, 14
+ B27 4KT + Birmingham + Birmingham + ESP +
+ + + JO@contoso.com + +
+
+ + + J + R + GB789456278 + + + The Cannon Group PLC + +
192 Market Square
+ B27 4KT + Birmingham + Birmingham + ESP +
+ + + mr.andy.teal@cannongroup.com + +
+
+
+ + + + 103033 + FC + OO + + + 2026-01-22 + XYZ + XYZ + es + + + + 01 + 25 + + 14000 + + + 1000 + + + + + 14000 + 14000 + 1000 + 0 + 14140 + 14140 + 14140 + + + + Bicycle + 1 + 01 + 4000.00 + 4000 + 4000 + + + 01 + 25 + + 4000 + + + 1000 + + + + 1000 + + + Bicycle v2 + 2 + 01 + 5000.00 + 10000 + 10000 + + + 01 + 25 + + 10000 + + + 0 + + + + 2000 + + + + +
\ No newline at end of file diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/app.json b/Apps/ES/EDocumentFormats/FacturaE/test/app.json index 80abb55a9a..ece447439a 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/test/app.json +++ b/Apps/ES/EDocumentFormats/FacturaE/test/app.json @@ -51,7 +51,7 @@ "idRanges": [ { "from": 148001, - "to": 148002 + "to": 148005 } ], "resourceExposurePolicy": { @@ -59,5 +59,9 @@ "allowDownloadingSource": true, "includeSourceInSymbolFile": true }, - "application": "27.0.0.0" + "application": "27.0.0.0", + "target": "OnPrem", + "resourceFolders": [ + ".resources" + ] } diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al new file mode 100644 index 0000000000..e5215cf135 --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al @@ -0,0 +1,56 @@ +codeunit 148004 "E-Doc. Format Mock" implements "E-Document" +{ + SingleInstance = true; + + procedure Check(var SourceDocumentHeader: RecordRef; EDocService: Record "E-Document Service"; EDocumentProcessingPhase: enum "E-Document Processing Phase"); + begin + OnCheck(SourceDocumentHeader, EDocService, EDocumentProcessingPhase); + end; + + procedure Create(EDocService: Record "E-Document Service"; var EDocument: Record "E-Document"; var SourceDocumentHeader: RecordRef; var SourceDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + OnCreate(EDocService, EDocument, SourceDocumentHeader, SourceDocumentLines, TempBlob); + end; + + procedure CreateBatch(EDocService: Record "E-Document Service"; var EDocuments: Record "E-Document"; var SourceDocumentHeaders: RecordRef; var SourceDocumentsLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + OnCreateBatch(EDocService, EDocuments, SourceDocumentHeaders, SourceDocumentsLines, TempBlob); + end; + + procedure GetBasicInfoFromReceivedDocument(var EDocument: Record "E-Document"; var TempBlob: codeunit "Temp Blob"); + begin + OnGetBasicInfoFromReceivedDocument(EDocument, TempBlob); + end; + + procedure GetCompleteInfoFromReceivedDocument(var EDocument: Record "E-Document"; var CreatedDocumentHeader: RecordRef; var CreatedDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + OnGetCompleteInfoFromReceivedDocument(EDocument, CreatedDocumentHeader, CreatedDocumentLines, TempBlob); + end; + + [IntegrationEvent(false, false)] + local procedure OnCheck(var SourceDocumentHeader: RecordRef; EDocService: Record "E-Document Service"; EDocumentProcessingPhase: enum "E-Document Processing Phase") + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnCreate(EDocService: Record "E-Document Service"; var EDocument: Record "E-Document"; var SourceDocumentHeader: RecordRef; var SourceDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnCreateBatch(EDocService: Record "E-Document Service"; var EDocuments: Record "E-Document"; var SourceDocumentHeaders: RecordRef; var SourceDocumentsLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnGetBasicInfoFromReceivedDocument(var EDocument: Record "E-Document"; var TempBlob: codeunit "Temp Blob"); + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnGetCompleteInfoFromReceivedDocument(var EDocument: Record "E-Document"; var CreatedDocumentHeader: RecordRef; var CreatedDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + end; + + +} \ No newline at end of file diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.EnumExt.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.EnumExt.al new file mode 100644 index 0000000000..21d54be88e --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.EnumExt.al @@ -0,0 +1,7 @@ +enumextension 148001 "E-Doc. Format Mock" extends "E-Document Format" +{ + value(6160; "Mock") + { + Implementation = "E-Document" = "E-Doc. Format Mock"; + } +} \ No newline at end of file diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/FacturaEStructValidations.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/FacturaEStructValidations.Codeunit.al new file mode 100644 index 0000000000..b66fbc5a1d --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/FacturaEStructValidations.Codeunit.al @@ -0,0 +1,102 @@ +codeunit 148005 "Factura-E Struct. Validations" +{ + var + Assert: Codeunit Assert; + UnitOfMeasureCodeTok: Label '01', Locked = true; + SalesInvoiceNoTok: Label '103033', Locked = true; + MockDate: Date; + MockCurrencyCode: Code[10]; + MockDataMismatchErr: Label 'The %1 in %2 does not align with the mock data. Expected: %3, Actual: %4', Locked = true, Comment = '%1 = Field caption, %2 = Table caption, %3 = Expected value, %4 = Actual value'; + + internal procedure AssertFullEDocumentContentExtracted(EDocumentEntryNo: Integer) + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + begin + EDocumentPurchaseHeader.Get(EDocumentEntryNo); + Assert.AreEqual(SalesInvoiceNoTok, EDocumentPurchaseHeader."Sales Invoice No.", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Sales Invoice No."), EDocumentPurchaseHeader.TableCaption(), '103033', EDocumentPurchaseHeader."Sales Invoice No.")); + Assert.AreEqual(MockDate, EDocumentPurchaseHeader."Document Date", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Document Date"), EDocumentPurchaseHeader.TableCaption(), DMY2Date(22, 1, 2026), EDocumentPurchaseHeader."Document Date")); + Assert.AreEqual(MockCurrencyCode, EDocumentPurchaseHeader."Currency Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Currency Code"), EDocumentPurchaseHeader.TableCaption(), 'XYZ', EDocumentPurchaseHeader."Currency Code")); + Assert.AreEqual(14000, EDocumentPurchaseHeader."Sub Total", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Sub Total"), EDocumentPurchaseHeader.TableCaption(), 14000, EDocumentPurchaseHeader."Sub Total")); + Assert.AreEqual(1000, EDocumentPurchaseHeader."Total VAT", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Total VAT"), EDocumentPurchaseHeader.TableCaption(), 1000, EDocumentPurchaseHeader."Total VAT")); + Assert.AreEqual(14140, EDocumentPurchaseHeader.Total, StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption(Total), EDocumentPurchaseHeader.TableCaption(), 14140, EDocumentPurchaseHeader.Total)); + Assert.AreEqual(14140, EDocumentPurchaseHeader."Amount Due", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Amount Due"), EDocumentPurchaseHeader.TableCaption(), 14140, EDocumentPurchaseHeader."Amount Due")); + Assert.AreEqual('CRONUS International', EDocumentPurchaseHeader."Vendor Company Name", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor Company Name"), EDocumentPurchaseHeader.TableCaption(), 'CRONUS International', EDocumentPurchaseHeader."Vendor Company Name")); + Assert.AreEqual('Main Street, 14', EDocumentPurchaseHeader."Vendor Address", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor Address"), EDocumentPurchaseHeader.TableCaption(), 'Main Street, 14', EDocumentPurchaseHeader."Vendor Address")); + Assert.AreEqual('GB123456789', EDocumentPurchaseHeader."Vendor VAT Id", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor VAT Id"), EDocumentPurchaseHeader.TableCaption(), 'GB123456789', EDocumentPurchaseHeader."Vendor VAT Id")); + Assert.AreEqual('The Cannon Group PLC', EDocumentPurchaseHeader."Customer Company Name", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Customer Company Name"), EDocumentPurchaseHeader.TableCaption(), 'The Cannon Group PLC', EDocumentPurchaseHeader."Customer Company Name")); + Assert.AreEqual('GB789456278', EDocumentPurchaseHeader."Customer VAT Id", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Customer VAT Id"), EDocumentPurchaseHeader.TableCaption(), 'GB789456278', EDocumentPurchaseHeader."Customer VAT Id")); + Assert.AreEqual('192 Market Square', EDocumentPurchaseHeader."Customer Address", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Customer Address"), EDocumentPurchaseHeader.TableCaption(), '192 Market Square', EDocumentPurchaseHeader."Customer Address")); + + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); + EDocumentPurchaseLine.FindSet(); + Assert.AreEqual(1, EDocumentPurchaseLine.Quantity, StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption(Quantity), EDocumentPurchaseLine.TableCaption(), 1, EDocumentPurchaseLine.Quantity)); + Assert.AreEqual(UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit of Measure"), EDocumentPurchaseLine.TableCaption(), '01', EDocumentPurchaseLine."Unit of Measure")); + Assert.AreEqual(4000, EDocumentPurchaseLine."Sub Total", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Sub Total"), EDocumentPurchaseLine.TableCaption(), 4000, EDocumentPurchaseLine."Sub Total")); + Assert.AreEqual(0, EDocumentPurchaseLine."Total Discount", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Total Discount"), EDocumentPurchaseLine.TableCaption(), 0, EDocumentPurchaseLine."Total Discount")); + Assert.AreEqual('Bicycle', EDocumentPurchaseLine.Description, StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption(Description), EDocumentPurchaseLine.TableCaption(), 'Bicycle', EDocumentPurchaseLine.Description)); + Assert.AreEqual('1000', EDocumentPurchaseLine."Product Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Product Code"), EDocumentPurchaseLine.TableCaption(), '1000', EDocumentPurchaseLine."Product Code")); + Assert.AreEqual(25, EDocumentPurchaseLine."VAT Rate", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("VAT Rate"), EDocumentPurchaseLine.TableCaption(), 25, EDocumentPurchaseLine."VAT Rate")); + Assert.AreEqual(4000, EDocumentPurchaseLine."Unit Price", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit Price"), EDocumentPurchaseLine.TableCaption(), 4000, EDocumentPurchaseLine."Unit Price")); + + EDocumentPurchaseLine.Next(); + Assert.AreEqual(2, EDocumentPurchaseLine.Quantity, StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption(Quantity), EDocumentPurchaseLine.TableCaption(), 2, EDocumentPurchaseLine.Quantity)); + Assert.AreEqual(UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit of Measure"), EDocumentPurchaseLine.TableCaption(), UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure")); + Assert.AreEqual(10000, EDocumentPurchaseLine."Sub Total", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Sub Total"), EDocumentPurchaseLine.TableCaption(), 10000, EDocumentPurchaseLine."Sub Total")); + Assert.AreEqual(0, EDocumentPurchaseLine."Total Discount", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Total Discount"), EDocumentPurchaseLine.TableCaption(), 0, EDocumentPurchaseLine."Total Discount")); + Assert.AreEqual('Bicycle v2', EDocumentPurchaseLine.Description, StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption(Description), EDocumentPurchaseLine.TableCaption(), 'Bicycle v2', EDocumentPurchaseLine.Description)); + Assert.AreEqual('2000', EDocumentPurchaseLine."Product Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Product Code"), EDocumentPurchaseLine.TableCaption(), '2000', EDocumentPurchaseLine."Product Code")); + Assert.AreEqual(25, EDocumentPurchaseLine."VAT Rate", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("VAT Rate"), EDocumentPurchaseLine.TableCaption(), 25, EDocumentPurchaseLine."VAT Rate")); + Assert.AreEqual(5000, EDocumentPurchaseLine."Unit Price", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit Price"), EDocumentPurchaseLine.TableCaption(), 5000, EDocumentPurchaseLine."Unit Price")); + end; + + internal procedure AssertPurchaseDocument(VendorNo: Code[20]; PurchaseHeader: Record "Purchase Header"; Item: Record Item) + var + PurchaseLine: Record "Purchase Line"; + Item1NoTok: Label 'GL00000000', Locked = true; + Item2NoTok: Label 'GL00000001', Locked = true; + begin + Assert.AreEqual(SalesInvoiceNoTok, PurchaseHeader."Vendor Invoice No.", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Vendor Invoice No."), PurchaseHeader.TableCaption(), SalesInvoiceNoTok, PurchaseHeader."Vendor Invoice No.")); + Assert.AreEqual(MockDate, PurchaseHeader."Document Date", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Document Date"), PurchaseHeader.TableCaption(), MockDate, PurchaseHeader."Document Date")); + Assert.AreEqual(MockCurrencyCode, PurchaseHeader."Currency Code", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Currency Code"), PurchaseHeader.TableCaption(), MockCurrencyCode, PurchaseHeader."Currency Code")); + Assert.AreEqual(VendorNo, PurchaseHeader."Buy-from Vendor No.", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Buy-from Vendor No."), PurchaseHeader.TableCaption(), VendorNo, PurchaseHeader."Buy-from Vendor No.")); + + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseLine.FindSet(); + Assert.AreEqual(1, PurchaseLine.Quantity, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Quantity), PurchaseLine.TableCaption(), 1, PurchaseLine.Quantity)); + Assert.AreEqual(4000, PurchaseLine."Direct Unit Cost", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Direct Unit Cost"), PurchaseLine.TableCaption(), 4000, PurchaseLine."Direct Unit Cost")); + Assert.AreEqual(MockCurrencyCode, PurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Currency Code"), PurchaseLine.TableCaption(), MockCurrencyCode, PurchaseLine."Currency Code")); + Assert.AreEqual(0, PurchaseLine."Line Discount Amount", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Line Discount Amount"), PurchaseLine.TableCaption(), 0, PurchaseLine."Line Discount Amount")); + // In the import file we have a name 'Bicycle' but because of Item Cross Reference validation Item description is being used + if Item."No." <> '' then begin + Assert.AreEqual('Bicycle', PurchaseLine.Description, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Description), PurchaseLine.TableCaption(), Item."No.", PurchaseLine.Description)); + Assert.AreEqual(Item."No.", PurchaseLine."No.", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("No."), PurchaseLine.TableCaption(), Item."No.", PurchaseLine."No.")); + Assert.AreEqual(Item."Purch. Unit of Measure", PurchaseLine."Unit of Measure Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Unit of Measure Code"), PurchaseLine.TableCaption(), UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code")); + end else begin + Assert.AreEqual(Item1NoTok, PurchaseLine.Description, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Description), PurchaseLine.TableCaption(), Item1NoTok, PurchaseLine.Description)); + Assert.AreEqual(Item1NoTok, PurchaseLine."No.", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("No."), PurchaseLine.TableCaption(), Item1NoTok, PurchaseLine."No.")); + Assert.AreEqual(UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Unit of Measure Code"), PurchaseLine.TableCaption(), UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code")); + end; + + PurchaseLine.Next(); + Assert.AreEqual(2, PurchaseLine.Quantity, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Quantity), PurchaseLine.TableCaption(), 2, PurchaseLine.Quantity)); + Assert.AreEqual(UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Unit of Measure Code"), PurchaseLine.TableCaption(), UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code")); + Assert.AreEqual(5000, PurchaseLine."Direct Unit Cost", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Direct Unit Cost"), PurchaseLine.TableCaption(), 5000, PurchaseLine."Direct Unit Cost")); + Assert.AreEqual(MockCurrencyCode, PurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Currency Code"), PurchaseLine.TableCaption(), MockCurrencyCode, PurchaseLine."Currency Code")); + Assert.AreEqual(0, PurchaseLine."Line Discount Amount", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Line Discount Amount"), PurchaseLine.TableCaption(), 0, PurchaseLine."Line Discount Amount")); + // In the import file we have a name 'Bicycle v2' but because of Item Cross Reference validation Item description is being used + Assert.AreEqual(Item2NoTok, PurchaseLine.Description, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Description), PurchaseLine.TableCaption(), Item2NoTok, PurchaseLine.Description)); + Assert.AreEqual(Item2NoTok, PurchaseLine."No.", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("No."), PurchaseLine.TableCaption(), Item2NoTok, PurchaseLine."No.")); + end; + + procedure SetMockDate(MockDate: Date) + begin + this.MockDate := MockDate; + end; + + procedure SetMockCurrencyCode(MockCurrencyCode: Code[10]) + begin + this.MockCurrencyCode := MockCurrencyCode; + end; +} \ No newline at end of file diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al new file mode 100644 index 0000000000..9f58d0f976 --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al @@ -0,0 +1,1045 @@ +codeunit 148003 "Library - E-Document" +{ + EventSubscriberInstance = Manual; + Permissions = tabledata "E-Document Service" = rimd, + tabledata "E-Doc. Service Supported Type" = rimd, + tabledata "E-Doc. Mapping" = rimd; + + var + StandardItem: Record Item; + VATPostingSetup: Record "VAT Posting Setup"; + Assert: Codeunit Assert; + LibraryUtility: Codeunit "Library - Utility"; + LibraryWorkflow: Codeunit "Library - Workflow"; + LibrarySales: Codeunit "Library - Sales"; + LibraryPurchase: Codeunit "Library - Purchase"; + LibraryERM: Codeunit "Library - ERM"; + LibraryRandom: Codeunit "Library - Random"; + LibraryInvt: Codeunit "Library - Inventory"; + LibraryVariableStorage: Codeunit "Library - Variable Storage"; + LibraryFinChargeMemo: Codeunit "Library - Finance Charge Memo"; + LibraryInventory: Codeunit "Library - Inventory"; + + procedure SetupStandardVAT() + begin + if (VATPostingSetup."VAT Bus. Posting Group" = '') and (VATPostingSetup."VAT Prod. Posting Group" = '') then + LibraryERM.CreateVATPostingSetupWithAccounts(VATPostingSetup, Enum::"Tax Calculation Type"::"Normal VAT", 1); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "E-Document Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + SetupStandardSalesScenario(Customer, EDocService); + end; +#pragma warning restore AL0432 +#endif + + procedure SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + SetupStandardSalesScenario(Customer, EDocService); + end; + + procedure SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service") + var + CountryRegion: Record "Country/Region"; + DocumentSendingProfile: Record "Document Sending Profile"; + SalesSetup: Record "Sales & Receivables Setup"; + WorkflowSetup: Codeunit "Workflow Setup"; + WorkflowCode: Code[20]; + begin + LibraryWorkflow.DeleteAllExistingWorkflows(); + WorkflowSetup.InitWorkflow(); + SetupCompanyInfo(); + + CreateDocSendingProfile(DocumentSendingProfile); + WorkflowCode := CreateSimpleFlow(DocumentSendingProfile.Code, EDocService.Code); + DocumentSendingProfile."Electronic Document" := DocumentSendingProfile."Electronic Document"::"Extended E-Document Service Flow"; + DocumentSendingProfile."Electronic Service Flow" := WorkflowCode; + DocumentSendingProfile.Modify(); + + // Create Customer for sales scenario + LibrarySales.CreateCustomer(Customer); + LibraryERM.FindCountryRegion(CountryRegion); + Customer.Validate(Address, LibraryUtility.GenerateRandomCode(Customer.FieldNo(Address), DATABASE::Customer)); + Customer.Validate("Country/Region Code", CountryRegion.Code); + Customer.Validate(City, LibraryUtility.GenerateRandomCode(Customer.FieldNo(City), DATABASE::Customer)); + Customer.Validate("Post Code", LibraryUtility.GenerateRandomCode(Customer.FieldNo("Post Code"), DATABASE::Customer)); + Customer.Validate("VAT Bus. Posting Group", VATPostingSetup."VAT Bus. Posting Group"); + Customer."VAT Registration No." := LibraryERM.GenerateVATRegistrationNo(CountryRegion.Code); + Customer.Validate(GLN, '1234567890128'); + Customer."Document Sending Profile" := DocumentSendingProfile.Code; + Customer.Modify(true); + + // Create Item + if StandardItem."No." = '' then begin + VATPostingSetup.TestField("VAT Prod. Posting Group"); + CreateGenericItem(StandardItem); + StandardItem."VAT Prod. Posting Group" := VATPostingSetup."VAT Prod. Posting Group"; + StandardItem.Modify(); + end; + + SalesSetup.Get(); + SalesSetup."Invoice Rounding" := false; + SalesSetup.Modify(); + end; + + procedure GetGenericItem(var Item: Record Item) + begin + if StandardItem."No." = '' then + CreateGenericItem(StandardItem); + Item.Get(StandardItem."No."); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "E-Document Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + if EDocService.Code = '' then begin + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + end; + SetupStandardPurchaseScenario(Vendor, EDocService); + end; +#pragma warning restore AL0432 +#endif + + procedure SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + if EDocService.Code = '' then begin + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + end; + SetupStandardPurchaseScenario(Vendor, EDocService); + end; + + + procedure SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service") + var + CountryRegion: Record "Country/Region"; + ItemReference: Record "Item Reference"; + UnitOfMeasure: Record "Unit of Measure"; + ExtraItem: Record "Item"; + WorkflowSetup: Codeunit "Workflow Setup"; + LibraryItemReference: Codeunit "Library - Item Reference"; + UnitOfMeasureCode: Code[10]; + begin + WorkflowSetup.InitWorkflow(); + SetupCompanyInfo(); + + // Create Customer for sales scenario + LibraryPurchase.CreateVendor(Vendor); + LibraryERM.FindCountryRegion(CountryRegion); + Vendor.Validate(Address, LibraryUtility.GenerateRandomCode(Vendor.FieldNo(Address), DATABASE::Vendor)); + Vendor.Validate("Country/Region Code", CountryRegion.Code); + Vendor.Validate(City, LibraryUtility.GenerateRandomCode(Vendor.FieldNo(City), DATABASE::Vendor)); + Vendor.Validate("Post Code", LibraryUtility.GenerateRandomCode(Vendor.FieldNo("Post Code"), DATABASE::Vendor)); + Vendor.Validate("VAT Bus. Posting Group", VATPostingSetup."VAT Bus. Posting Group"); + Vendor."VAT Registration No." := LibraryERM.GenerateVATRegistrationNo(CountryRegion.Code); + Vendor."Receive E-Document To" := Enum::"E-Document Type"::"Purchase Invoice"; + Vendor.Validate(GLN, '1234567890128'); + Vendor.Modify(true); + + // Create Item + if StandardItem."No." = '' then begin + VATPostingSetup.TestField("VAT Prod. Posting Group"); + CreateGenericItem(StandardItem, VATPostingSetup."VAT Prod. Posting Group"); + end; + + UnitOfMeasureCode := '01'; + UnitOfMeasure.Init(); + UnitOfMeasure."International Standard Code" := UnitOfMeasureCode; + UnitOfMeasure.Code := UnitOfMeasureCode; + if UnitOfMeasure.Insert() then; + + CreateItemUnitOfMeasure(StandardItem."No.", UnitOfMeasure.Code); + LibraryItemReference.CreateItemReference(ItemReference, StandardItem."No.", '', UnitOfMeasure.Code, Enum::"Item Reference Type"::Vendor, Vendor."No.", '1000'); + + CreateGenericItem(ExtraItem, VATPostingSetup."VAT Prod. Posting Group"); + CreateItemUnitOfMeasure(ExtraItem."No.", UnitOfMeasure.Code); + LibraryItemReference.CreateItemReference(ItemReference, ExtraItem."No.", '', UnitOfMeasure.Code, Enum::"Item Reference Type"::Vendor, Vendor."No.", '2000'); + end; + + + procedure CreateInboundEDocument(var EDocument: Record "E-Document"; EDocService: Record "E-Document Service") + var + EDocumentServiceStatus: Record "E-Document Service Status"; + begin + EDocument.Insert(); + EDocumentServiceStatus."E-Document Entry No" := EDocument."Entry No"; + EDocumentServiceStatus."E-Document Service Code" := EDocService.Code; + EDocumentServiceStatus.Insert(); + end; + + procedure MockPurchaseDraftPrepared(EDocument: Record "E-Document") + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentProcessing: Codeunit "E-Document Processing"; + begin + EDocumentPurchaseHeader.InsertForEDocument(EDocument); + EDocumentPurchaseHeader."Sub Total" := 1000; + EDocumentPurchaseHeader."Total VAT" := 100; + EDocumentPurchaseHeader.Total := 1100; + EDocumentPurchaseHeader.Modify(); + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Draft Ready"); + EDocument."Document Type" := "E-Document Type"::"Purchase Invoice"; + EDocument.Modify(); + end; + + procedure CreateInboundPEPPOLDocumentToState(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; FileName: Text; EDocImportParams: Record "E-Doc. Import Parameters"): Boolean + var + EDocImport: Codeunit "E-Doc. Import"; + InStream: InStream; + begin + NavApp.GetResource(FileName, InStream, TextEncoding::UTF8); + EDocImport.CreateFromType(EDocument, EDocumentService, Enum::"E-Doc. File Format"::XML, 'TestFile', InStream); + exit(EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams)); + end; + + /// + /// Given a purchase header with purchase lines created from an e-document, it modifies the required fields to make it ready for posting. + /// + procedure EditPurchaseDocumentFromEDocumentForPosting(var PurchaseHeader: Record "Purchase Header"; var EDocument: Record "E-Document") + var + PurchaseLine: Record "Purchase Line"; + GLAccount: Record "G/L Account"; + GeneralPostingSetup: Record "General Posting Setup"; + GenBusinessPostingGroup: Record "Gen. Business Posting Group"; + GenProductPostingGroup: Record "Gen. Product Posting Group"; + VATBusinessPostingGroup: Record "VAT Business Posting Group"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LocalVATPostingSetup: Record "VAT Posting Setup"; + begin + Assert.AreEqual(EDocument.SystemId, PurchaseHeader."E-Document Link", 'The purchase header has no link to the e-document.'); + LibraryERM.CreateGenBusPostingGroup(GenBusinessPostingGroup); + LibraryERM.CreateGenProdPostingGroup(GenProductPostingGroup); + LibraryERM.CreateGeneralPostingSetup(GeneralPostingSetup, GenBusinessPostingGroup.Code, GenProductPostingGroup.Code); + LibraryERM.CreateVATBusinessPostingGroup(VATBusinessPostingGroup); + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + LibraryERM.CreateVATPostingSetup(LocalVATPostingSetup, VATBusinessPostingGroup.Code, VATProductPostingGroup.Code); + LibraryERM.CreateGLAccount(GLAccount); + LocalVATPostingSetup."Purchase VAT Account" := GLAccount."No."; + LocalVATPostingSetup.Modify(); + LibraryERM.CreateGLAccount(GLAccount); + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseLine.FindSet(); + repeat + PurchaseLine.Type := PurchaseLine.Type::"G/L Account"; + PurchaseLine."No." := GLAccount."No."; + PurchaseLine."Gen. Bus. Posting Group" := GenBusinessPostingGroup.Code; + PurchaseLine."Gen. Prod. Posting Group" := GenProductPostingGroup.Code; + PurchaseLine."VAT Bus. Posting Group" := VATBusinessPostingGroup.Code; + PurchaseLine."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + PurchaseLine.UpdateAmounts(); + PurchaseLine.Modify(); + until PurchaseLine.Next() = 0; + PurchaseHeader.CalcFields("Amount Including VAT"); + EDocument."Amount Incl. VAT" := PurchaseHeader."Amount Including VAT"; + EDocument.Modify(); + end; + + procedure CreateDocSendingProfile(var DocumentSendingProfile: Record "Document Sending Profile") + begin + DocumentSendingProfile.Init(); + DocumentSendingProfile.Code := LibraryUtility.GenerateRandomCode(DocumentSendingProfile.FieldNo(Code), DATABASE::"Document Sending Profile"); + DocumentSendingProfile.Insert(); + end; + + + procedure CreateSimpleFlow(DocSendingProfileCode: Code[20]; ServiceCode: Code[20]): Code[20] + var + Workflow: Record Workflow; + WorkflowStepResponse: Record "Workflow Step"; + WorkflowStepArgument: Record "Workflow Step Argument"; + WorkflowStep: Record "Workflow Step"; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID, SendEDocResponseEventID : Integer; + begin + // Create a simple workflow + // Send to Service 'ServiceCode' when using Document Sending Profile 'DocSendingProfile' + WorkflowStep.SetRange("Function Name", EDocWorkflowSetup.EDocCreated()); + WorkflowStep.SetRange("Entry Point", true); + if WorkflowStep.FindSet() then + repeat + Workflow.Get(WorkflowStep."Workflow Code"); + if not Workflow.Template then + exit; + until WorkflowStep.Next() = 0; + + LibraryWorkflow.CreateWorkflow(Workflow); + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + SendEDocResponseEventID := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventID); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + + WorkflowStepArgument."E-Document Service" := ServiceCode; + WorkflowStepArgument.Modify(); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + procedure CreateCustomerNoWithEDocSendingProfile(var DocumentSendingProfile: Code[20]): Code[20] + var + CustomerNo: Code[20]; + begin + CustomerNo := LibrarySales.CreateCustomerNo(); + DocumentSendingProfile := CreateDocumentSendingProfileForWorkflow(CustomerNo, ''); + exit(CustomerNo); + end; + + procedure CreateEDocumentFromSales(var EDocument: Record "E-Document") + begin + CreateEDocumentFromSales(EDocument, LibrarySales.CreateCustomerNo()); + end; + + procedure CreateEDocumentFromSales(var EDocument: Record "E-Document"; CustomerNo: Code[20]) + var + SalesInvHeader: Record "Sales Invoice Header"; + SalesHeader: Record "Sales Header"; + begin + LibrarySales.CreateSalesInvoiceForCustomerNo(SalesHeader, CustomerNo); + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + EDocument.FindLast(); + end; + + local procedure CreateGenericSalesHeader(var Cust: Record Customer; var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type") + begin + LibrarySales.CreateSalesHeader(SalesHeader, DocumentType, Cust."No."); + SalesHeader.Validate("Your Reference", LibraryUtility.GenerateRandomCode(SalesHeader.FieldNo("Your Reference"), DATABASE::"Sales Header")); + + if DocumentType = SalesHeader."Document Type"::"Credit Memo" then + SalesHeader.Validate("Shipment Date", WorkDate()); + + SalesHeader.Modify(true); + end; + + procedure CreateGenericItem(var Item: Record Item; VATProdPostingGroupCode: Code[20]) + begin + CreateGenericItem(Item); + Item."VAT Prod. Posting Group" := VATPostingSetup."VAT Prod. Posting Group"; + Item.Modify(); + end; + + procedure CreateGenericItem(var Item: Record Item) + var + UOM: Record "Unit of Measure"; + ItemUOM: Record "Item Unit of Measure"; + QtyPerUnit: Integer; + begin + QtyPerUnit := LibraryRandom.RandInt(10); + + LibraryInvt.CreateUnitOfMeasureCode(UOM); + UOM.Validate("International Standard Code", + LibraryUtility.GenerateRandomCode(UOM.FieldNo("International Standard Code"), DATABASE::"Unit of Measure")); + UOM.Modify(true); + + CreateItemWithPrice(Item, LibraryRandom.RandInt(10)); + + LibraryInvt.CreateItemUnitOfMeasure(ItemUOM, Item."No.", UOM.Code, QtyPerUnit); + + Item.Validate("Sales Unit of Measure", UOM.Code); + Item.Modify(true); + end; + + local procedure CreateItemWithPrice(var Item: Record Item; UnitPrice: Decimal) + begin + LibraryInvt.CreateItem(Item); + Item."Unit Price" := UnitPrice; + Item.Modify(); + end; + + procedure SetupCompanyInfo() + var + CompanyInfo: Record "Company Information"; + CountryRegion: Record "Country/Region"; + begin + LibraryERM.FindCountryRegion(CountryRegion); + + CompanyInfo.Get(); + CompanyInfo.Validate(IBAN, 'GB33BUKB20201555555555'); + CompanyInfo.Validate("SWIFT Code", 'MIDLGB22Z0K'); + CompanyInfo.Validate("Bank Branch No.", '1234'); + CompanyInfo.Validate(Address, CopyStr(LibraryUtility.GenerateRandomXMLText(MaxStrLen(CompanyInfo.Address)), 1, MaxStrLen(CompanyInfo.Address))); + CompanyInfo.Validate("Post Code", CopyStr(LibraryUtility.GenerateRandomXMLText(MaxStrLen(CompanyInfo."Post Code")), 1, MaxStrLen(CompanyInfo."Post Code"))); + CompanyInfo.Validate("City", CopyStr(LibraryUtility.GenerateRandomXMLText(MaxStrLen(CompanyInfo."City")), 1, MaxStrLen(CompanyInfo."Post Code"))); + CompanyInfo."Country/Region Code" := CountryRegion.Code; + + if CompanyInfo."VAT Registration No." = '' then + CompanyInfo."VAT Registration No." := LibraryERM.GenerateVATRegistrationNo(CompanyInfo."Country/Region Code"); + + CompanyInfo.Modify(true); + end; + + procedure CreateSalesHeaderWithItem(Customer: Record Customer; var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type") + var + SalesLine: Record "Sales Line"; + begin + CreateGenericSalesHeader(Customer, SalesHeader, DocumentType); + + if StandardItem."No." = '' then + CreateGenericItem(StandardItem); + + LibrarySales.CreateSalesLine(SalesLine, SalesHeader, SalesLine.Type::Item, StandardItem."No.", 1); + end; + + procedure CreatePurchaseOrderWithLine(var Vendor: Record Vendor; var PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; Quantity: Decimal) + begin + LibraryPurchase.CreatePurchHeader(PurchaseHeader, Enum::"Purchase Document Type"::Order, Vendor."No."); + if StandardItem."No." = '' then + CreateGenericItem(StandardItem); + LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, StandardItem."No.", Quantity); + end; + + procedure PostSalesDocument(var SalesHeader: Record "Sales Header"; var SalesInvHeader: Record "Sales Invoice Header") + begin + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + end; + + procedure PostSalesDocument(var SalesHeader: Record "Sales Header"; var SalesInvHeader: Record "Sales Invoice Header"; Ship: Boolean) + begin + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, Ship, true)); + end; + + procedure CreateReminderWithLine(Customer: Record Customer; var ReminderHeader: Record "Reminder Header") + var + ReminderLine: Record "Reminder Line"; + begin + LibraryERM.CreateReminderHeader(ReminderHeader); + ReminderHeader.Validate("Customer No.", Customer."No."); + ReminderHeader."Your Reference" := LibraryRandom.RandText(35); + ReminderHeader.Modify(false); + + LibraryERM.CreateReminderLine(ReminderLine, ReminderHeader."No.", Enum::"Reminder Source Type"::"G/L Account"); + ReminderLine.Validate("Remaining Amount", this.LibraryRandom.RandInt(100)); + ReminderLine.Description := LibraryRandom.RandText(100); + ReminderLine.Modify(false); + end; + + procedure CreateFinChargeMemoWithLine(Customer: Record Customer; var FinChargeMemoHeader: Record "Finance Charge Memo Header") + var + FinChargeMemoLine: Record "Finance Charge Memo Line"; + FinanceChargeTerms: Record "Finance Charge Terms"; + begin + LibraryERM.CreateFinanceChargeMemoHeader(FinChargeMemoHeader, Customer."No."); + LibraryFinChargeMemo.CreateFinanceChargeTermAndText(FinanceChargeTerms); + FinChargeMemoHeader.Validate("Fin. Charge Terms Code", FinanceChargeTerms.Code); + FinChargeMemoHeader."Your Reference" := LibraryRandom.RandText(35); + FinChargeMemoHeader.Modify(false); + + LibraryERM.CreateFinanceChargeMemoLine(FinChargeMemoLine, FinChargeMemoHeader."No.", FinChargeMemoLine.Type::"G/L Account"); + FinChargeMemoLine.Validate("Remaining Amount", this.LibraryRandom.RandInt(100)); + FinChargeMemoLine.Description := LibraryRandom.RandText(100); + FinChargeMemoLine.Modify(false); + end; + + procedure IssueReminder(Customer: Record Customer) IssuedReminderHeader: Record "Issued Reminder Header" + var + ReminderHeader: Record "Reminder Header"; + ReminderIssue: Codeunit "Reminder-Issue"; + begin + CreateReminderWithLine(Customer, ReminderHeader); + + ReminderHeader.SetRange("No.", ReminderHeader."No."); + ReminderIssue.Set(ReminderHeader, false, 0D); + ReminderIssue.Run(); + + ReminderIssue.GetIssuedReminder(IssuedReminderHeader); + end; + + procedure IssueFinChargeMemo(Customer: Record Customer) IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header" + var + FinChargeMemoHeader: Record "Finance Charge Memo Header"; + FinChargeMemoIssue: Codeunit "FinChrgMemo-Issue"; + begin + CreateFinChargeMemoWithLine(Customer, FinChargeMemoHeader); + + FinChargeMemoHeader.SetRange("No.", FinChargeMemoHeader."No."); + FinChargeMemoIssue.Set(FinChargeMemoHeader, false, 0D); + FinChargeMemoIssue.Run(); + + FinChargeMemoIssue.GetIssuedFinChrgMemo(IssuedFinChargeMemoHeader); + end; + + procedure SetupReminderNoSeries() + var + SalesSetup: Record "Sales & Receivables Setup"; + begin + SalesSetup.Get(); + SalesSetup.Validate("Reminder Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Validate("Issued Reminder Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Modify(false); + end; + + procedure SetupFinChargeMemoNoSeries() + var + SalesSetup: Record "Sales & Receivables Setup"; + begin + SalesSetup.Get(); + SalesSetup.Validate("Fin. Chrg. Memo Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Validate("Issued Fin. Chrg. M. Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Modify(false); + end; + + procedure Initialize() + var + DocumentSendingProfile: Record "Document Sending Profile"; + EDocService: Record "E-Document Service"; + // EDocMappingTestRec: Record "E-Doc. Mapping Test Rec"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocServiceSupportedType: Record "E-Doc. Service Supported Type"; + EDocMapping: Record "E-Doc. Mapping"; + EDocLogs: Record "E-Document Log"; + EDocMappingLogs: Record "E-Doc. Mapping Log"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocument: Record "E-Document"; + WorkflowSetup: Codeunit "Workflow Setup"; + begin + LibraryWorkflow.DeleteAllExistingWorkflows(); + WorkflowSetup.InitWorkflow(); + DocumentSendingProfile.DeleteAll(); + EDocService.DeleteAll(); + EDocServiceSupportedType.DeleteAll(); + EDocument.DeleteAll(); + EDocServiceStatus.DeleteAll(); + EDocDataStorage.DeleteAll(); + EDocMapping.DeleteAll(); + EDocLogs.DeleteAll(); + EDocMappingLogs.DeleteAll(); + // EDocMappingTestRec.DeleteAll(); + Commit(); + end; + + procedure PostSalesDocument(CustomerNo: Code[20]): Code[20] + var + SalesInvHeader: Record "Sales Invoice Header"; + SalesHeader: Record "Sales Header"; + begin + LibrarySales.CreateSalesInvoiceForCustomerNo(SalesHeader, CustomerNo); + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + exit(SalesInvHeader."No."); + end; + + procedure PostSalesDocument(): Code[20] + begin + PostSalesDocument(''); + end; + + procedure CreateDocumentSendingProfileForWorkflow(CustomerNo: Code[20]; WorkflowCode: Code[20]): Code[20] + var + Customer: Record Customer; + DocumentSendingProfile: Record "Document Sending Profile"; + begin + DocumentSendingProfile.Init(); + DocumentSendingProfile.Code := LibraryUtility.GenerateRandomCode20(DocumentSendingProfile.FieldNo(Code), Database::"Document Sending Profile"); + DocumentSendingProfile."Electronic Document" := Enum::"Doc. Sending Profile Elec.Doc."::"Extended E-Document Service Flow"; + DocumentSendingProfile."Electronic Service Flow" := WorkflowCode; + DocumentSendingProfile.Insert(); + + Customer.Get(CustomerNo); + Customer.Validate("Document Sending Profile", DocumentSendingProfile.Code); + Customer.Modify(); + exit(DocumentSendingProfile.Code); + end; + + procedure UpdateWorkflowOnDocumentSendingProfile(DocSendingProfile: Code[20]; WorkflowCode: Code[20]) + var + DocumentSendingProfile: Record "Document Sending Profile"; + begin + DocumentSendingProfile.Get(DocSendingProfile); + DocumentSendingProfile.Validate("Electronic Service Flow", WorkflowCode); + DocumentSendingProfile.Modify(); + end; + + procedure CreateFlowWithService(DocSendingProfile: Code[20]; ServiceCode: Code[20]): Code[20] + var + Workflow: Record Workflow; + WorkflowStepResponse: Record "Workflow Step"; + WorkflowStepArgument: Record "Workflow Step Argument"; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID, SendEDocResponseEventID : Integer; + EventConditions: Text; + begin + LibraryWorkflow.CreateWorkflow(Workflow); + EventConditions := CreateWorkflowEventConditionDocSendingProfileFilter(DocSendingProfile); + + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + LibraryWorkflow.InsertEventArgument(EDocCreatedEventID, EventConditions); + SendEDocResponseEventID := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventID); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + + WorkflowStepArgument.Validate("E-Document Service", ServiceCode); + WorkflowStepArgument.Modify(); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + procedure CreateEmptyFlow(): Code[20] + var + Workflow: Record Workflow; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID: Integer; + begin + LibraryWorkflow.CreateWorkflow(Workflow); + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + procedure CreateFlowWithServices(DocSendingProfile: Code[20]; ServiceCodeA: Code[20]; ServiceCodeB: Code[20]): Code[20] + var + Workflow: Record Workflow; + WorkflowStepResponse: Record "Workflow Step"; + WorkflowStepArgument: Record "Workflow Step Argument"; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID, SendEDocResponseEventIDA, SendEDocResponseEventIDB : Integer; + EventConditionsDocProfile, EventConditionsService : Text; + begin + LibraryWorkflow.CreateWorkflow(Workflow); + EventConditionsDocProfile := CreateWorkflowEventConditionDocSendingProfileFilter(DocSendingProfile); + EventConditionsService := CreateWorkflowEventConditionServiceFilter(ServiceCodeA); + + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + LibraryWorkflow.InsertEventArgument(EDocCreatedEventID, EventConditionsDocProfile); + SendEDocResponseEventIDA := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + SendEDocResponseEventIDB := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), SendEDocResponseEventIDA); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventIDA); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + WorkflowStepArgument."E-Document Service" := ServiceCodeA; + WorkflowStepArgument.Modify(); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventIDB); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + WorkflowStepArgument."E-Document Service" := ServiceCodeB; + WorkflowStepArgument.Modify(); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + local procedure DeleteEDocumentRelatedEntities() + var + DynamicRequestPageEntity: Record "Dynamic Request Page Entity"; + begin + DynamicRequestPageEntity.SetRange("Table ID", DATABASE::"E-Document"); + DynamicRequestPageEntity.DeleteAll(true); + end; + + local procedure CreateWorkflowEventConditionDocSendingProfileFilter(DocSendingProfile: Code[20]): Text + var + RequestPageParametersHelper: Codeunit "Request Page Parameters Helper"; + FilterPageBuilder: FilterPageBuilder; + EntityName: Code[20]; + begin + EntityName := CreateDynamicRequestPageEntity(DATABASE::"E-Document", Database::"Document Sending Profile"); + CreateEDocumentDocSendingProfileDataItem(FilterPageBuilder, DocSendingProfile); + exit(RequestPageParametersHelper.GetViewFromDynamicRequestPage(FilterPageBuilder, EntityName, Database::"E-Document")); + end; + + local procedure CreateWorkflowEventConditionServiceFilter(ServiceCode: Code[20]): Text + var + RequestPageParametersHelper: Codeunit "Request Page Parameters Helper"; + FilterPageBuilder: FilterPageBuilder; + EntityName: Code[20]; + begin + EntityName := CreateDynamicRequestPageEntity(DATABASE::"E-Document", Database::"E-Document Service"); + CreateEDocServiceDataItem(FilterPageBuilder, ServiceCode); + exit(RequestPageParametersHelper.GetViewFromDynamicRequestPage(FilterPageBuilder, EntityName, Database::"E-Document Service")); + end; + + local procedure CreateEDocumentDocSendingProfileDataItem(var FilterPageBuilder: FilterPageBuilder; DocumentSendingProfile: Code[20]) + var + EDocument: Record "E-Document"; + EDocumentDataItem: Text; + begin + EDocumentDataItem := FilterPageBuilder.AddTable(EDocument.TableCaption, DATABASE::"E-Document"); + FilterPageBuilder.AddField(EDocumentDataItem, EDocument."Document Sending Profile", DocumentSendingProfile); + end; + + local procedure CreateEDocServiceDataItem(var FilterPageBuilder: FilterPageBuilder; ServiceCode: Code[20]) + var + EDocService: Record "E-Document Service"; + EDocumentDataItem: Text; + begin + EDocumentDataItem := FilterPageBuilder.AddTable(EDocService.TableCaption, DATABASE::"E-Document Service"); + FilterPageBuilder.AddField(EDocumentDataItem, EDocService.Code, ServiceCode); + end; + + local procedure CreateDynamicRequestPageEntity(TableID: Integer; RelatedTable: Integer): Code[20] + var + EntityName: Code[20]; + begin + DeleteEDocumentRelatedEntities(); + EntityName := LibraryUtility.GenerateGUID(); + LibraryWorkflow.CreateDynamicRequestPageEntity(EntityName, TableID, RelatedTable); + exit(EntityName); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure CreateService(Integration: Enum "E-Document Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; +#pragma warning restore AL0432 +#endif + + procedure CreateService(Integration: Enum "Service Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "E-Document Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := EDocDocumentFormat; + EDocService."Service Integration" := EDocIntegration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; +#pragma warning restore AL0432 +#endif + + procedure CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := EDocDocumentFormat; + EDocService."Service Integration V2" := EDocIntegration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; + + + procedure CreateServiceMapping(EDocService: Record "E-Document Service") + var + TransformationRule: Record "Transformation Rule"; + EDocMapping: Record "E-Doc. Mapping"; + SalesInvHeader: Record "Sales Invoice Header"; + begin + TransformationRule.Get(TransformationRule.GetLowercaseCode()); + // Lower case mapping + CreateTransformationMapping(EDocMapping, TransformationRule, EDocService.Code); + EDocMapping."Table ID" := Database::"Sales Invoice Header"; + EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Name"); + EDocMapping.Modify(); + CreateTransformationMapping(EDocMapping, TransformationRule, EDocService.Code); + EDocMapping."Table ID" := Database::"Sales Invoice Header"; + EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Address"); + EDocMapping.Modify(); + end; + + procedure DeleteServiceMapping(EDocService: Record "E-Document Service") + var + EDocMapping: Record "E-Doc. Mapping"; + begin + EDocMapping.SetRange(Code, EDocService.Code); + EDocMapping.DeleteAll(); + end; + + procedure CreateSupportedDocTypes(EDocService: Record "E-Document Service") + var + EDocServiceSupportedType: Record "E-Doc. Service Supported Type"; + begin + EDocServiceSupportedType.Init(); + EDocServiceSupportedType."E-Document Service Code" := EDocService.Code; + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Sales Invoice"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Sales Credit Memo"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Service Invoice"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Service Credit Memo"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Issued Finance Charge Memo"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Issued Reminder"; + EDocServiceSupportedType.Insert(); + end; + + procedure AddEDocServiceSupportedType(EDocService: Record "E-Document Service"; EDocumentType: Enum "E-Document Type") + var + EDocServiceSupportedType: Record "E-Doc. Service Supported Type"; + begin + if not EDocService.Get(EDocService.Code) then + exit; + + EDocServiceSupportedType.Init(); + EDocServiceSupportedType."E-Document Service Code" := EDocService.Code; + EDocServiceSupportedType."Source Document Type" := EDocumentType; + if EDocServiceSupportedType.Insert() then; + end; + + procedure CreateTestReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") + begin + if not EDocService.Get('TESTRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'TESTRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + end; + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateTestReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") instead', '26.0')] + procedure CreateTestReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "E-Document Integration") + begin + if not EDocService.Get('TESTRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'TESTRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + end; + end; +#pragma warning restore AL0432 +#endif + + procedure CreateGetBasicInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") + begin + if not EDocService.Get('BIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'BIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + end; + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateGetBasicInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") instead', '26.0')] + procedure CreateGetBasicInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "E-Document Integration") + begin + if not EDocService.Get('BIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'BIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + end; + end; +#pragma warning restore AL0432 +#endif + + procedure CreateGetCompleteInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") + begin + if not EDocService.Get('CIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'CIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + end; + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateGetCompleteInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") instead', '26.0')] + procedure CreateGetCompleteInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "E-Document Integration") + begin + if not EDocService.Get('CIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'CIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + end; + end; +#pragma warning restore AL0432 +#endif + + procedure CreateDirectMapping(var EDocMapping: Record "E-Doc. Mapping"; EDocService: Record "E-Document Service"; FindValue: Text; ReplaceValue: Text) + begin + CreateDirectMapping(EDocMapping, EDocService, FindValue, ReplaceValue, 0, 0); + end; + + procedure CreateTransformationMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule") + begin + CreateTransformationMapping(EDocMapping, TransformationRule, ''); + end; + + procedure CreateTransformationMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule"; ServiceCode: Code[20]) + begin + EDocMapping.Init(); + EDocMapping.Code := ServiceCode; + EDocMapping."Entry No." := 0; + EDocMapping."Transformation Rule" := TransformationRule.Code; + EDocMapping.Insert(); + end; + + procedure CreateDirectMapping(var EDocMapping: Record "E-Doc. Mapping"; EDocService: Record "E-Document Service"; FindValue: Text; ReplaceValue: Text; TableId: Integer; FieldId: Integer) + begin + EDocMapping.Init(); + EDocMapping."Entry No." := 0; + EDocMapping.Code := EDocService.Code; + EDocMapping."Table ID" := TableId; + EDocMapping."Field ID" := FieldId; + EDocMapping."Find Value" := CopyStr(FindValue, 1, LibraryUtility.GetFieldLength(DATABASE::"E-Doc. Mapping", EDocMapping.FieldNo("Find Value"))); + EDocMapping."Replace Value" := CopyStr(ReplaceValue, 1, LibraryUtility.GetFieldLength(DATABASE::"E-Doc. Mapping", EDocMapping.FieldNo("Replace Value"))); + EDocMapping.Insert(); + end; + + local procedure CreateItemUnitOfMeasure(ItemNo: Code[20]; UnitOfMeasureCode: Code[10]) + var + ItemUnitOfMeasure: Record "Item Unit of Measure"; + begin + ItemUnitOfMeasure.Init(); + ItemUnitOfMeasure.Validate("Item No.", ItemNo); + ItemUnitOfMeasure.Validate(Code, UnitOfMeasureCode); + ItemUnitOfMeasure."Qty. per Unit of Measure" := 1; + if ItemUnitOfMeasure.Insert() then; + end; + + procedure TempBlobToTxt(var TempBlob: Codeunit "Temp Blob"): Text + var + InStr: InStream; + Content: Text; + begin + TempBlob.CreateInStream(InStr); + InStr.Read(Content); + exit(Content); + end; + + internal procedure CreateLocationsWithPostingSetups( + var FromLocation: Record Location; + var ToLocation: Record Location; + var InTransitLocation: Record Location; + var InventoryPostingGroup: Record "Inventory Posting Group") + var + InventoryPostingSetupFromLocation: Record "Inventory Posting Setup"; + InventoryPostingSetupToLocation: Record "Inventory Posting Setup"; + InventoryPostingSetupInTransitLocation: Record "Inventory Posting Setup"; + LibraryWarehouse: Codeunit "Library - Warehouse"; + begin + LibraryWarehouse.CreateLocation(FromLocation); + LibraryWarehouse.CreateLocation(ToLocation); + LibraryWarehouse.CreateInTransitLocation(InTransitLocation); + + LibraryInventory.CreateInventoryPostingGroup(InventoryPostingGroup); + LibraryInventory.CreateInventoryPostingSetup(InventoryPostingSetupFromLocation, FromLocation.Code, InventoryPostingGroup.Code); + LibraryInventory.UpdateInventoryPostingSetup(FromLocation, InventoryPostingGroup.Code); + + InventoryPostingSetupFromLocation.Get(FromLocation.Code, InventoryPostingGroup.Code); + InventoryPostingSetupToLocation := InventoryPostingSetupFromLocation; + InventoryPostingSetupToLocation."Location Code" := ToLocation.Code; + InventoryPostingSetupToLocation.Insert(false); + + InventoryPostingSetupInTransitLocation := InventoryPostingSetupFromLocation; + InventoryPostingSetupInTransitLocation."Location Code" := InTransitLocation.Code; + InventoryPostingSetupInTransitLocation.Insert(false); + end; + + internal procedure CreateItemWithInventoryPostingGroup(var Item: Record Item; InventoryPostingGroupCode: Code[20]) + begin + LibraryInventory.CreateItem(Item); + Item."Inventory Posting Group" := InventoryPostingGroupCode; + Item.Modify(false); + end; + + internal procedure CreateItemWIthInventoryStock(var Item: Record Item; var FromLocation: Record Location; var InventoryPostingGroup: Record "Inventory Posting Group") + var + ItemJournalLine: Record "Item Journal Line"; + begin + CreateItemWithInventoryPostingGroup(Item, InventoryPostingGroup.Code); + LibraryInventory.CreateItemJournalLineInItemTemplate( + ItemJournalLine, Item."No.", FromLocation.Code, '', LibraryRandom.RandIntInRange(10, 20)); + LibraryInventory.PostItemJournalLine( + ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name"); + end; + + // Verify procedures + + procedure AssertEDocumentLogs(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocLogList: List of [Enum "E-Document Service Status"]) + var + EDocLog: Record "E-Document Log"; + Count: Integer; + begin + EDocLog.SetRange("E-Doc. Entry No", EDocument."Entry No"); + EDocLog.SetRange("Service Code", EDocumentService.Code); + Assert.AreEqual(EDocLogList.Count(), EDocLog.Count(), 'Wrong number of logs'); + Count := 1; + EDocLog.SetCurrentKey("Entry No."); + EDocLog.SetAscending("Entry No.", true); + if EDocLog.FindSet() then + repeat + Assert.AreEqual(EDocLogList.Get(Count), EDocLog.Status, 'Wrong status'); + Count := Count + 1; + until EDocLog.Next() = 0; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Export", 'OnAfterCreateEDocument', '', false, false)] + local procedure OnAfterCreateEDocument(var EDocument: Record "E-Document") + begin + LibraryVariableStorage.Enqueue(EDocument); + end; + +} \ No newline at end of file diff --git a/Apps/W1/EDocument/app/app.json b/Apps/W1/EDocument/app/app.json index 15e978da0d..d804e13b85 100644 --- a/Apps/W1/EDocument/app/app.json +++ b/Apps/W1/EDocument/app/app.json @@ -42,6 +42,11 @@ "id": "8238f99b-cbe5-4b9c-b247-7771145b7470", "name": "E-Document Format for Factura-E", "publisher": "Microsoft" + }, + { + "id": "8238f78b-cbe5-4b9c-b247-7771145b7470", + "name": "E-Document Format for Factura-E Tests", + "publisher": "Microsoft" } ], "screenshots": [], From 53cb6c97a2c1f28cb4e88fb3bd3bcf413d9cdb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 27 Aug 2025 11:30:58 +0300 Subject: [PATCH 14/16] missing codeunit added --- .../src/EDocumentStructuredTests.Codeunit.al | 268 ++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al new file mode 100644 index 0000000000..be8afb8b29 --- /dev/null +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al @@ -0,0 +1,268 @@ +codeunit 148002 "E-Document Structured Tests" +{ + Subtype = Test; + TestType = IntegrationTest; + + var + Customer: Record Customer; + Vendor: Record Vendor; + EDocumentService: Record "E-Document Service"; + Assert: Codeunit Assert; + LibraryVariableStorage: Codeunit "Library - Variable Storage"; + LibraryEDoc: Codeunit "Library - E-Document"; + // EDocImplState: Codeunit "E-Doc. Impl. State"; + LibraryLowerPermission: Codeunit "Library - Lower Permissions"; + FacturaEStructValidations: Codeunit "Factura-E Struct. Validations"; + IsInitialized: Boolean; + EDocumentStatusNotUpdatedErr: Label 'The status of the EDocument was not updated to the expected status after the step was executed.'; + MockCurrencyCode: Code[10]; + MockDate: Date; + + #region FacturaE 3.0 XML + [Test] + procedure TestFacturaEInvoice_ValidDocument() + var + EDocument: Record "E-Document"; + begin + Initialize(Enum::"Service Integration"::"No Integration"); + SetupFacturaEEDocumentService(); + CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + if ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft") then begin + FacturaEStructValidations.SetMockCurrencyCode(MockCurrencyCode); + FacturaEStructValidations.SetMockDate(MockDate); + FacturaEStructValidations.AssertFullEDocumentContentExtracted(EDocument."Entry No"); + end + else + Assert.Fail(EDocumentStatusNotUpdatedErr); + end; + + [Test] + [HandlerFunctions('EDocumentPurchaseHeaderPageHandler')] + procedure TestFacturaEInvoice_ValidDocument_ViewExtractedData() + var + EDocument: Record "E-Document"; + EDocImport: Codeunit "E-Doc. Import"; + begin + // [FEATURE] [E-Document] [FacturaE] [View Data] + // [SCENARIO] View extracted data from a valid FacturaE invoice document + + // [GIVEN] A valid FacturaE XML invoice document is imported + Initialize(Enum::"Service Integration"::"No Integration"); + SetupFacturaEEDocumentService(); + CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + + // [WHEN] The document is processed to draft status + ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft"); + EDocument.Get(EDocument."Entry No"); + + // [WHEN] View extracted data is called + EDocImport.ViewExtractedData(EDocument); + + // [THEN] The extracted data page opens and can be handled properly (verified by page handler) + // EDocumentPurchaseHeaderPageHandler + end; + + [Test] + procedure TestFacturaEInvoice_ValidDocument_PurchaseInvoiceCreated() + var + EDocument: Record "E-Document"; + PurchaseHeader: Record "Purchase Header"; + DummyItem: Record Item; + EDocumentProcessing: Codeunit "E-Document Processing"; + DataTypeManagement: Codeunit "Data Type Management"; + RecRef: RecordRef; + VariantRecord: Variant; + begin + // [FEATURE] [E-Document] [FacturaE] [Purchase Invoice Creation] + // [SCENARIO] Create a purchase invoice from a valid FacturaE invoice document + + // [GIVEN] A valid FacturaE XML invoice document is imported + Initialize(Enum::"Service Integration"::"No Integration"); + Vendor."VAT Registration No." := 'GB123456789'; + Vendor.Modify(true); + + SetupFacturaEEDocumentService(); + CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + + // [WHEN] The document is processed through finish draft step + ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Finish draft"); + EDocument.Get(EDocument."Entry No"); + + // [WHEN] The created purchase record is retrieved + EDocumentProcessing.GetRecord(EDocument, VariantRecord); + DataTypeManagement.GetRecordRef(VariantRecord, RecRef); + RecRef.SetTable(PurchaseHeader); + + // [THEN] The purchase header is correctly created with FacturaE data + FacturaEStructValidations.SetMockCurrencyCode(MockCurrencyCode); + FacturaEStructValidations.SetMockDate(MockDate); + FacturaEStructValidations.AssertPurchaseDocument(Vendor."No.", PurchaseHeader, DummyItem); + end; + + [Test] + procedure TestFacturaEInvoice_ValidDocument_UpdateDraftAndFinalize() + var + EDocument: Record "E-Document"; + PurchaseHeader: Record "Purchase Header"; + Item: Record Item; + EDocImportParameters: Record "E-Doc. Import Parameters"; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentProcessing: Codeunit "E-Document Processing"; + DataTypeManagement: Codeunit "Data Type Management"; + RecRef: RecordRef; + EDocPurchaseDraft: TestPage "E-Document Purchase Draft"; + VariantRecord: Variant; + begin + // [FEATURE] [E-Document] [FacturaE] [Draft Update] + // [SCENARIO] Update draft purchase document data and finalize processing + + // [GIVEN] A valid FacturaE XML invoice document is imported and processed to draft preparation + Initialize(Enum::"Service Integration"::"No Integration"); + Vendor."VAT Registration No." := 'GB123456789'; + Vendor.Modify(true); + SetupFacturaEEDocumentService(); + CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Prepare draft"); + + // [GIVEN] A generic item is created for manual assignment + LibraryEDoc.CreateGenericItem(Item, ''); + + // [WHEN] The draft document is opened and modified through UI + EDocPurchaseDraft.OpenEdit(); + EDocPurchaseDraft.GoToRecord(EDocument); + EDocPurchaseDraft.Lines.First(); + EDocPurchaseDraft.Lines."Line Type".SetValue("Purchase Line Type"::Item); + EDocPurchaseDraft.Lines."No.".SetValue(Item."No."); + EDocPurchaseDraft.Lines.Next(); + + // [WHEN] The processing is completed to finish draft step + EDocImportParameters."Step to Run" := "Import E-Document Steps"::"Finish draft"; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParameters); + EDocument.Get(EDocument."Entry No"); + + // [WHEN] The final purchase record is retrieved + EDocumentProcessing.GetRecord(EDocument, VariantRecord); + DataTypeManagement.GetRecordRef(VariantRecord, RecRef); + RecRef.SetTable(PurchaseHeader); + + // [THEN] The purchase header contains both imported FacturaE data and manual updates + FacturaEStructValidations.SetMockCurrencyCode(MockCurrencyCode); + FacturaEStructValidations.SetMockDate(MockDate); + FacturaEStructValidations.AssertPurchaseDocument(Vendor."No.", PurchaseHeader, Item); + end; + + [PageHandler] + procedure EDocumentPurchaseHeaderPageHandler(var EDocReadablePurchaseDoc: TestPage "E-Doc. Readable Purchase Doc.") + begin + EDocReadablePurchaseDoc.Close(); + end; + #endregion + + local procedure Initialize(Integration: Enum "Service Integration") + var + TransformationRule: Record "Transformation Rule"; + EDocument: Record "E-Document"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocumentsSetup: Record "E-Documents Setup"; + EDocumentServiceStatus: Record "E-Document Service Status"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + DocumentAttachment: Record "Document Attachment"; + Currency: Record Currency; + begin + LibraryLowerPermission.SetOutsideO365Scope(); + LibraryVariableStorage.Clear(); + // Clear(EDocImplState); + Clear(LibraryVariableStorage); + + if IsInitialized then + exit; + + EDocument.DeleteAll(); + EDocumentServiceStatus.DeleteAll(); + EDocumentService.DeleteAll(); + EDocDataStorage.DeleteAll(); + EDocumentPurchaseHeader.DeleteAll(); + EDocumentPurchaseLine.DeleteAll(); + DocumentAttachment.DeleteAll(); + + LibraryEDoc.SetupStandardVAT(); + LibraryEDoc.SetupStandardSalesScenario(Customer, EDocumentService, Enum::"E-Document Format"::"Factura-E 3.2.2", Integration); + LibraryEDoc.SetupStandardPurchaseScenario(Vendor, EDocumentService, Enum::"E-Document Format"::"Factura-E 3.2.2", Integration); + EDocumentService."Import Process" := "E-Document Import Process"::"Version 2.0"; + EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::"Factura-E"; + EDocumentService.Modify(); + EDocumentsSetup.InsertNewExperienceSetup(); + + // Set a currency that can be used across all localizations + MockCurrencyCode := 'XYZ'; + Currency.Init(); + Currency.Validate(Code, MockCurrencyCode); + if Currency.Insert(true) then; + CreateCurrencyExchangeRate(); + + MockDate := DMY2Date(22, 01, 2026); + + TransformationRule.DeleteAll(); + TransformationRule.CreateDefaultTransformations(); + + IsInitialized := true; + end; + + local procedure SetupFacturaEEDocumentService() + begin + EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::"Factura-E"; + EDocumentService.Modify(); + end; + + local procedure CreateInboundEDocumentFromXML(var EDocument: Record "E-Document"; FilePath: Text) + var + EDocLogRecord: Record "E-Document Log"; + EDocumentLog: Codeunit "E-Document Log"; + begin + LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService); + + EDocumentLog.SetBlob('Test', Enum::"E-Doc. File Format"::XML, NavApp.GetResourceAsText(FilePath)); + EDocumentLog.SetFields(EDocument, EDocumentService); + EDocLogRecord := EDocumentLog.InsertLog(Enum::"E-Document Service Status"::Imported, Enum::"Import E-Doc. Proc. Status"::Readable); + + EDocument."Structured Data Entry No." := EDocLogRecord."E-Doc. Data Storage Entry No."; + EDocument.Modify(); + end; + + local procedure ProcessEDocumentToStep(var EDocument: Record "E-Document"; ProcessingStep: Enum "Import E-Document Steps"): Boolean + var + EDocImportParameters: Record "E-Doc. Import Parameters"; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentProcessing: Codeunit "E-Document Processing"; + begin + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::Readable); + EDocImportParameters."Step to Run" := ProcessingStep; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParameters); + EDocument.CalcFields("Import Processing Status"); + + // Update the exit condition to handle different processing steps + case ProcessingStep of + "Import E-Document Steps"::"Read into Draft": + exit(EDocument."Import Processing Status" = Enum::"Import E-Doc. Proc. Status"::"Ready for draft"); + "Import E-Document Steps"::"Finish draft": + exit(EDocument."Import Processing Status" = Enum::"Import E-Doc. Proc. Status"::Processed); + "Import E-Document Steps"::"Prepare draft": + exit(EDocument."Import Processing Status" = Enum::"Import E-Doc. Proc. Status"::"Draft Ready"); + else + exit(EDocument."Import Processing Status" = Enum::"Import E-Doc. Proc. Status"::"Ready for draft"); + end; + end; + + local procedure CreateCurrencyExchangeRate() + var + CurrencyExchangeRate: Record "Currency Exchange Rate"; + begin + CurrencyExchangeRate.Init(); + CurrencyExchangeRate."Currency Code" := MockCurrencyCode; + CurrencyExchangeRate."Starting Date" := WorkDate(); + CurrencyExchangeRate."Exchange Rate Amount" := 10; + CurrencyExchangeRate."Relational Exch. Rate Amount" := 1.23; + CurrencyExchangeRate.Insert(true); + end; +} From 37de8db5e12a8653467bfe0ff266996ecbeb5ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 27 Aug 2025 14:53:06 +0300 Subject: [PATCH 15/16] Optimize FacturaE tests; drop OnPrem target Removes OnPrem target to align tests with SaaS environments. Skips triggers on bulk delete/modify during setup to speed execution and reduce side effects/permission issues, improving test stability. Centralizes the test XML path in a reusable token and adds scenario-style comments for readability, plus minor cleanup. --- .../EDocumentFormats/FacturaE/test/app.json | 1 - .../test/src/EDocFormatMock.Codeunit.al | 2 - .../src/EDocumentStructuredTests.Codeunit.al | 44 +++++++++++-------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/app.json b/Apps/ES/EDocumentFormats/FacturaE/test/app.json index ece447439a..2b9f93435f 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/test/app.json +++ b/Apps/ES/EDocumentFormats/FacturaE/test/app.json @@ -60,7 +60,6 @@ "includeSourceInSymbolFile": true }, "application": "27.0.0.0", - "target": "OnPrem", "resourceFolders": [ ".resources" ] diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al index e5215cf135..1f9914b75c 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocFormatMock.Codeunit.al @@ -51,6 +51,4 @@ codeunit 148004 "E-Doc. Format Mock" implements "E-Document" local procedure OnGetCompleteInfoFromReceivedDocument(var EDocument: Record "E-Document"; var CreatedDocumentHeader: RecordRef; var CreatedDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); begin end; - - } \ No newline at end of file diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al index be8afb8b29..a3934d67e6 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al @@ -10,11 +10,11 @@ codeunit 148002 "E-Document Structured Tests" Assert: Codeunit Assert; LibraryVariableStorage: Codeunit "Library - Variable Storage"; LibraryEDoc: Codeunit "Library - E-Document"; - // EDocImplState: Codeunit "E-Doc. Impl. State"; LibraryLowerPermission: Codeunit "Library - Lower Permissions"; FacturaEStructValidations: Codeunit "Factura-E Struct. Validations"; IsInitialized: Boolean; EDocumentStatusNotUpdatedErr: Label 'The status of the EDocument was not updated to the expected status after the step was executed.'; + TestFileTok: Label 'factura-e/facturae-invoice-0.xml', Locked = true; MockCurrencyCode: Code[10]; MockDate: Date; @@ -24,15 +24,22 @@ codeunit 148002 "E-Document Structured Tests" var EDocument: Record "E-Document"; begin + // [FEATURE] [E-Document] [FacturaE] [Import] + // [SCENARIO] Import and validate a valid FacturaE invoice document + + // [GIVEN] A valid FacturaE XML invoice document Initialize(Enum::"Service Integration"::"No Integration"); SetupFacturaEEDocumentService(); - CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + CreateInboundEDocumentFromXML(EDocument, TestFileTok); + + // [WHEN] The document is processed to read into draft step if ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft") then begin FacturaEStructValidations.SetMockCurrencyCode(MockCurrencyCode); FacturaEStructValidations.SetMockDate(MockDate); + + // [THEN] The document content is fully extracted and validated FacturaEStructValidations.AssertFullEDocumentContentExtracted(EDocument."Entry No"); - end - else + end else Assert.Fail(EDocumentStatusNotUpdatedErr); end; @@ -49,7 +56,7 @@ codeunit 148002 "E-Document Structured Tests" // [GIVEN] A valid FacturaE XML invoice document is imported Initialize(Enum::"Service Integration"::"No Integration"); SetupFacturaEEDocumentService(); - CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + CreateInboundEDocumentFromXML(EDocument, TestFileTok); // [WHEN] The document is processed to draft status ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft"); @@ -82,7 +89,7 @@ codeunit 148002 "E-Document Structured Tests" Vendor.Modify(true); SetupFacturaEEDocumentService(); - CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + CreateInboundEDocumentFromXML(EDocument, TestFileTok); // [WHEN] The document is processed through finish draft step ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Finish draft"); @@ -121,7 +128,7 @@ codeunit 148002 "E-Document Structured Tests" Vendor."VAT Registration No." := 'GB123456789'; Vendor.Modify(true); SetupFacturaEEDocumentService(); - CreateInboundEDocumentFromXML(EDocument, 'factura-e/facturae-invoice-0.xml'); + CreateInboundEDocumentFromXML(EDocument, TestFileTok); ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Prepare draft"); // [GIVEN] A generic item is created for manual assignment @@ -172,26 +179,25 @@ codeunit 148002 "E-Document Structured Tests" begin LibraryLowerPermission.SetOutsideO365Scope(); LibraryVariableStorage.Clear(); - // Clear(EDocImplState); Clear(LibraryVariableStorage); if IsInitialized then exit; - EDocument.DeleteAll(); - EDocumentServiceStatus.DeleteAll(); - EDocumentService.DeleteAll(); - EDocDataStorage.DeleteAll(); - EDocumentPurchaseHeader.DeleteAll(); - EDocumentPurchaseLine.DeleteAll(); - DocumentAttachment.DeleteAll(); + EDocument.DeleteAll(false); + EDocumentServiceStatus.DeleteAll(false); + EDocumentService.DeleteAll(false); + EDocDataStorage.DeleteAll(false); + EDocumentPurchaseHeader.DeleteAll(false); + EDocumentPurchaseLine.DeleteAll(false); + DocumentAttachment.DeleteAll(false); LibraryEDoc.SetupStandardVAT(); LibraryEDoc.SetupStandardSalesScenario(Customer, EDocumentService, Enum::"E-Document Format"::"Factura-E 3.2.2", Integration); LibraryEDoc.SetupStandardPurchaseScenario(Vendor, EDocumentService, Enum::"E-Document Format"::"Factura-E 3.2.2", Integration); EDocumentService."Import Process" := "E-Document Import Process"::"Version 2.0"; EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::"Factura-E"; - EDocumentService.Modify(); + EDocumentService.Modify(false); EDocumentsSetup.InsertNewExperienceSetup(); // Set a currency that can be used across all localizations @@ -203,7 +209,7 @@ codeunit 148002 "E-Document Structured Tests" MockDate := DMY2Date(22, 01, 2026); - TransformationRule.DeleteAll(); + TransformationRule.DeleteAll(false); TransformationRule.CreateDefaultTransformations(); IsInitialized := true; @@ -212,7 +218,7 @@ codeunit 148002 "E-Document Structured Tests" local procedure SetupFacturaEEDocumentService() begin EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::"Factura-E"; - EDocumentService.Modify(); + EDocumentService.Modify(false); end; local procedure CreateInboundEDocumentFromXML(var EDocument: Record "E-Document"; FilePath: Text) @@ -227,7 +233,7 @@ codeunit 148002 "E-Document Structured Tests" EDocLogRecord := EDocumentLog.InsertLog(Enum::"E-Document Service Status"::Imported, Enum::"Import E-Doc. Proc. Status"::Readable); EDocument."Structured Data Entry No." := EDocLogRecord."E-Doc. Data Storage Entry No."; - EDocument.Modify(); + EDocument.Modify(false); end; local procedure ProcessEDocumentToStep(var EDocument: Record "E-Document"; ProcessingStep: Enum "Import E-Document Steps"): Boolean From f916797065e1f37ebfe6ccbaab8e078b95ea2af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 27 Aug 2025 14:54:40 +0300 Subject: [PATCH 16/16] Normalizes EOF whitespace in tests Trims trailing blank line and removes final newline to align with repository formatting conventions. No functional changes. --- .../FacturaE/test/src/EDocumentStructuredTests.Codeunit.al | 2 +- .../FacturaE/test/src/LibraryEDocument.Codeunit.al | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al index a3934d67e6..2e78e219e9 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/EDocumentStructuredTests.Codeunit.al @@ -271,4 +271,4 @@ codeunit 148002 "E-Document Structured Tests" CurrencyExchangeRate."Relational Exch. Rate Amount" := 1.23; CurrencyExchangeRate.Insert(true); end; -} +} \ No newline at end of file diff --git a/Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al b/Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al index 9f58d0f976..1e2a911d2a 100644 --- a/Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al +++ b/Apps/ES/EDocumentFormats/FacturaE/test/src/LibraryEDocument.Codeunit.al @@ -1041,5 +1041,4 @@ codeunit 148003 "Library - E-Document" begin LibraryVariableStorage.Enqueue(EDocument); end; - } \ No newline at end of file