diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/src/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al
new file mode 100644
index 00000000000..bf03d9bc33b
--- /dev/null
+++ b/src/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al
@@ -0,0 +1,353 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Processing.Import.Purchase;
+using Microsoft.eServices.EDocument.Processing.Interfaces;
+using Microsoft.eServices.EDocument.Service.Participant;
+using Microsoft.Purchases.Vendor;
+using System.Utilities;
+
+codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader
+{
+ Access = Internal;
+ InherentEntitlements = X;
+ InherentPermissions = X;
+
+ var
+ GLNTok: Label 'GLN', Locked = true;
+ DKCVRTok: Label 'DK:CVR', Locked = true;
+ InvoiceLineTok: Label 'cac:InvoiceLine', Locked = true;
+ CreditNoteLineTok: Label 'cac:CreditNoteLine', Locked = true;
+ 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;
+ UnsupportedXmlRootElementErr: Label 'Unsupported XML root element: %1.', Comment = '%1 = local name of the XML root element';
+
+ ///
+ /// Reads an OIOUBL format XML document and converts it into a draft purchase document.
+ /// This procedure processes Invoice and CreditNote document types and populates the E-Document Purchase Header with the extracted data.
+ ///
+ /// 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";
+ OIOUBLXml: XmlDocument;
+ XmlNamespaces: XmlNamespaceManager;
+ XmlElement: XmlElement;
+ ProcessDraft: Enum "E-Doc. Process Draft";
+ begin
+ ResetDraft(EDocument);
+ EDocumentPurchaseHeader.InsertForEDocument(EDocument);
+
+ XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), OIOUBLXml);
+
+ XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsTok);
+ XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsTok);
+ XmlNamespaces.AddNamespace('inv', DefaultInvoiceTok);
+ XmlNamespaces.AddNamespace('cn', DefaultCreditNoteTok);
+
+ OIOUBLXml.GetRoot(XmlElement);
+ case XmlElement.LocalName() of
+ 'Invoice': begin
+ if XmlElement.NamespaceUri() <> DefaultInvoiceTok then
+ Error(UnsupportedXmlRootElementErr, XmlElement.LocalName());
+ PopulateEDocumentForInvoice(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument);
+ ProcessDraft := Enum::"E-Doc. Process Draft"::"Purchase Invoice";
+ end;
+ 'CreditNote': begin
+ if XmlElement.NamespaceUri() <> DefaultCreditNoteTok then
+ Error(UnsupportedXmlRootElementErr, XmlElement.LocalName());
+ PopulateEDocumentForCreditNote(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument);
+ ProcessDraft := Enum::"E-Doc. Process Draft"::"Purchase Credit Memo";
+ end;
+ else
+ Error(UnsupportedXmlRootElementErr, XmlElement.LocalName());
+ end;
+
+ EDocumentPurchaseHeader.Modify(false);
+ EDocument.Direction := EDocument.Direction::Incoming;
+ exit(ProcessDraft);
+ end;
+
+ ///
+ /// 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 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";
+ EDocPurchaseLine: Record "E-Document Purchase Line";
+ TempEDocPurchaseHeader: Record "E-Document Purchase Header" temporary;
+ TempEDocPurchaseLine: Record "E-Document Purchase Line" temporary;
+ EDocReadablePurchaseDoc: Page "E-Doc. Readable Purchase Doc.";
+ begin
+ EDocPurchaseHeader.GetFromEDocument(EDocument);
+ TempEDocPurchaseHeader := EDocPurchaseHeader;
+ TempEDocPurchaseHeader.Insert();
+
+ EDocPurchaseLine.SetRange("E-Document Entry No.", EDocPurchaseHeader."E-Document Entry No.");
+ if EDocPurchaseLine.FindSet() then
+ repeat
+ TempEDocPurchaseLine := EDocPurchaseLine;
+ TempEDocPurchaseLine.Insert();
+ until EDocPurchaseLine.Next() = 0;
+
+ EDocReadablePurchaseDoc.SetBuffer(TempEDocPurchaseHeader, TempEDocPurchaseLine);
+ EDocReadablePurchaseDoc.Run();
+ end;
+
+ local procedure PopulateEDocumentForInvoice(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document")
+ var
+ EDocumentXMLHelper: Codeunit "E-Document PEPPOL Utility";
+ VendorNo: Code[20];
+ begin
+#pragma warning disable AA0139
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No.");
+ EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cbc:IssueDate', EDocumentPurchaseHeader."Document Date");
+ EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cbc:DueDate', EDocumentPurchaseHeader."Due Date");
+ if EDocumentPurchaseHeader."Due Date" = 0D then
+ EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cac:PaymentMeans/cbc:PaymentDueDate', EDocumentPurchaseHeader."Due Date");
+ EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No.");
+#pragma warning restore AA0139
+ EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total");
+ EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', EDocumentPurchaseHeader.Total);
+ EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader."Amount Due");
+ EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount";
+ VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'inv:Invoice');
+ ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'inv:Invoice');
+ if VendorNo <> '' then
+ EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo;
+ InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", "E-Document Type"::"Purchase Invoice");
+ end;
+
+ local procedure PopulateEDocumentForCreditNote(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document")
+ var
+ EDocumentXMLHelper: Codeunit "E-Document PEPPOL Utility";
+ VendorNo: Code[20];
+ begin
+#pragma warning disable AA0139
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No.");
+ EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cbc:IssueDate', EDocumentPurchaseHeader."Document Date");
+ EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cac:PaymentMeans/cbc:PaymentDueDate', EDocumentPurchaseHeader."Due Date");
+ EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No.");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cac:BillingReference/cac:InvoiceDocumentReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Applies-to Ext. Invoice No."), EDocumentPurchaseHeader."Applies-to Ext. Invoice No.");
+#pragma warning restore AA0139
+ EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total");
+ EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount', EDocumentPurchaseHeader.Total);
+ EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader."Amount Due");
+ EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount";
+ VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'cn:CreditNote');
+ ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'cn:CreditNote');
+ if VendorNo <> '' then
+ EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo;
+ InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", "E-Document Type"::"Purchase Credit Memo");
+ end;
+
+ local procedure ParseAccountingSupplierParty(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document"; DocumentType: Text) VendorNo: Code[20]
+ var
+ EDocumentXMLHelper: Codeunit "E-Document PEPPOL Utility";
+ EDocumentImportHelper: Codeunit "E-Document Import 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(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Company Name"), EDocumentPurchaseHeader."Vendor Company Name");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Vendor Address"), EDocumentPurchaseHeader."Vendor Address");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:AdditionalStreetName', MaxStrLen(EDocumentPurchaseHeader."Vendor Address Recipient"), EDocumentPurchaseHeader."Vendor Address Recipient");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Vendor VAT Id"), EDocumentPurchaseHeader."Vendor VAT Id");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:Contact/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Contact Name"), EDocumentPurchaseHeader."Vendor Contact Name");
+#pragma warning restore AA0139
+ if OIOUBLXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin
+ SchemeID := XMLNode.AsXmlAttribute().Value();
+ EndpointID := EDocumentXMLHelper.GetNodeValue(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID');
+ case SchemeID of
+ DKCVRTok:
+ VATRegistrationNo := CopyStr(EndpointID, 1, MaxStrLen(VATRegistrationNo));
+ GLNTok:
+ begin
+ GLN := CopyStr(EndpointID, 1, MaxStrLen(GLN));
+ EDocumentPurchaseHeader."Vendor GLN" := GLN;
+ end;
+ end;
+ VendorParticipantId := SchemeID + ':' + EndpointID;
+ end;
+ if EDocumentPurchaseHeader."Vendor VAT Id" <> '' then
+ VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, MaxStrLen(VATRegistrationNo));
+ if VATRegistrationNo = '' then
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyIdentification/cbc:ID', MaxStrLen(VATRegistrationNo), VATRegistrationNo);
+ 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(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; DocumentType: Text)
+ var
+ EDocumentXMLHelper: Codeunit "E-Document PEPPOL Utility";
+ 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(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyName/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Customer Company Name"), EDocumentPurchaseHeader."Customer Company Name");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:StreetName', MaxStrLen(EDocumentPurchaseHeader."Customer Address"), EDocumentPurchaseHeader."Customer Address");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PostalAddress/cbc:AdditionalStreetName', MaxStrLen(EDocumentPurchaseHeader."Customer Address Recipient"), EDocumentPurchaseHeader."Customer Address Recipient");
+ EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id"), EDocumentPurchaseHeader."Customer VAT Id");
+#pragma warning restore AA0139
+ if OIOUBLXml.SelectSingleNode(BasePathTxt + '/cbc:EndpointID/@schemeID', XmlNamespaces, XMLNode) then begin
+ SchemeID := XMLNode.AsXmlAttribute().Value();
+ EndpointID := EDocumentXMLHelper.GetNodeValue(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cbc:EndpointID');
+ if SchemeID = GLNTok then
+ EDocumentPurchaseHeader."Customer GLN" := CopyStr(EndpointID, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN"));
+ end;
+ end;
+
+ local procedure InsertOIOUBLPurchaseLines(OIOUBLXml: 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
+ LineXPath := '/inv:Invoice/cac:InvoiceLine';
+ LineElementName := InvoiceLineTok;
+ end;
+ "E-Document Type"::"Purchase Credit Memo":
+ begin
+ LineXPath := '/cn:CreditNote/cac:CreditNoteLine';
+ LineElementName := CreditNoteLineTok;
+ end;
+ end;
+
+ if not OIOUBLXml.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);
+
+ PopulateOIOUBLPurchaseLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine, LineElementName);
+ EDocumentPurchaseLine.Insert(false);
+ end;
+ end;
+
+ local procedure PopulateOIOUBLPurchaseLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line"; LineElementName: Text)
+ var
+ EDocumentXMLHelper: Codeunit "E-Document PEPPOL Utility";
+ LineIdFieldName: Text;
+ QuantityFieldName: Text;
+ begin
+#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField
+ case LineElementName of
+ InvoiceLineTok:
+ begin
+ LineIdFieldName := 'cac:InvoiceLine/cac:Item/cac:SellersItemIdentification/cbc:ID';
+ QuantityFieldName := 'cac:InvoiceLine/cbc:InvoicedQuantity';
+ end;
+ CreditNoteLineTok:
+ begin
+ LineIdFieldName := 'cac:CreditNoteLine/cac:Item/cac:SellersItemIdentification/cbc:ID';
+ QuantityFieldName := 'cac:CreditNoteLine/cbc:CreditedQuantity';
+ end;
+ end;
+
+ EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineIdFieldName, 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
+ 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;
+
+ procedure ResetDraft(EDocument: Record "E-Document")
+ var
+ EDocPurchaseHeader: Record "E-Document Purchase Header";
+ EDocPurchaseLine: Record "E-Document Purchase Line";
+ begin
+ EDocPurchaseHeader.SetRange("E-Document Entry No.", EDocument."Entry No");
+ EDocPurchaseHeader.DeleteAll();
+ EDocPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No");
+ EDocPurchaseLine.DeleteAll();
+ end;
+}
diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/app/src/OIOUBLEDocReadIntoDraft.EnumExt.al b/src/Apps/DK/EDocumentFormatOIOUBL/app/src/OIOUBLEDocReadIntoDraft.EnumExt.al
new file mode 100644
index 00000000000..7f3be0041a2
--- /dev/null
+++ b/src/Apps/DK/EDocumentFormatOIOUBL/app/src/OIOUBLEDocReadIntoDraft.EnumExt.al
@@ -0,0 +1,17 @@
+// ------------------------------------------------------------------------------------------------
+// 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.Processing.Interfaces;
+
+enumextension 13911 "OIOUBL EDoc Read into Draft" extends "E-Doc. Read into Draft"
+{
+ value(13910; "OIOUBL")
+ {
+ Caption = 'OIOUBL';
+ Implementation = IStructuredFormatReader = "E-Document OIOUBL Handler";
+ }
+}
diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-creditnote-0.xml b/src/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-creditnote-0.xml
new file mode 100644
index 00000000000..929e055d989
--- /dev/null
+++ b/src/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-creditnote-0.xml
@@ -0,0 +1,100 @@
+
+
+ 2.0
+ OIOUBL-2.01
+ Procurement-OrdSimR-BilSim-1.0
+ CN-5001
+ 2026-02-15
+ 381
+ XYZ
+
+ 5
+
+
+
+ 103033
+ 2026-01-22
+
+
+
+
+ GB123456789
+
+ CRONUS International
+
+
+ Main Street, 14
+ Birmingham
+ B27 4KT
+
+ DK
+
+
+
+ GB123456789
+
+ VAT
+
+
+
+ Jim Olive
+
+
+
+
+
+ GB789456278
+
+ The Cannon Group PLC
+
+
+ 192 Market Square
+ Birmingham
+ B27 4KT
+
+ DK
+
+
+
+ GB789456278
+
+ VAT
+
+
+
+
+
+ 30
+ 2026-03-15
+
+
+ 500
+
+
+ 2000
+ 2000
+ 2500
+ 2500
+
+
+ 10000
+ 1
+ 2000
+
+ Bicycle - Return
+
+ 1000
+
+
+ S
+ 25
+
+ VAT
+
+
+
+
+ 2000.00
+
+
+
diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-invoice-0.xml b/src/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-invoice-0.xml
new file mode 100644
index 00000000000..63e53c02467
--- /dev/null
+++ b/src/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-invoice-0.xml
@@ -0,0 +1,204 @@
+
+
+ 2.0
+ OIOUBL-2.01
+ Procurement-OrdSimR-BilSim-1.0
+ 103033
+ false
+ 01d4dd7d-ac2b-460d-ae44-5f20d540952a
+ 2026-01-22
+ 12:00:00
+ 380
+ XYZ
+
+ 2
+ false
+ 9756b468-8815-1029-857a-e388fe63f399
+ 2025-07-20
+ 12:00:00
+
+
+
+ GB123456789
+
+ GB123456789
+
+
+ CRONUS International
+
+
+ StructuredDK
+ Main Street, 14
+ Birmingham
+ B27 4KT
+
+ DK
+
+
+
+ GB123456789
+
+ 63
+ Moms
+
+
+
+ CRONUS International
+ GB123456789
+
+
+ Jim Olive
+ JO@contoso.com
+
+
+
+
+
+ GB789456278
+
+ GB789456278
+
+
+ The Cannon Group PLC
+
+
+ StructuredDK
+ 192 Market Square
+ Birmingham
+ B27 4KT
+
+ DK
+
+
+
+ GB789456278
+
+ 63
+ Moms
+
+
+
+ The Cannon Group PLC
+ GB789456278
+
+
+ Contact Person
+ mr.andy.teal@cannongroup.com
+
+
+
+
+ 42
+ 2026-02-22
+
+
+ 1000.00
+
+ 14000.00
+ 1000.00
+
+ StandardRated
+ 25
+
+ 63
+ Moms
+
+
+
+
+
+ 14000.00
+ 14000.00
+ 14140.00
+ 0
+ 0.00
+ 0
+ 14140.00
+
+
+ 10000
+ Vare
+ 1
+ 4000.00
+
+ 1000.00
+
+ 4000.00
+ 1000.00
+
+ StandardRated
+ 25
+
+ 63
+ Moms
+
+
+
+
+
+ Bicycle
+ Bicycle
+
+ 1000
+
+
+ 1000
+
+
+ StandardRated
+ 25
+
+ 63
+ Moms
+
+
+
+
+ 4000.00
+ 1
+
+
+
+ 20000
+ Vare
+ 2
+ 10000.00
+
+ 0.00
+
+ 10000.00
+ 0.00
+
+ StandardRated
+ 25
+
+ 63
+ Moms
+
+
+
+
+
+ Bicycle v2
+ Bicycle v2
+
+ 2000
+
+
+ 2000
+
+
+ StandardRated
+ 25
+
+ 63
+ Moms
+
+
+
+
+ 5000.00
+ 1
+
+
+
\ No newline at end of file
diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/test/ExtensionLogo.png b/src/Apps/DK/EDocumentFormatOIOUBL/test/ExtensionLogo.png
new file mode 100644
index 00000000000..4d2c9a626cb
Binary files /dev/null and b/src/Apps/DK/EDocumentFormatOIOUBL/test/ExtensionLogo.png differ
diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/test/app.json b/src/Apps/DK/EDocumentFormatOIOUBL/test/app.json
new file mode 100644
index 00000000000..c0fe56e23a4
--- /dev/null
+++ b/src/Apps/DK/EDocumentFormatOIOUBL/test/app.json
@@ -0,0 +1,72 @@
+{
+ "id": "f78b9487-b911-4db1-893b-5a5c310919b8",
+ "name": "E-Document Format for OIOUBL Tests",
+ "publisher": "Microsoft",
+ "brief": "E-Document Format for OIOUBL Tests.",
+ "description": "Tests for E-Document Format for OIOUBL.",
+ "version": "29.0.0.0",
+ "privacyStatement": "https://go.microsoft.com/fwlink/?LinkId=724009",
+ "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120",
+ "help": "https://go.microsoft.com/fwlink/?linkid=2299409",
+ "url": "https://go.microsoft.com/fwlink/?LinkId=724011",
+ "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2299409",
+ "logo": "ExtensionLogo.png",
+ "dependencies": [
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8b",
+ "name": "E-Document Core",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ },
+ {
+ "id": "e1d97edc-c239-46b4-8d84-6368bdf67c8c",
+ "name": "E-Document Core Tests",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ },
+ {
+ "id": "5d86850b-0d76-4eca-bd7b-951ad998e997",
+ "name": "Tests-TestLibraries",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ },
+ {
+ "id": "9856ae4f-d1a7-46ef-89bb-6ef056398228",
+ "name": "System Application Test Library",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ },
+ {
+ "id": "5095f467-0a01-4b99-99d1-9ff1237d286f",
+ "publisher": "Microsoft",
+ "name": "Library Variable Storage",
+ "version": "29.0.0.0"
+ },
+ {
+ "id": "8228f99b-cce5-4b9c-b247-9ee1145b7470",
+ "name": "E-Document format for OIOUBL",
+ "publisher": "Microsoft",
+ "version": "29.0.0.0"
+ }
+ ],
+ "screenshots": [],
+ "platform": "29.0.0.0",
+ "features": [
+ "TranslationFile"
+ ],
+ "idRanges": [
+ {
+ "from": 148061,
+ "to": 148062
+ }
+ ],
+ "resourceFolders": [
+ ".resources"
+ ],
+ "resourceExposurePolicy": {
+ "allowDebugging": true,
+ "allowDownloadingSource": true,
+ "includeSourceInSymbolFile": true
+ },
+ "application": "29.0.0.0"
+}
diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocumentStructuredTests.Codeunit.al b/src/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocumentStructuredTests.Codeunit.al
new file mode 100644
index 00000000000..cb015ae3cf3
--- /dev/null
+++ b/src/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocumentStructuredTests.Codeunit.al
@@ -0,0 +1,405 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+using Microsoft.eServices.EDocument.Processing.Interfaces;
+
+codeunit 148061 "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";
+ OIOUBLStructuredValidations: Codeunit "OIOUBL Structured Validations";
+ IsInitialized: Boolean;
+ EDocumentStatusNotUpdatedErr: Label 'The status of the EDocument was not updated to the expected status after the step was executed.';
+ TestFileTok: Label 'oioubl/oioubl-invoice-0.xml', Locked = true;
+ CreditNoteTestFileTok: Label 'oioubl/oioubl-creditnote-0.xml', Locked = true;
+ UnsupportedXmlRootElementErr: Label 'Unsupported XML root element: %1.', Comment = '%1 = local name of the XML root element';
+ MockCurrencyCode: Code[10];
+ MockDate: Date;
+
+ #region OIOUBL XML
+ [Test]
+ procedure TestOIOUBLInvoice_ValidDocument()
+ var
+ EDocument: Record "E-Document";
+ begin
+ // [FEATURE] [E-Document] [OIOUBL] [Content Extraction]
+ // [SCENARIO] Import and extract content from a valid OIOUBL invoice document
+
+ // [GIVEN] A valid OIOUBL XML invoice document is imported
+ Initialize(Enum::"Service Integration"::"No Integration");
+ SetupOIOUBLEDocumentService();
+ 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
+ OIOUBLStructuredValidations.SetMockCurrencyCode(MockCurrencyCode);
+ OIOUBLStructuredValidations.SetMockDate(MockDate);
+
+ // [THEN] All document content is correctly extracted and validated
+ OIOUBLStructuredValidations.AssertFullEDocumentContentExtracted(EDocument."Entry No");
+ end else
+ Assert.Fail(EDocumentStatusNotUpdatedErr);
+ end;
+
+ [Test]
+ procedure TestOIOUBLInvoice_ReturnsInvoiceProcessDraftImpl()
+ var
+ EDocument: Record "E-Document";
+ begin
+ // [SCENARIO] An OIOUBL Invoice selects the purchase invoice draft implementation
+ Initialize(Enum::"Service Integration"::"No Integration");
+ SetupOIOUBLEDocumentService();
+ CreateInboundEDocumentFromXML(EDocument, TestFileTok);
+
+ if ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft") then begin
+ EDocument.Get(EDocument."Entry No");
+ Assert.AreEqual(Enum::"E-Doc. Process Draft"::"Purchase Invoice", EDocument."Process Draft Impl.", 'The process draft implementation should be set to Purchase Invoice for invoices.');
+ end else
+ Assert.Fail(EDocumentStatusNotUpdatedErr);
+ end;
+
+ [Test]
+ procedure TestOIOUBLInvoice_ReReadingReplacesExistingDraftLines()
+ var
+ EDocument: Record "E-Document";
+ begin
+ // [SCENARIO] Re-reading an OIOUBL Invoice replaces the previous staging data
+ Initialize(Enum::"Service Integration"::"No Integration");
+ SetupOIOUBLEDocumentService();
+ CreateInboundEDocumentFromXML(EDocument, TestFileTok);
+ ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft");
+
+ if ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft") then
+ OIOUBLStructuredValidations.AssertPurchaseLineCount(EDocument."Entry No", 2)
+ else
+ Assert.Fail(EDocumentStatusNotUpdatedErr);
+ end;
+
+ [Test]
+ procedure TestOIOUBLCreditNote_ReturnsCreditMemoProcessDraftImpl()
+ var
+ EDocument: Record "E-Document";
+ begin
+ // [SCENARIO] An OIOUBL CreditNote selects the purchase credit memo draft implementation
+ Initialize(Enum::"Service Integration"::"No Integration");
+ SetupOIOUBLEDocumentService();
+ CreateInboundEDocumentFromXML(EDocument, CreditNoteTestFileTok);
+
+ if ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft") then begin
+ EDocument.Get(EDocument."Entry No");
+ Assert.AreEqual(Enum::"E-Doc. Process Draft"::"Purchase Credit Memo", EDocument."Process Draft Impl.", 'The process draft implementation should be set to Purchase Credit Memo for credit notes.');
+ end else
+ Assert.Fail(EDocumentStatusNotUpdatedErr);
+ end;
+
+ [Test]
+ procedure TestOIOUBLCreditNote_ExtractsPaymentDueDate()
+ var
+ EDocument: Record "E-Document";
+ begin
+ // [SCENARIO] An OIOUBL CreditNote reads its due date from PaymentMeans
+ Initialize(Enum::"Service Integration"::"No Integration");
+ SetupOIOUBLEDocumentService();
+ CreateInboundEDocumentFromXML(EDocument, CreditNoteTestFileTok);
+
+ if ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft") then
+ OIOUBLStructuredValidations.AssertCreditNoteDueDate(EDocument."Entry No")
+ else
+ Assert.Fail(EDocumentStatusNotUpdatedErr);
+ end;
+
+ [Test]
+ procedure TestOIOUBLCreditNote_ExtractsExternalInvoiceReference()
+ var
+ EDocument: Record "E-Document";
+ begin
+ // [SCENARIO] An OIOUBL CreditNote stores BillingReference as the vendor's external invoice number
+ Initialize(Enum::"Service Integration"::"No Integration");
+ SetupOIOUBLEDocumentService();
+ CreateInboundEDocumentFromXML(EDocument, CreditNoteTestFileTok);
+
+ if ProcessEDocumentToStep(EDocument, "Import E-Document Steps"::"Read into Draft") then
+ OIOUBLStructuredValidations.AssertCreditNoteExternalInvoiceReference(EDocument."Entry No")
+ else
+ Assert.Fail(EDocumentStatusNotUpdatedErr);
+ end;
+
+ [Test]
+ procedure TestOIOUBLUnsupportedRootElement_IsRejected()
+ var
+ EDocument: Record "E-Document";
+ TempBlob: Codeunit "Temp Blob";
+ StructuredFormatReader: Interface IStructuredFormatReader;
+ XmlOutStream: OutStream;
+ begin
+ // [SCENARIO] An unsupported OIOUBL document type is rejected instead of creating an empty draft
+ Initialize(Enum::"Service Integration"::"No Integration");
+ LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService);
+ TempBlob.CreateOutStream(XmlOutStream, TextEncoding::UTF8);
+ XmlOutStream.WriteText('');
+ StructuredFormatReader := Enum::"E-Doc. Read into Draft"::OIOUBL;
+
+ asserterror StructuredFormatReader.ReadIntoDraft(EDocument, TempBlob);
+
+ Assert.ExpectedError(StrSubstNo(UnsupportedXmlRootElementErr, 'Reminder'));
+ Assert.ExpectedErrorCode('Dialog');
+ end;
+
+ [Test]
+ procedure TestOIOUBLUnexpectedRootNamespace_IsRejected()
+ var
+ EDocument: Record "E-Document";
+ TempBlob: Codeunit "Temp Blob";
+ StructuredFormatReader: Interface IStructuredFormatReader;
+ XmlOutStream: OutStream;
+ begin
+ // [SCENARIO] An Invoice from an unsupported namespace is rejected instead of creating an empty draft
+ Initialize(Enum::"Service Integration"::"No Integration");
+ LibraryEDoc.CreateInboundEDocument(EDocument, EDocumentService);
+ TempBlob.CreateOutStream(XmlOutStream, TextEncoding::UTF8);
+ XmlOutStream.WriteText('');
+ StructuredFormatReader := Enum::"E-Doc. Read into Draft"::OIOUBL;
+
+ asserterror StructuredFormatReader.ReadIntoDraft(EDocument, TempBlob);
+
+ Assert.ExpectedError(StrSubstNo(UnsupportedXmlRootElementErr, 'Invoice'));
+ Assert.ExpectedErrorCode('Dialog');
+ end;
+
+ [Test]
+ [HandlerFunctions('EDocumentPurchaseHeaderPageHandler')]
+ procedure TestOIOUBLInvoice_ValidDocument_ViewExtractedData()
+ var
+ EDocument: Record "E-Document";
+ EDocImport: Codeunit "E-Doc. Import";
+ begin
+ // [FEATURE] [E-Document] [OIOUBL] [View Data]
+ // [SCENARIO] View extracted data from a valid OIOUBL invoice document
+
+ // [GIVEN] A valid OIOUBL XML invoice document is imported
+ Initialize(Enum::"Service Integration"::"No Integration");
+ SetupOIOUBLEDocumentService();
+ 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 TestOIOUBLInvoice_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] [OIOUBL] [Purchase Invoice Creation]
+ // [SCENARIO] Create a purchase invoice from a valid OIOUBL invoice document
+
+ // [GIVEN] A valid OIOUBL XML invoice document is imported
+ Initialize(Enum::"Service Integration"::"No Integration");
+ Vendor."VAT Registration No." := 'GB123456789';
+ Vendor.Modify(true);
+ SetupOIOUBLEDocumentService();
+ 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 OIOUBL data
+ OIOUBLStructuredValidations.SetMockCurrencyCode(MockCurrencyCode);
+ OIOUBLStructuredValidations.SetMockDate(MockDate);
+ OIOUBLStructuredValidations.AssertPurchaseDocument(Vendor."No.", PurchaseHeader, DummyItem);
+ end;
+
+ [Test]
+ procedure TestOIOUBLInvoice_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] [OIOUBL] [Draft Update]
+ // [SCENARIO] Update draft purchase document data and finalize processing
+
+ // [GIVEN] A valid OIOUBL XML invoice document is imported and processed to draft preparation
+ Initialize(Enum::"Service Integration"::"No Integration");
+ Vendor."VAT Registration No." := 'GB123456789';
+ Vendor.Modify(true);
+ SetupOIOUBLEDocumentService();
+ 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 OIOUBL data and manual updates
+ OIOUBLStructuredValidations.SetMockCurrencyCode(MockCurrencyCode);
+ OIOUBLStructuredValidations.SetMockDate(MockDate);
+ OIOUBLStructuredValidations.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"::OIOUBL, Integration);
+ LibraryEDoc.SetupStandardPurchaseScenario(Vendor, EDocumentService, Enum::"E-Document Format"::OIOUBL, Integration);
+ EDocumentService."Import Process" := "E-Document Import Process"::"Version 2.0";
+ EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::OIOUBL;
+ EDocumentService.Modify(false);
+ 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 SetupOIOUBLEDocumentService()
+ begin
+ EDocumentService."Read into Draft Impl." := "E-Doc. Read into Draft"::OIOUBL;
+ 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;
+}
diff --git a/src/Apps/DK/EDocumentFormatOIOUBL/test/src/OIOUBLStructuredValidations.Codeunit.al b/src/Apps/DK/EDocumentFormatOIOUBL/test/src/OIOUBLStructuredValidations.Codeunit.al
new file mode 100644
index 00000000000..b99b4062102
--- /dev/null
+++ b/src/Apps/DK/EDocumentFormatOIOUBL/test/src/OIOUBLStructuredValidations.Codeunit.al
@@ -0,0 +1,135 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+codeunit 148062 "OIOUBL Structured Validations"
+{
+ var
+ Assert: Codeunit Assert;
+ UnitOfMeasureCodeTok: Label 'PCS', Locked = true;
+ SalesInvoiceNoTok: Label '103033', Locked = true;
+ PurchaseorderNoTok: Label '2', Locked = true;
+ Item1NoTok: Label 'GL00000001', Locked = true;
+ Item2NoTok: Label 'GL00000003', 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";
+ 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;
+
+ internal procedure AssertCreditNoteDueDate(EDocumentEntryNo: Integer)
+ var
+ EDocumentPurchaseHeader: Record "E-Document Purchase Header";
+ begin
+ EDocumentPurchaseHeader.Get(EDocumentEntryNo);
+ Assert.AreEqual(DMY2Date(15, 3, 2026), EDocumentPurchaseHeader."Due Date", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Due Date"), EDocumentPurchaseHeader.TableCaption(), DMY2Date(15, 3, 2026), EDocumentPurchaseHeader."Due Date"));
+ end;
+
+ internal procedure AssertCreditNoteExternalInvoiceReference(EDocumentEntryNo: Integer)
+ var
+ EDocumentPurchaseHeader: Record "E-Document Purchase Header";
+ begin
+ EDocumentPurchaseHeader.Get(EDocumentEntryNo);
+ Assert.AreEqual(SalesInvoiceNoTok, EDocumentPurchaseHeader."Applies-to Ext. Invoice No.", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Applies-to Ext. Invoice No."), EDocumentPurchaseHeader.TableCaption(), SalesInvoiceNoTok, EDocumentPurchaseHeader."Applies-to Ext. Invoice No."));
+ Assert.AreEqual('', EDocumentPurchaseHeader."Applies-to Doc. No.", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Applies-to Doc. No."), EDocumentPurchaseHeader.TableCaption(), '', EDocumentPurchaseHeader."Applies-to Doc. No."));
+ end;
+
+ internal procedure AssertPurchaseLineCount(EDocumentEntryNo: Integer; ExpectedCount: Integer)
+ var
+ EDocumentPurchaseLine: Record "E-Document Purchase Line";
+ begin
+ EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo);
+ Assert.RecordCount(EDocumentPurchaseLine, ExpectedCount);
+ end;
+
+ procedure SetMockDate(MockDate: Date)
+ begin
+ this.MockDate := MockDate;
+ end;
+
+ procedure SetMockCurrencyCode(MockCurrencyCode: Code[10])
+ begin
+ this.MockCurrencyCode := MockCurrencyCode;
+ end;
+}
diff --git a/src/Apps/W1/EDocument/App/app.json b/src/Apps/W1/EDocument/App/app.json
index 298740e589e..16f919f5b5e 100644
--- a/src/Apps/W1/EDocument/App/app.json
+++ b/src/Apps/W1/EDocument/App/app.json
@@ -54,6 +54,11 @@
"id": "8238f78b-cbe5-4b9c-b247-7771145b7470",
"name": "E-Document Format for Factura-E Tests",
"publisher": "Microsoft"
+ },
+ {
+ "id": "f78b9487-b911-4db1-893b-5a5c310919b8",
+ "name": "E-Document Format for OIOUBL Tests",
+ "publisher": "Microsoft"
}
],
"screenshots": [],