From 0e3512c3ee11a05a9a8dcf121b5544f174610f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 6 Aug 2025 15:55:45 +0300 Subject: [PATCH 1/7] Adds XRechnung format support for e-document import Implements structured format reader for processing XRechnung electronic documents (invoices and credit memos) based on UBL 2.1 standard. Enables automatic parsing of XML content into draft purchase documents with support for: - Vendor identification through VAT registration, GLN, or participant ID - Document header information (dates, amounts, currency, references) - Line item details (products, quantities, prices, VAT rates) - Customer and supplier party information extraction Provides viewing capability for imported documents through readable purchase document interface. --- .../EDocumentXRechnungHandler.Codeunit.al | 317 ++++++++++++++++++ .../XRechnungEDocReadIntoDraft.EnumExt.al | 18 + Apps/W1/EDocument/app/app.json | 5 + 3 files changed, 340 insertions(+) create mode 100644 Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al create mode 100644 Apps/DE/EDocumentDE/app/src/XRechnung/XRechnungEDocReadIntoDraft.EnumExt.al diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al new file mode 100644 index 0000000000..98bb36871a --- /dev/null +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al @@ -0,0 +1,317 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Formats; + +using Microsoft.eServices.EDocument; +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 System.Telemetry; +using System.Utilities; + +/// +/// Handler for processing XRechnung electronic documents. +/// Implements structured format reader interface for importing XRechnung invoices and credit memos. +/// +codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + var + EDocumentImportHelper: Codeunit "E-Document Import Helper"; + FeatureTelemetry: Codeunit "Feature Telemetry"; + FeatureNameTok: Label 'E-document XRechnung Format', Locked = true; + StartEventNameTok: Label 'E-document XRechnung import started. Parsing basic information.', Locked = true; + InvoiceLbl: Label 'INVOICE', Locked = true; + CreditNoteLbl: Label 'CREDITNOTE', Locked = true; + + /// + /// Reads an XRechnung electronic document into a draft purchase document. + /// Parses XML content and populates purchase header and lines based on document type (Invoice or Credit Note). + /// + /// The E-Document record to process. + /// The temporary blob containing the XML content to parse. + /// Returns the process draft type indicating a Purchase Document was created. + internal procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + XRechnungXml: XmlDocument; + XmlNamespaces: XmlNamespaceManager; + XmlElement: XmlElement; + CommonAggregateComponentsTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', Locked = true; + CommonBasicComponentsTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', Locked = true; + DefaultInvoiceTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2', Locked = true; + DefaultCreditNoteTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2', Locked = true; + begin + FeatureTelemetry.LogUsage('0000EXH', FeatureNameTok, StartEventNameTok); + EDocumentPurchaseHeader.InsertForEDocument(EDocument); + + XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), XRechnungXml); + + // Setup XML namespaces for XRechnung (UBL 2.1 based) + XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsTok); + XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsTok); + XmlNamespaces.AddNamespace('inv', DefaultInvoiceTok); + XmlNamespaces.AddNamespace('cn', DefaultCreditNoteTok); + + XRechnungXml.GetRoot(XmlElement); + case UpperCase(XmlElement.LocalName()) of + InvoiceLbl: + PopulateEDocumentForInvoice(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + CreditNoteLbl: + PopulateEDocumentForCreditNote(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + end; + + EDocumentPurchaseHeader.Modify(false); + EDocument.Direction := EDocument.Direction::Incoming; + exit(Enum::"E-Doc. Process Draft"::"Purchase Document"); + end; + + /// + /// Opens a page to view the readable purchase document content for the specified E-Document. + /// Displays purchase header and line information in a user-friendly format. + /// + /// The E-Document record to view. + /// The temporary blob containing the document content (not used in current 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"; + 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; + + local procedure PopulateEDocumentForInvoice(XRechnungXml: 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 + EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Invoice"; +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); + EDocumentXMLHelper.SetDateValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); + EDocumentXMLHelper.SetDateValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cbc:DueDate', EDocumentPurchaseHeader."Due Date"); + EDocumentXMLHelper.SetCurrencyValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); +#pragma warning restore AA0139 + EDocumentXMLHelper.SetNumberValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); + EDocumentXMLHelper.SetNumberValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', EDocumentPurchaseHeader.Total); + EDocumentXMLHelper.SetNumberValueInField(XRechnungXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader."Amount Due"); + EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader.Total - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; + VendorNo := ParseAccountingSupplierParty(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'inv:Invoice'); + ParseAccountingCustomerParty(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, 'inv:Invoice'); + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + InsertXRechnungPurchaseLines(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); + end; + + local procedure PopulateEDocumentForCreditNote(XRechnungXml: 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 + EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Credit Memo"; +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); + EDocumentXMLHelper.SetDateValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); + EDocumentXMLHelper.SetDateValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cbc:DueDate', EDocumentPurchaseHeader."Due Date"); + EDocumentXMLHelper.SetCurrencyValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); +#pragma warning restore AA0139 + EDocumentXMLHelper.SetNumberValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); + EDocumentXMLHelper.SetNumberValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', EDocumentPurchaseHeader.Total); + EDocumentXMLHelper.SetNumberValueInField(XRechnungXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader."Amount Due"); + EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader.Total - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; + VendorNo := ParseAccountingSupplierParty(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'cn:CreditNote'); + ParseAccountingCustomerParty(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, 'cn:CreditNote'); + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + InsertXRechnungPurchaseLines(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); + end; + + local procedure ParseAccountingSupplierParty(XRechnungXml: 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]; + EndpointID, SchemeID : Text; + GLN: Code[13]; + BasePathTxt: Text; + XMLNode: XmlNode; + begin +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + BasePathTxt := '/' + DocumentType + '/cac:AccountingSupplierParty/cac:Party'; + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Company Name"), EDocumentPurchaseHeader."Vendor Company Name"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Vendor Address"), EDocumentPurchaseHeader."Vendor Address"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:AdditionalStreetName', MaxStrLen(EDocumentPurchaseHeader."Vendor Address Recipient"), EDocumentPurchaseHeader."Vendor Address Recipient"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Vendor VAT Id"), EDocumentPurchaseHeader."Vendor VAT Id"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:Contact/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Contact Name"), EDocumentPurchaseHeader."Vendor Contact Name"); +#pragma warning restore AA0139 + if XRechnungXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin + SchemeID := XMLNode.AsXmlAttribute().Value(); + EndpointID := EDocumentXMLHelper.GetNodeValue(XRechnungXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); + case SchemeID of + 'EM', '0198', '9930': + VATRegistrationNo := CopyStr(EndpointID, 1, MaxStrLen(VATRegistrationNo)); + '0088': + begin + GLN := CopyStr(EndpointID, 1, MaxStrLen(GLN)); + EDocumentPurchaseHeader."Vendor GLN" := GLN; + end; + end; + VendorParticipantId := SchemeID + ':' + EndpointID; + end; + VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, 20); + VendorName := EDocumentPurchaseHeader."Vendor Company Name"; + VendorAddress := EDocumentPurchaseHeader."Vendor Address"; + if not FindVendorByVATRegNoOrGLN(VendorNo, VATRegistrationNo, GLN) then + if not FindVendorByParticipantId(VendorNo, EDocument, VendorParticipantId) then + VendorNo := EDocumentImportHelper.FindVendorByNameAndAddress(VendorName, VendorAddress); + end; + + local procedure ParseAccountingCustomerParty(XRechnungXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; DocumentType: Text) + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + BasePathTxt: Text; + XMLNode: XmlNode; + SchemeID, EndpointID : Text; + begin +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + BasePathTxt := '/' + DocumentType + '/cac:AccountingCustomerParty/cac:Party'; + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Customer Company Name"), EDocumentPurchaseHeader."Customer Company Name"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PartyLegalEntity/cbc:RegistrationName', MaxStrLen(EDocumentPurchaseHeader."Customer Company Name"), EDocumentPurchaseHeader."Customer Company Name"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Customer Address"), EDocumentPurchaseHeader."Customer Address"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:AdditionalStreetName', MaxStrLen(EDocumentPurchaseHeader."Customer Address Recipient"), EDocumentPurchaseHeader."Customer Address Recipient"); + EDocumentXMLHelper.SetStringValueInField(XRechnungXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id"), EDocumentPurchaseHeader."Customer VAT Id"); +#pragma warning restore AA0139 + if XRechnungXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin + SchemeID := XMLNode.AsXmlAttribute().Value(); + EndpointID := EDocumentXMLHelper.GetNodeValue(XRechnungXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); + if SchemeID = '0088' then + EDocumentPurchaseHeader."Customer GLN" := CopyStr(EndpointID, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN")); + end; + end; + + local procedure InsertXRechnungPurchaseLines(XRechnungXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; EDocumentEntryNo: Integer; DocumentType: Enum "E-Document Type") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + NewLineXML: XmlDocument; + LineXMLList: XmlNodeList; + LineXMLNode: XmlNode; + LineXPath: Text; + LineElementName: Text; + begin + case DocumentType of + "E-Document Type"::"Purchase Invoice": + begin + LineElementName := 'cac:InvoiceLine'; + LineXPath := '//inv:Invoice/cac:InvoiceLine'; + end; + "E-Document Type"::"Purchase Credit Memo": + begin + LineElementName := 'cac:CreditNoteLine'; + LineXPath := '//cn:CreditNote/cac:CreditNoteLine'; + end; + end; + + if not XRechnungXml.SelectNodes(LineXPath, 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); + PopulateXRechnungPurchaseLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine, LineElementName); + EDocumentPurchaseLine.Insert(false); + end; + end; + + local procedure PopulateXRechnungPurchaseLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line"; LineElementName: Text) + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + QuantityFieldName: Text; + begin +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + case LineElementName of + 'cac:InvoiceLine': + QuantityFieldName := 'cac:InvoiceLine/cbc:InvoicedQuantity'; + 'cac:CreditNoteLine': + QuantityFieldName := 'cac:CreditNoteLine/cbc:CreditedQuantity'; + end; + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineElementName + '/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineElementName + '/cac:Item/cbc:Name', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineElementName + '/cac:Item/cac:SellersItemIdentification/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineElementName + '/cac:Item/cac:StandardItemIdentification/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, QuantityFieldName, EDocumentPurchaseLine.Quantity); + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, QuantityFieldName + '/@unitCode', MaxStrLen(EDocumentPurchaseLine."Unit of Measure"), EDocumentPurchaseLine."Unit of Measure"); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, LineElementName + '/cac:Price/cbc:PriceAmount', EDocumentPurchaseLine."Unit Price"); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, LineElementName + '/cbc:LineExtensionAmount', EDocumentPurchaseLine."Sub Total"); + EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, LineElementName + '/cbc:LineExtensionAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); + EDocumentXMLHelper.SetNumberValueInField(LineXML, XmlNamespaces, LineElementName + '/cac:Item/cac:ClassifiedTaxCategory/cbc:Percent', EDocumentPurchaseLine."VAT Rate"); +#pragma warning restore AA0139 + end; + + local procedure FindVendorByVATRegNoOrGLN(var VendorNo: Code[20]; VATRegistrationNo: Text[20]; GLN: Code[13]): Boolean + var + Vendor: Record Vendor; + begin + // Try to find vendor by VAT Registration Number + if VATRegistrationNo <> '' then begin + Vendor.Reset(); + Vendor.SetLoadFields("VAT Registration No."); + Vendor.SetRange("VAT Registration No.", VATRegistrationNo); + if Vendor.FindFirst() then begin + VendorNo := Vendor."No."; + exit(true); + end; + end; + + // Try to find vendor by GLN + if GLN <> '' then begin + Vendor.Reset(); + Vendor.SetLoadFields("GLN"); + Vendor.SetRange("GLN", GLN); + if Vendor.FindFirst() then begin + VendorNo := Vendor."No."; + exit(true); + end; + end; + + exit(false); + end; + + local procedure FindVendorByParticipantId(var VendorNo: Code[20]; EDocument: Record "E-Document"; ParticipantId: Text): Boolean + var + EDocServiceParticipant: Record "Service Participant"; + EDocumentService: Record "E-Document Service"; + EDocumentHelper: Codeunit "E-Document Helper"; + begin + if ParticipantId = '' then + exit(false); + + EDocumentHelper.GetEdocumentService(EDocument, EDocumentService); + EDocServiceParticipant.SetRange("Participant Type", EDocServiceParticipant."Participant Type"::Vendor); + EDocServiceParticipant.SetRange("Participant Identifier", ParticipantId); + EDocServiceParticipant.SetRange(Service, EDocumentService.Code); + if not EDocServiceParticipant.FindFirst() then begin + EDocServiceParticipant.SetRange(Service); + if not EDocServiceParticipant.FindFirst() then + exit(false); + end; + + VendorNo := EDocServiceParticipant.Participant; + exit(true); + end; +} diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/XRechnungEDocReadIntoDraft.EnumExt.al b/Apps/DE/EDocumentDE/app/src/XRechnung/XRechnungEDocReadIntoDraft.EnumExt.al new file mode 100644 index 0000000000..812c14ecfc --- /dev/null +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/XRechnungEDocReadIntoDraft.EnumExt.al @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------------------------ +// 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; + +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Formats; +using Microsoft.eServices.EDocument.Processing.Interfaces; + +enumextension 13917 "XRechnung EDoc Read into Draft" extends "E-Doc. Read into Draft" +{ + value(13914; "XRechnung") + { + Caption = 'XRechnung'; + Implementation = IStructuredFormatReader = "E-Document XRechnung Handler"; + } +} diff --git a/Apps/W1/EDocument/app/app.json b/Apps/W1/EDocument/app/app.json index 69bae72d52..04b36c218a 100644 --- a/Apps/W1/EDocument/app/app.json +++ b/Apps/W1/EDocument/app/app.json @@ -37,6 +37,11 @@ "id": "967eceac-106e-4102-b76a-fe5dc3d799e5", "name": "Payables Agent Tests", "publisher": "Microsoft" + }, + { + "id": "fdeb586e-beff-49d8-947d-1e73ce980b34", + "name": "E-Document for Germany", + "publisher": "Microsoft" } ], "screenshots": [], From f687dfebde24bffb36beae43ad510f5599f73812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Thu, 14 Aug 2025 10:20:58 +0300 Subject: [PATCH 2/7] Refactors XRechnung handler to improve maintainability Replaces hardcoded string literals with named constants throughout the XRechnung document processing logic. Consolidates duplicate constants and moves them to appropriate scopes to reduce code duplication and improve readability. Updates XML documentation comments to provide clearer descriptions of method functionality and parameter usage. --- .../EDocumentXRechnungHandler.Codeunit.al | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al index 98bb36871a..cdc2e770f7 100644 --- a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al @@ -27,18 +27,17 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader var EDocumentImportHelper: Codeunit "E-Document Import Helper"; FeatureTelemetry: Codeunit "Feature Telemetry"; - FeatureNameTok: Label 'E-document XRechnung Format', Locked = true; - StartEventNameTok: Label 'E-document XRechnung import started. Parsing basic information.', Locked = true; - InvoiceLbl: Label 'INVOICE', Locked = true; - CreditNoteLbl: Label 'CREDITNOTE', Locked = true; + SchemeIDGLNTok: Label '0088', Locked = true; + InvoiceLineTok: Label 'cac:InvoiceLine', Locked = true; + CreditNoteLineTok: Label 'cac:CreditNoteLine', Locked = true; /// - /// Reads an XRechnung electronic document into a draft purchase document. - /// Parses XML content and populates purchase header and lines based on document type (Invoice or Credit Note). + /// Reads an XRechnung format XML document and converts it into a draft purchase document. + /// This procedure processes both Invoice and CreditNote document types and populates the E-Document Purchase Header with the extracted data. /// - /// The E-Document record to process. - /// The temporary blob containing the XML content to parse. - /// Returns the process draft type indicating a Purchase Document was created. + /// The E-Document record that contains the document metadata and information. + /// A temporary blob containing the XML document stream to be processed. + /// Returns an enum indicating that the process resulted in a purchase document draft. internal procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; @@ -49,6 +48,8 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader CommonBasicComponentsTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2', Locked = true; DefaultInvoiceTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2', Locked = true; DefaultCreditNoteTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2', Locked = true; + FeatureNameTok: Label 'E-document XRechnung Format', Locked = true; + StartEventNameTok: Label 'E-document XRechnung import started. Parsing basic information.', Locked = true; begin FeatureTelemetry.LogUsage('0000EXH', FeatureNameTok, StartEventNameTok); EDocumentPurchaseHeader.InsertForEDocument(EDocument); @@ -63,9 +64,9 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader XRechnungXml.GetRoot(XmlElement); case UpperCase(XmlElement.LocalName()) of - InvoiceLbl: + 'INVOICE': PopulateEDocumentForInvoice(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); - CreditNoteLbl: + 'CREDITNOTE': PopulateEDocumentForCreditNote(XRechnungXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); end; @@ -75,11 +76,11 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader end; /// - /// Opens a page to view the readable purchase document content for the specified E-Document. - /// Displays purchase header and line information in a user-friendly format. + /// Displays a readable view of the processed E-Document purchase information. + /// This procedure opens a page showing the purchase header and lines in a user-friendly format for review. /// - /// The E-Document record to view. - /// The temporary blob containing the document content (not used in current implementation). + /// The E-Document record that contains the document to be displayed. + /// A temporary blob containing the document data (not used in current implementation). internal procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") var EDocPurchaseHeader: Record "E-Document Purchase Header"; @@ -143,6 +144,9 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader local procedure ParseAccountingSupplierParty(XRechnungXml: 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"; + EMTok: Label 'EM', Locked = true; + "0198Tok": Label '0198', Locked = true; + "9930Tok": Label '9930', Locked = true; VendorName, VendorAddress, VendorParticipantId : Text; VATRegistrationNo: Text[20]; EndpointID, SchemeID : Text; @@ -162,9 +166,9 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader SchemeID := XMLNode.AsXmlAttribute().Value(); EndpointID := EDocumentXMLHelper.GetNodeValue(XRechnungXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); case SchemeID of - 'EM', '0198', '9930': + EMTok, "0198Tok", "9930Tok": VATRegistrationNo := CopyStr(EndpointID, 1, MaxStrLen(VATRegistrationNo)); - '0088': + SchemeIDGLNTok: begin GLN := CopyStr(EndpointID, 1, MaxStrLen(GLN)); EDocumentPurchaseHeader."Vendor GLN" := GLN; @@ -198,7 +202,7 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader if XRechnungXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin SchemeID := XMLNode.AsXmlAttribute().Value(); EndpointID := EDocumentXMLHelper.GetNodeValue(XRechnungXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); - if SchemeID = '0088' then + if SchemeID = SchemeIDGLNTok then EDocumentPurchaseHeader."Customer GLN" := CopyStr(EndpointID, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN")); end; end; @@ -215,12 +219,12 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader case DocumentType of "E-Document Type"::"Purchase Invoice": begin - LineElementName := 'cac:InvoiceLine'; + LineElementName := InvoiceLineTok; LineXPath := '//inv:Invoice/cac:InvoiceLine'; end; "E-Document Type"::"Purchase Credit Memo": begin - LineElementName := 'cac:CreditNoteLine'; + LineElementName := CreditNoteLineTok; LineXPath := '//cn:CreditNote/cac:CreditNoteLine'; end; end; @@ -245,9 +249,9 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader begin #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField case LineElementName of - 'cac:InvoiceLine': + InvoiceLineTok: QuantityFieldName := 'cac:InvoiceLine/cbc:InvoicedQuantity'; - 'cac:CreditNoteLine': + CreditNoteLineTok: QuantityFieldName := 'cac:CreditNoteLine/cbc:CreditedQuantity'; end; EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineElementName + '/cbc:ID', MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); From 2674c219a946c444553eb687331871b3fe0a096f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:07:13 +0300 Subject: [PATCH 3/7] Update Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al index cdc2e770f7..ad763653c1 100644 --- a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al @@ -14,10 +14,6 @@ using Microsoft.Purchases.Vendor; using System.Telemetry; using System.Utilities; -/// -/// Handler for processing XRechnung electronic documents. -/// Implements structured format reader interface for importing XRechnung invoices and credit memos. -/// codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader { Access = Internal; From a86905606042f163a38dd854440ea46d80b8c45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:07:20 +0300 Subject: [PATCH 4/7] Update Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al | 2 -- 1 file changed, 2 deletions(-) diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al index ad763653c1..5d62d05609 100644 --- a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al @@ -51,8 +51,6 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader EDocumentPurchaseHeader.InsertForEDocument(EDocument); XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), XRechnungXml); - - // Setup XML namespaces for XRechnung (UBL 2.1 based) XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsTok); XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsTok); XmlNamespaces.AddNamespace('inv', DefaultInvoiceTok); From ddcd5c8db1a98739f70804db8922514b1915bd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Fri, 22 Aug 2025 17:28:43 +0300 Subject: [PATCH 5/7] Scopes helpers, clarifies tokens, fixes VAT len Limits telemetry and import helper to local scope for better encapsulation. Improves token naming for routing ID and VAT registration clarity. Avoids hardcoded length by using field max length to prevent VAT ID truncation. --- .../XRechnung/EDocumentXRechnungHandler.Codeunit.al | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al index 5d62d05609..176a47b13d 100644 --- a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al @@ -21,8 +21,6 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader InherentPermissions = X; var - EDocumentImportHelper: Codeunit "E-Document Import Helper"; - FeatureTelemetry: Codeunit "Feature Telemetry"; SchemeIDGLNTok: Label '0088', Locked = true; InvoiceLineTok: Label 'cac:InvoiceLine', Locked = true; CreditNoteLineTok: Label 'cac:CreditNoteLine', Locked = true; @@ -37,6 +35,7 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader internal procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + FeatureTelemetry: Codeunit "Feature Telemetry"; XRechnungXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; XmlElement: XmlElement; @@ -137,10 +136,11 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader local procedure ParseAccountingSupplierParty(XRechnungXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document"; DocumentType: Text) VendorNo: Code[20] var + EDocumentImportHelper: Codeunit "E-Document Import Helper"; EDocumentXMLHelper: Codeunit "EDocument XML Helper"; EMTok: Label 'EM', Locked = true; - "0198Tok": Label '0198', Locked = true; - "9930Tok": Label '9930', Locked = true; + RoutingIdTok: Label '0198', Locked = true; + VATRegNoTok: Label '9930', Locked = true; VendorName, VendorAddress, VendorParticipantId : Text; VATRegistrationNo: Text[20]; EndpointID, SchemeID : Text; @@ -160,7 +160,7 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader SchemeID := XMLNode.AsXmlAttribute().Value(); EndpointID := EDocumentXMLHelper.GetNodeValue(XRechnungXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID'); case SchemeID of - EMTok, "0198Tok", "9930Tok": + EMTok, RoutingIdTok, VATRegNoTok: VATRegistrationNo := CopyStr(EndpointID, 1, MaxStrLen(VATRegistrationNo)); SchemeIDGLNTok: begin @@ -170,7 +170,7 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader end; VendorParticipantId := SchemeID + ':' + EndpointID; end; - VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, 20); + VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, MaxStrLen(VATRegistrationNo)); VendorName := EDocumentPurchaseHeader."Vendor Company Name"; VendorAddress := EDocumentPurchaseHeader."Vendor Address"; if not FindVendorByVATRegNoOrGLN(VendorNo, VATRegistrationNo, GLN) then From f75822d6add5d1ba0f3358a6abda092f473b8d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Thu, 28 Aug 2025 15:07:12 +0300 Subject: [PATCH 6/7] Add XRechnung structured import tests and mocks - Introduces integration tests for XRechnung invoice ingestion: content extraction into draft, viewing extracted data, draft updates, and purchase invoice creation. - Provides a mock e-document format and a test utility library to set up services, data, and mappings. - Adds an XML test resource and configures the test app to load resources; extends the test object ID range. - Adds a helper to reset e-document draft data. - Updates dependencies to include the DE tests extension. Improves confidence in end-to-end structured import and draft processing. --- .../EDocumentXRechnungHandler.Codeunit.al | 8 + .../xrechnung/xrechnung-invoice-0.xml | 166 +++ Apps/DE/EDocumentDE/test/app.json | 9 +- .../test/src/EDocFormatMock.Codeunit.al | 56 + .../test/src/EDocFormatMock.EnumExt.al | 7 + .../src/EDocumentStructuredTests.Codeunit.al | 273 +++++ .../test/src/LibraryEDocument.Codeunit.al | 1080 +++++++++++++++++ .../XRechnungStructValidations.Codeunit.al | 107 ++ Apps/W1/EDocument/app/app.json | 5 + 9 files changed, 1708 insertions(+), 3 deletions(-) create mode 100644 Apps/DE/EDocumentDE/test/.resources/xrechnung/xrechnung-invoice-0.xml create mode 100644 Apps/DE/EDocumentDE/test/src/EDocFormatMock.Codeunit.al create mode 100644 Apps/DE/EDocumentDE/test/src/EDocFormatMock.EnumExt.al create mode 100644 Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al create mode 100644 Apps/DE/EDocumentDE/test/src/LibraryEDocument.Codeunit.al create mode 100644 Apps/DE/EDocumentDE/test/src/XRechnungStructValidations.Codeunit.al diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al index 176a47b13d..2f40d9687e 100644 --- a/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/EDocumentXRechnungHandler.Codeunit.al @@ -312,4 +312,12 @@ codeunit 13921 "E-Document XRechnung Handler" implements IStructuredFormatReader VendorNo := EDocServiceParticipant.Participant; exit(true); 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/DE/EDocumentDE/test/.resources/xrechnung/xrechnung-invoice-0.xml b/Apps/DE/EDocumentDE/test/.resources/xrechnung/xrechnung-invoice-0.xml new file mode 100644 index 0000000000..ef430f47a5 --- /dev/null +++ b/Apps/DE/EDocumentDE/test/.resources/xrechnung/xrechnung-invoice-0.xml @@ -0,0 +1,166 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + 103033 + 2026-01-22 + 2026-02-22 + 380 + XYZ + 1 + + 2 + + + 103033 + + + 103033 + + + + + + 103033 + + + + + + + seller@email.de + + CRONUS International + + + Main Street, 14 + Birmingham + B27 4KT + + GB + + + + GB123456789 + + VAT + + + + CRONUS International + 123456789 + 123/456/7890, HRA-Eintrag in […] + + + Jim Olive + JO@contoso.com + + + + + + buyer@info.de + + 8712345000004 + + + The Cannon Group PLC + + + 192 Market Square + Birmingham + B27 4KT + + GB + + + + GB789456278 + + VAT + + + + The Cannon Group PLC + 789456278 + + + + + 2026-01-22 + + + + + 58 + + DE75512108001245126199 + + + + Zahlbar sofort ohne Abzug. + + + 980 + + 14000 + 980 + + S + 7 + + VAT + + + + + + 14000 + 14000 + 14980 + 0 + 0.00 + 0 + 14980 + + + 1000 + Item + 1 + 4000 + + Bicycle + + S + 25 + + VAT + + + + + 4000 + + + + 2000 + Item + 2 + 10000 + + Bicycle v2 + + S + 25 + + VAT + + + + + 5000 + + + \ No newline at end of file diff --git a/Apps/DE/EDocumentDE/test/app.json b/Apps/DE/EDocumentDE/test/app.json index d00c6971aa..a8c143604d 100644 --- a/Apps/DE/EDocumentDE/test/app.json +++ b/Apps/DE/EDocumentDE/test/app.json @@ -58,7 +58,7 @@ }, { "from": 13922, - "to": 13923 + "to": 13926 } ], "features": [ @@ -70,5 +70,8 @@ "allowDownloadingSource": true, "includeSourceInSymbolFile": true }, - "application": "27.0.0.0" -} + "application": "27.0.0.0", + "resourceFolders": [ + ".resources" + ] +} \ No newline at end of file diff --git a/Apps/DE/EDocumentDE/test/src/EDocFormatMock.Codeunit.al b/Apps/DE/EDocumentDE/test/src/EDocFormatMock.Codeunit.al new file mode 100644 index 0000000000..206fda4303 --- /dev/null +++ b/Apps/DE/EDocumentDE/test/src/EDocFormatMock.Codeunit.al @@ -0,0 +1,56 @@ +codeunit 13926 "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/DE/EDocumentDE/test/src/EDocFormatMock.EnumExt.al b/Apps/DE/EDocumentDE/test/src/EDocFormatMock.EnumExt.al new file mode 100644 index 0000000000..017e1f0d4b --- /dev/null +++ b/Apps/DE/EDocumentDE/test/src/EDocFormatMock.EnumExt.al @@ -0,0 +1,7 @@ +enumextension 13918 "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/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al b/Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al new file mode 100644 index 0000000000..f75bebe16b --- /dev/null +++ b/Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al @@ -0,0 +1,273 @@ +codeunit 13924 "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"; + LibraryLowerPermission: Codeunit "Library - Lower Permissions"; + XRechnungStructuredValidations: Codeunit "XRechnung 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 'xrechnung/xrechnung-invoice-0.xml', Locked = true; + MockCurrencyCode: Code[10]; + MockDate: Date; + + + #region XRechnung XML + [Test] + procedure TestXRechnungInvoice_ValidDocument() + var + EDocument: Record "E-Document"; + begin + // [FEATURE] [E-Document] [XRechnung] [Content Extraction] + // [SCENARIO] Process a valid XRechnung invoice document and validate full content extraction + + // [GIVEN] A valid XRechnung XML invoice document is imported + Initialize(Enum::"Service Integration"::"No Integration"); + SetupXRechnungEDocumentService(); + 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 + XRechnungStructuredValidations.SetMockCurrencyCode(MockCurrencyCode); + XRechnungStructuredValidations.SetMockDate(MockDate); + + // [THEN] All content from the XRechnung document is correctly extracted and validated + XRechnungStructuredValidations.AssertFullEDocumentContentExtracted(EDocument."Entry No"); + end else + Assert.Fail(EDocumentStatusNotUpdatedErr); + end; + + [Test] + [HandlerFunctions('EDocumentPurchaseHeaderPageHandler')] + procedure TestXRechnungInvoice_ValidDocument_ViewExtractedData() + var + EDocument: Record "E-Document"; + EDocImport: Codeunit "E-Doc. Import"; + begin + // [FEATURE] [E-Document] [XRechnung] [View Data] + // [SCENARIO] View extracted data from a valid XRechnung invoice document + + // [GIVEN] A valid XRechnung XML invoice document is imported + Initialize(Enum::"Service Integration"::"No Integration"); + SetupXRechnungEDocumentService(); + CreateInboundEDocumentFromXML(EDocument, TestFileTok); + + // [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 TestXRechnungInvoice_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] [XRechnung] [Purchase Invoice Creation] + // [SCENARIO] Create a purchase invoice from a valid XRechnung invoice document + + // [GIVEN] A valid XRechnung XML invoice document is imported + Initialize(Enum::"Service Integration"::"No Integration"); + Vendor."VAT Registration No." := 'GB123456789'; + Vendor.Modify(true); + SetupXRechnungEDocumentService(); + CreateInboundEDocumentFromXML(EDocument, TestFileTok); + + // [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 XRechnung data + XRechnungStructuredValidations.SetMockCurrencyCode(MockCurrencyCode); + XRechnungStructuredValidations.SetMockDate(MockDate); + XRechnungStructuredValidations.AssertPurchaseDocument(Vendor."No.", PurchaseHeader, DummyItem); + end; + + [Test] + procedure TestXRechnungInvoice_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] [XRechnung] [Draft Update] + // [SCENARIO] Update draft purchase document data and finalize processing + + // [GIVEN] A valid XRechnung XML invoice document is imported and processed to draft preparation + Initialize(Enum::"Service Integration"::"No Integration"); + Vendor."VAT Registration No." := 'GB123456789'; + Vendor.Modify(true); + SetupXRechnungEDocumentService(); + CreateInboundEDocumentFromXML(EDocument, TestFileTok); + 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."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 XRechnung data and manual updates + XRechnungStructuredValidations.SetMockCurrencyCode(MockCurrencyCode); + XRechnungStructuredValidations.SetMockDate(MockDate); + XRechnungStructuredValidations.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(LibraryVariableStorage); + + if IsInitialized then + exit; + + 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"::Mock, Integration); + LibraryEDoc.SetupStandardPurchaseScenario(Vendor, EDocumentService, Enum::"E-Document Format"::Mock, Integration); + EDocumentService."Import Process" := "E-Document Import Process"::"Version 2.0"; + EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::XRechnung; + 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(false); + TransformationRule.CreateDefaultTransformations(); + + IsInitialized := true; + end; + + local procedure SetupXRechnungEDocumentService() + begin + EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::XRechnung; + EDocumentService.Modify(false); + 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(false); + 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; +} \ No newline at end of file diff --git a/Apps/DE/EDocumentDE/test/src/LibraryEDocument.Codeunit.al b/Apps/DE/EDocumentDE/test/src/LibraryEDocument.Codeunit.al new file mode 100644 index 0000000000..88596086a7 --- /dev/null +++ b/Apps/DE/EDocumentDE/test/src/LibraryEDocument.Codeunit.al @@ -0,0 +1,1080 @@ +codeunit 13923 "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"; + 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; + + UnitOfMeasure.Init(); + UnitOfMeasure."International Standard Code" := 'PCS'; + UnitOfMeasure.Code := 'PCS'; + if UnitOfMeasure.Insert() then; + + CreateItemUnitOfMeasure(StandardItem."No.", UnitOfMeasure.Code); + LibraryItemReference.CreateItemReference(ItemReference, StandardItem."No.", '', 'PCS', 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.", '', 'PCS', Enum::"Item Reference Type"::Vendor, Vendor."No.", '2000'); + end; + + procedure PostSalesShipment(var Customer: Record Customer) SalesShipmentHeader: Record "Sales Shipment Header"; + var + SalesHeader: Record "Sales Header"; + begin + this.CreateSalesHeaderWithItem(Customer, SalesHeader, Enum::"Sales Document Type"::Order); + SalesShipmentHeader.Get(this.LibrarySales.PostSalesDocument(SalesHeader, true, false)); + 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"; + 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(); + 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 CreateServiceWithMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule"; Integration: Enum "E-Document Integration"): Code[20] + // begin + // exit(CreateServiceWithMapping(EDocMapping, TransformationRule, false, Integration)); + // end; + + // procedure CreateServiceWithMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule"; UseBatching: Boolean; Integration: Enum "E-Document Integration"): Code[20] + // var + // SalesInvHeader: Record "Sales Invoice Header"; + // 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."Use Batch Processing" := UseBatching; + // EDocService.Insert(); + + // CreateSupportedDocTypes(EDocService); + + // // 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(); + + // exit(EDocService.Code); + // 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/DE/EDocumentDE/test/src/XRechnungStructValidations.Codeunit.al b/Apps/DE/EDocumentDE/test/src/XRechnungStructValidations.Codeunit.al new file mode 100644 index 0000000000..844b19651c --- /dev/null +++ b/Apps/DE/EDocumentDE/test/src/XRechnungStructValidations.Codeunit.al @@ -0,0 +1,107 @@ +codeunit 13925 "XRechnung Struct. Validations" +{ + var + Assert: Codeunit Assert; + UnitOfMeasureCodeTok: Label 'PCS', Locked = true; + SalesInvoiceNoTok: Label '103033', Locked = true; + PurchaseorderNoTok: Label '2', 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(), SalesInvoiceNoTok, EDocumentPurchaseHeader."Sales Invoice No.")); + Assert.AreEqual(MockDate, EDocumentPurchaseHeader."Document Date", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Document Date"), EDocumentPurchaseHeader.TableCaption(), MockDate, EDocumentPurchaseHeader."Document Date")); + Assert.AreEqual(CalcDate('<+1M>', MockDate), EDocumentPurchaseHeader."Due Date", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Due Date"), EDocumentPurchaseHeader.TableCaption(), CalcDate('<+1M>', MockDate), EDocumentPurchaseHeader."Due Date")); + Assert.AreEqual(MockCurrencyCode, EDocumentPurchaseHeader."Currency Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Currency Code"), EDocumentPurchaseHeader.TableCaption(), MockCurrencyCode, EDocumentPurchaseHeader."Currency Code")); + Assert.AreEqual(PurchaseorderNoTok, EDocumentPurchaseHeader."Purchase Order No.", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Purchase Order No."), EDocumentPurchaseHeader.TableCaption(), PurchaseorderNoTok, EDocumentPurchaseHeader."Purchase Order No.")); + 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('Jim Olive', EDocumentPurchaseHeader."Vendor Contact Name", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor Contact Name"), EDocumentPurchaseHeader.TableCaption(), 'Jim Olive', EDocumentPurchaseHeader."Vendor Contact Name")); + 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(), UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure")); + Assert.AreEqual(4000, EDocumentPurchaseLine."Sub Total", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Sub Total"), EDocumentPurchaseLine.TableCaption(), 4000, EDocumentPurchaseLine."Sub Total")); + Assert.AreEqual(MockCurrencyCode, EDocumentPurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Currency Code"), EDocumentPurchaseLine.TableCaption(), MockCurrencyCode, EDocumentPurchaseLine."Currency Code")); + 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(MockCurrencyCode, EDocumentPurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Currency Code"), EDocumentPurchaseLine.TableCaption(), MockCurrencyCode, EDocumentPurchaseLine."Currency Code")); + 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 'GL00000001', Locked = true; + Item2NoTok: Label 'GL00000003', 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(CalcDate('<+1M>', MockDate), PurchaseHeader."Due Date", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Due Date"), PurchaseHeader.TableCaption(), CalcDate('<+1M>', MockDate), PurchaseHeader."Due Date")); + Assert.AreEqual(MockCurrencyCode, PurchaseHeader."Currency Code", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Currency Code"), PurchaseHeader.TableCaption(), MockCurrencyCode, PurchaseHeader."Currency Code")); + Assert.AreEqual(PurchaseorderNoTok, PurchaseHeader."Vendor Order No.", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Vendor Order No."), PurchaseHeader.TableCaption(), PurchaseorderNoTok, PurchaseHeader."Vendor Order No.")); + 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/W1/EDocument/app/app.json b/Apps/W1/EDocument/app/app.json index 74cf904eb1..f193c1dead 100644 --- a/Apps/W1/EDocument/app/app.json +++ b/Apps/W1/EDocument/app/app.json @@ -42,6 +42,11 @@ "id": "fdeb586e-beff-49d8-947d-1e73ce980b34", "name": "E-Document for Germany", "publisher": "Microsoft" + }, + { + "id": "4022897d-41ca-4ae2-a766-a429d0b19273", + "name": "E-Document for Germany Tests", + "publisher": "Microsoft" } ], "screenshots": [], From 333690e8fd61e35e41c3991a8276d4f77c9e8318 Mon Sep 17 00:00:00 2001 From: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:05:45 +0300 Subject: [PATCH 7/7] Update Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al --- .../DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al b/Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al index f75bebe16b..31713dbe12 100644 --- a/Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al +++ b/Apps/DE/EDocumentDE/test/src/EDocumentStructuredTests.Codeunit.al @@ -18,7 +18,6 @@ codeunit 13924 "E-Document Structured Tests" MockCurrencyCode: Code[10]; MockDate: Date; - #region XRechnung XML [Test] procedure TestXRechnungInvoice_ValidDocument()