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": [],