From 1345cfcdd53fe3c671a57e96f93c8f051ee3a2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Fri, 1 Aug 2025 14:36:08 +0300 Subject: [PATCH 1/9] Adds OIOUBL electronic document format support Implements comprehensive OIOUBL (Danish UBL) format handling for electronic document processing. Introduces structured format reader for parsing OIOUBL invoices, credit notes, and reminders into draft purchase documents. Supports multiple vendor lookup strategies including VAT registration number, GLN, and participant identifier matching. Extends document type enumeration to support purchase credit memos and issued reminders with appropriate draft finishing implementations. Enables Danish businesses to process incoming electronic documents in the standardized OIOUBL format through the existing e-document framework. --- .../src/EDocumentOIOUBLHandler.Codeunit.al | 447 ++++++++++++++++++ .../src/OIOUBLEDocReadIntoDraft.EnumExt.al | 17 + Apps/W1/EDocument/app/app.json | 5 + .../app/src/Document/EDocumentType.Enum.al | 2 + .../EDocCreatePurchaseInvoice.Codeunit.al | 9 +- 5 files changed, 476 insertions(+), 4 deletions(-) create mode 100644 Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al create mode 100644 Apps/DK/EDocumentFormatOIOUBL/app/src/OIOUBLEDocReadIntoDraft.EnumExt.al diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al new file mode 100644 index 0000000000..9a31fc5582 --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -0,0 +1,447 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Interfaces; +using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.eServices.EDocument.Service.Participant; +using Microsoft.eServices.EDocument.Helpers; +using Microsoft.Purchases.Vendor; +using System.Utilities; + +codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + var + EDocumentImportHelper: Codeunit "E-Document Import Helper"; + + /// + /// Reads an OIOUBL electronic document into a draft purchase document. + /// Parses XML content and populates purchase header and lines based on document type (Invoice, Credit Note, or Reminder). + /// + /// The E-Document record to process. + /// The temporary blob containing the XML content to parse. + /// Returns the process draft type indicating a Purchase Document was created. + internal procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + OIOUBLXml: XmlDocument; + XmlNamespaces: XmlNamespaceManager; + XmlElement: XmlElement; + CommonAggregateComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'; + CommonBasicComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'; + DefaultInvoiceLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'; + DefaultCreditNoteLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'; + DefaultReminderLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:Reminder-2'; + InvoiceLbl: Label 'INVOICE', Locked = true; + CreditNoteLbl: Label 'CREDITNOTE', Locked = true; + ReminderLbl: Label 'REMINDER', Locked = true; + begin + EDocumentPurchaseHeader.InsertForEDocument(EDocument); + + XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), OIOUBLXml); + + // Setup XML namespaces for OIOUBL v2.0 (UBL 2.1 based) + XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsLbl); + XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsLbl); + XmlNamespaces.AddNamespace('inv', DefaultInvoiceLbl); + XmlNamespaces.AddNamespace('cn', DefaultCreditNoteLbl); + XmlNamespaces.AddNamespace('rem', DefaultReminderLbl); + + OIOUBLXml.GetRoot(XmlElement); + case UpperCase(XmlElement.LocalName()) of + InvoiceLbl: + PopulateEDocumentForInvoice(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + CreditNoteLbl: + PopulateEDocumentForCreditNote(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + ReminderLbl: + PopulateEDocumentForReminder(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); + end; + + EDocumentPurchaseHeader.Modify(false); + EDocument.Direction := EDocument.Direction::Incoming; + exit(Enum::"E-Doc. Process Draft"::"Purchase Document"); + end; + + /// + /// Opens a page to view the readable purchase document content for the specified E-Document. + /// Displays purchase header and line information in a user-friendly format. + /// + /// The E-Document record to view. + /// The temporary blob containing the document content (not used in current implementation). + internal procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") + var + EDocPurchaseHeader: Record "E-Document Purchase Header"; + EDocPurchaseLine: Record "E-Document Purchase Line"; + EDocReadablePurchaseDoc: Page "E-Doc. Readable Purchase Doc."; + begin + EDocPurchaseHeader.GetFromEDocument(EDocument); + EDocPurchaseLine.SetRange("E-Document Entry No.", EDocPurchaseHeader."E-Document Entry No."); + EDocReadablePurchaseDoc.SetBuffer(EDocPurchaseHeader, EDocPurchaseLine); + EDocReadablePurchaseDoc.Run(); + end; + + local procedure PopulateEDocumentForInvoice(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + VendorNo: Code[20]; + begin + EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Invoice"; +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + // Basic document information + 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"); + EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); + + // Order reference + EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); +#pragma warning restore AA0139 + + // Monetary totals + 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"); + + // Calculate VAT + EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; + + // Parse parties + VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'inv:Invoice'); + ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'inv:Invoice'); + + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + + // Insert lines + InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); + end; + + local procedure PopulateEDocumentForCreditNote(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + VendorNo: Code[20]; + begin + EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Credit Memo"; +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + // Basic document information + 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/cbc:DueDate', EDocumentPurchaseHeader."Due Date"); + EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); + + // Order reference + EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); +#pragma warning restore AA0139 + + // Monetary totals + 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"); + + // Calculate VAT + EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; + + // Parse parties + VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'cn:CreditNote'); + ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'cn:CreditNote'); + + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + + // Insert lines + InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); + end; + + local procedure PopulateEDocumentForReminder(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocument: Record "E-Document") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + VendorNo: Code[20]; + begin + EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Issued Reminder"; +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + // Basic document information + EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); + EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); + EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cac:PaymentMeans/cbc:PaymentDueDate', EDocumentPurchaseHeader."Due Date"); + EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); +#pragma warning restore AA0139 + + // Monetary totals for reminder + EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); + EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader.Total); + + // Calculate VAT + EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; + + // Parse parties + VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'rem:Reminder'); + ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'rem:Reminder'); + + if VendorNo <> '' then + EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; + + // Insert lines + InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); + 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 "EDocument XML Helper"; + VendorName, VendorAddress, VendorParticipantId : Text; + VATRegistrationNo: Text[20]; + EndpointID, SchemeID : Text; + GLN: Code[13]; + BasePathTxt: Text; + XMLNode: XmlNode; + begin +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + BasePathTxt := '/' + DocumentType + '/cac:AccountingSupplierParty/cac:Party'; + + // Extract basic vendor information + 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"); + + // Extract VAT registration number + EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Vendor VAT Id"), EDocumentPurchaseHeader."Vendor VAT Id"); + + // Extract contact information + EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:Contact/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Contact Name"), EDocumentPurchaseHeader."Vendor Contact Name"); +#pragma warning restore AA0139 + + // Extract endpoint information for OIOUBL specific identification + 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 + 'DK:CVR': + VATRegistrationNo := CopyStr(EndpointID, 1, MaxStrLen(VATRegistrationNo)); + 'GLN': + begin + GLN := CopyStr(EndpointID, 1, MaxStrLen(GLN)); + EDocumentPurchaseHeader."Vendor GLN" := GLN; + end; + end; + VendorParticipantId := SchemeID + ':' + EndpointID; + end; + + // Vendor lookup strategy (multiple fallback methods) + VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, 20); + VendorName := EDocumentPurchaseHeader."Vendor Company Name"; + VendorAddress := EDocumentPurchaseHeader."Vendor Address"; + + if not FindVendorByVATRegNoOrGLN(VendorNo, VATRegistrationNo, GLN) then + if not FindVendorByParticipantId(VendorNo, EDocument, VendorParticipantId) then + VendorNo := EDocumentImportHelper.FindVendorByNameAndAddress(VendorName, VendorAddress); + end; + + local procedure ParseAccountingCustomerParty(OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; DocumentType: Text) + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + BasePathTxt: Text; + XMLNode: XmlNode; + SchemeID, EndpointID : Text; + begin +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + BasePathTxt := '/' + DocumentType + '/cac:AccountingCustomerParty/cac:Party'; + + // Extract basic customer information + 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"); + + // Extract VAT registration number + EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id"), EDocumentPurchaseHeader."Customer VAT Id"); +#pragma warning restore AA0139 + + // Extract GLN from endpoint information + 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 = 'GLN' 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 + // Set up XPath and element name based on document type and actual XML structure + case DocumentType of + "E-Document Type"::"Purchase Invoice": + begin + LineXPath := '/inv:Invoice/cac:InvoiceLine'; + LineElementName := 'cac:InvoiceLine'; + end; + "E-Document Type"::"Purchase Credit Memo": + begin + LineXPath := '/cn:CreditNote/cac:CreditNoteLine'; + LineElementName := 'cac:CreditNoteLine'; + end; + "E-Document Type"::"Issued Reminder": + begin + LineXPath := '/rem:Reminder/cac:ReminderLine'; + LineElementName := 'cac:ReminderLine'; + 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); + + // Call appropriate population method based on line element type + if LineElementName = 'cac:ReminderLine' then + PopulateOIOUBLReminderLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine) + else + 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 "EDocument XML Helper"; + LineIdFieldName: Text; + QuantityFieldName: Text; + begin +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + // Set up field names based on document type + case LineElementName of + 'cac:InvoiceLine': + begin + LineIdFieldName := 'cac:InvoiceLine/cac:Item/cac:SellersItemIdentification/cbc:ID'; + QuantityFieldName := 'cac:InvoiceLine/cbc:InvoicedQuantity'; + end; + 'cac:CreditNoteLine': + begin + LineIdFieldName := 'cac:CreditNoteLine/cac:Item/cac:SellersItemIdentification/cbc:ID'; + QuantityFieldName := 'cac:CreditNoteLine/cbc:CreditedQuantity'; + end; + end; + + // Product Code from Line ID + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineIdFieldName, MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); + + // Line description and product information + 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"); + + // Quantities and amounts + 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"); + + // Currency and VAT + 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 PopulateOIOUBLReminderLine(LineXML: XmlDocument; XmlNamespaces: XmlNamespaceManager; var EDocumentPurchaseLine: Record "E-Document Purchase Line") + var + EDocumentXMLHelper: Codeunit "EDocument XML Helper"; + DebitAmount, CreditAmount : Decimal; + DebitAmountText, CreditAmountText : Text; + begin +#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField + // Line description and note + EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:Note', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); +#pragma warning restore AA0139 + + // Handle debit amount + DebitAmountText := EDocumentXMLHelper.GetNodeValue(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:DebitLineAmount'); + if DebitAmountText <> '' then begin + Evaluate(DebitAmount, DebitAmountText, 9); + EDocumentPurchaseLine."Unit Price" := DebitAmount; + // Get currency from debit amount + EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:DebitLineAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); + end; + + // Handle credit amount (negative) + CreditAmountText := EDocumentXMLHelper.GetNodeValue(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:CreditLineAmount'); + if CreditAmountText <> '' then begin + Evaluate(CreditAmount, CreditAmountText, 9); + EDocumentPurchaseLine."Unit Price" := -CreditAmount; // Credit amounts should be negative + // Get currency from credit amount if debit currency wasn't set + if EDocumentPurchaseLine."Currency Code" = '' then + EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:CreditLineAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); + end; + + // Set quantity to 1 for reminder lines and calculate subtotal + EDocumentPurchaseLine.Quantity := 1; + EDocumentPurchaseLine."Sub Total" := EDocumentPurchaseLine."Unit Price" * EDocumentPurchaseLine.Quantity; + end; + + local procedure FindVendorByVATRegNoOrGLN(var VendorNo: Code[20]; VATRegistrationNo: Text[20]; GLN: Code[13]): Boolean + var + Vendor: Record Vendor; + begin + // Try to find vendor by VAT Registration Number + if VATRegistrationNo <> '' then begin + Vendor.Reset(); + Vendor.SetLoadFields("VAT Registration No."); + Vendor.SetRange("VAT Registration No.", VATRegistrationNo); + if Vendor.FindFirst() then begin + VendorNo := Vendor."No."; + exit(true); + end; + end; + + // Try to find vendor by GLN + if GLN <> '' then begin + Vendor.Reset(); + Vendor.SetLoadFields("GLN"); + Vendor.SetRange("GLN", GLN); + if Vendor.FindFirst() then begin + VendorNo := Vendor."No."; + exit(true); + end; + end; + + exit(false); + end; + + local procedure FindVendorByParticipantId(var VendorNo: Code[20]; EDocument: Record "E-Document"; ParticipantId: Text): Boolean + var + EDocServiceParticipant: Record "Service Participant"; + EDocumentService: Record "E-Document Service"; + EDocumentHelper: Codeunit "E-Document Helper"; + begin + if ParticipantId = '' then + exit(false); + + EDocumentHelper.GetEdocumentService(EDocument, EDocumentService); + EDocServiceParticipant.SetRange("Participant Type", EDocServiceParticipant."Participant Type"::Vendor); + EDocServiceParticipant.SetRange("Participant Identifier", ParticipantId); + EDocServiceParticipant.SetRange(Service, EDocumentService.Code); + if not EDocServiceParticipant.FindFirst() then begin + EDocServiceParticipant.SetRange(Service); + if not EDocServiceParticipant.FindFirst() then + exit(false); + end; + + VendorNo := EDocServiceParticipant.Participant; + exit(true); + end; +} diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/OIOUBLEDocReadIntoDraft.EnumExt.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/OIOUBLEDocReadIntoDraft.EnumExt.al new file mode 100644 index 0000000000..7f3be0041a --- /dev/null +++ b/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/Apps/W1/EDocument/app/app.json b/Apps/W1/EDocument/app/app.json index cab223c598..f89fc34b34 100644 --- a/Apps/W1/EDocument/app/app.json +++ b/Apps/W1/EDocument/app/app.json @@ -32,6 +32,11 @@ "id": "967eceac-106e-4102-b76a-fe5dc3d799e5", "name": "Payables Agent Tests", "publisher": "Microsoft" + }, + { + "id": "8228f99b-cce5-4b9c-b247-9ee1145b7470", + "name": "E-Document format for OIOUBL", + "publisher": "Microsoft" } ], "screenshots": [], diff --git a/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al b/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al index 08b282174e..8b16c92970 100644 --- a/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al +++ b/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al @@ -56,6 +56,7 @@ enum 6121 "E-Document Type" implements IEDocumentFinishDraft value(10; "Purchase Credit Memo") { Caption = 'Purchase Credit Memo'; + Implementation = IEDocumentFinishDraft = "E-Doc. Create Purch. Cr. Memo"; } value(11; "Service Order") { @@ -84,6 +85,7 @@ enum 6121 "E-Document Type" implements IEDocumentFinishDraft value(17; "Issued Reminder") { Caption = 'Issued Reminder'; + Implementation = IEDocumentFinishDraft = "E-Doc. Create Purchase Invoice"; } value(18; "General Journal") { diff --git a/Apps/W1/EDocument/app/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al b/Apps/W1/EDocument/app/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al index 41ef481260..dfc78fa92e 100644 --- a/Apps/W1/EDocument/app/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al +++ b/Apps/W1/EDocument/app/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al @@ -88,10 +88,11 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, GlobalDim1, GlobalDim2 : Code[20]; begin EDocumentPurchaseHeader.GetFromEDocument(EDocument); - if not AllDraftLinesHaveTypeAndNumberSpecificed(EDocumentPurchaseHeader) then begin - Telemetry.LogMessage('0000PLY', 'Draft line does not contain type or number', Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::All); - Error(DraftLineDoesNotConstantTypeAndNumberErr); - end; + if EDocumentPurchaseHeader."E-Document Type" <> "E-Document Type"::"Issued Reminder" then + if not AllDraftLinesHaveTypeAndNumberSpecificed(EDocumentPurchaseHeader) then begin + Telemetry.LogMessage('0000PLY', 'Draft line does not contain type or number', Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::All); + Error(DraftLineDoesNotConstantTypeAndNumberErr); + end; EDocumentPurchaseHeader.TestField("E-Document Entry No."); PurchaseHeader.SetRange("Buy-from Vendor No.", EDocumentPurchaseHeader."[BC] Vendor No."); // Setting the filter, so that the insert trigger assigns the right vendor to the purchase header PurchaseHeader."Document Type" := "Purchase Document Type"::Invoice; From 92eb13e1c0f4fedf8192ea67413997e974c136f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:41:53 +0300 Subject: [PATCH 2/9] Update Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/EDocumentOIOUBLHandler.Codeunit.al | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al index 9a31fc5582..e6cdcaef97 100644 --- a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -236,7 +236,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, 20); VendorName := EDocumentPurchaseHeader."Vendor Company Name"; VendorAddress := EDocumentPurchaseHeader."Vendor Address"; - if not FindVendorByVATRegNoOrGLN(VendorNo, VATRegistrationNo, GLN) then if not FindVendorByParticipantId(VendorNo, EDocument, VendorParticipantId) then VendorNo := EDocumentImportHelper.FindVendorByNameAndAddress(VendorName, VendorAddress); From c3e1b7942a4dce237cfc63b9aa5cfcebdaab07b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:42:46 +0300 Subject: [PATCH 3/9] Delete Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al --- .../app/src/Document/EDocumentType.Enum.al | 106 ------------------ 1 file changed, 106 deletions(-) delete mode 100644 Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al diff --git a/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al b/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al deleted file mode 100644 index 8b16c92970..0000000000 --- a/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al +++ /dev/null @@ -1,106 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// 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; - -enum 6121 "E-Document Type" implements IEDocumentFinishDraft -{ - Extensible = true; - DefaultImplementation = IEDocumentFinishDraft = "E-Doc. Unspecified Impl."; - - value(0; "None") - { - Caption = 'None'; - } - value(1; "Sales Quote") - { - Caption = 'Sales Quote'; - } - value(2; "Sales Order") - { - Caption = 'Sales Order'; - } - value(3; "Sales Invoice") - { - Caption = 'Sales Invoice'; - } - value(4; "Sales Return Order") - { - Caption = 'Sales Return Order'; - } - value(5; "Sales Credit Memo") - { - Caption = 'Sales Credit Memo'; - } - value(6; "Purchase Quote") - { - Caption = 'Purchase Quote'; - } - value(7; "Purchase Order") - { - Caption = 'Purchase Order'; - } - value(8; "Purchase Invoice") - { - Caption = 'Purchase Invoice'; - Implementation = IEDocumentFinishDraft = "E-Doc. Create Purchase Invoice"; - } - value(9; "Purchase Return Order") - { - Caption = 'Purchase Return Order'; - } - value(10; "Purchase Credit Memo") - { - Caption = 'Purchase Credit Memo'; - Implementation = IEDocumentFinishDraft = "E-Doc. Create Purch. Cr. Memo"; - } - value(11; "Service Order") - { - Caption = 'Service Order'; - } - value(12; "Service Invoice") - { - Caption = 'Service Invoice'; - } - value(13; "Service Credit Memo") - { - Caption = 'Service Credit Memo'; - } - value(14; "Finance Charge Memo") - { - Caption = 'Finance Charge Memo'; - } - value(15; "Issued Finance Charge Memo") - { - Caption = 'Issued Finance Charge Memo'; - } - value(16; "Reminder") - { - Caption = 'Reminder'; - } - value(17; "Issued Reminder") - { - Caption = 'Issued Reminder'; - Implementation = IEDocumentFinishDraft = "E-Doc. Create Purchase Invoice"; - } - value(18; "General Journal") - { - Caption = 'General Journal'; - } - value(19; "G/L Entry") - { - Caption = 'G/L Entry'; - } - value(20; "Sales Shipment") - { - Caption = 'Sales Shipment'; - } - value(21; "Transfer Shipment") - { - Caption = 'Transfer Shipment'; - } -} From 9fbdc4002e51b3a8836f7e5f5550f8a7f7061017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Wed, 13 Aug 2025 18:46:15 +0300 Subject: [PATCH 4/9] Update Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/EDocumentOIOUBLHandler.Codeunit.al | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al index e6cdcaef97..14e6624f95 100644 --- a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -115,7 +115,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader // Parse parties VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'inv:Invoice'); ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'inv:Invoice'); - if VendorNo <> '' then EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; From 94ddb1b5cdd8011fab7cebf225dbeafe562229db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Wed, 13 Aug 2025 18:48:04 +0300 Subject: [PATCH 5/9] Update Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/EDocumentOIOUBLHandler.Codeunit.al | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al index 14e6624f95..925b827b8e 100644 --- a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -150,7 +150,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader // Parse parties VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'cn:CreditNote'); ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'cn:CreditNote'); - if VendorNo <> '' then EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; From 7902126aa2658634de49d98e0fde5334a6a9ecea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= <30231314+AndriusAndrulevicius@users.noreply.github.com> Date: Wed, 13 Aug 2025 18:48:15 +0300 Subject: [PATCH 6/9] Update Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al Co-authored-by: Grasiele Matuleviciute <131970463+GMatuleviciute@users.noreply.github.com> --- .../app/src/EDocumentOIOUBLHandler.Codeunit.al | 1 - 1 file changed, 1 deletion(-) diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al index 925b827b8e..a68a947fe0 100644 --- a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -181,7 +181,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader // Parse parties VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'rem:Reminder'); ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'rem:Reminder'); - if VendorNo <> '' then EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; From ae85dee65b4f6463b7710a2b953cd88a18d1d5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 13 Aug 2025 19:04:34 +0300 Subject: [PATCH 7/9] Refactors OIOUBL handler with new enum and cleanup Introduces new E-Document Type enum with extensible implementation support and proper interface assignments for purchase invoice and reminder processing. Improves code maintainability by converting label variables to token constants with proper naming conventions and removes redundant comments throughout the XML parsing logic. Adds support for credit note billing reference extraction and consolidates duplicate XML processing code for better readability. --- .../src/EDocumentOIOUBLHandler.Codeunit.al | 143 +++++------------- .../app/src/Document/EDocumentType.Enum.al | 105 +++++++++++++ 2 files changed, 143 insertions(+), 105 deletions(-) create mode 100644 Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al index a68a947fe0..39a5ee4b18 100644 --- a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -20,47 +20,46 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader var EDocumentImportHelper: Codeunit "E-Document Import Helper"; + GLNTok: Label 'GLN', Locked = true; + InvoiceLineTok: Label 'cac:InvoiceLine', Locked = true; + CreditNoteLineTok: Label 'cac:CreditNoteLine', Locked = true; /// - /// Reads an OIOUBL electronic document into a draft purchase document. - /// Parses XML content and populates purchase header and lines based on document type (Invoice, Credit Note, or Reminder). + /// Reads an OIOUBL format XML document and converts it into a draft purchase document. + /// This procedure processes Invoice, CreditNote and Reminder document types and populates the E-Document Purchase Header with the extracted data. /// - /// The E-Document record to process. - /// The temporary blob containing the XML content to parse. - /// Returns the process draft type indicating a Purchase Document was created. + /// The E-Document record that contains the document metadata and information. + /// A temporary blob containing the XML document stream to be processed. + /// Returns an enum indicating that the process resulted in a purchase document draft. internal procedure ReadIntoDraft(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob"): Enum "E-Doc. Process Draft" var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; OIOUBLXml: XmlDocument; XmlNamespaces: XmlNamespaceManager; XmlElement: XmlElement; - CommonAggregateComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'; - CommonBasicComponentsLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'; - DefaultInvoiceLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'; - DefaultCreditNoteLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2'; - DefaultReminderLbl: Label 'urn:oasis:names:specification:ubl:schema:xsd:Reminder-2'; - InvoiceLbl: Label 'INVOICE', Locked = true; - CreditNoteLbl: Label 'CREDITNOTE', Locked = true; - ReminderLbl: Label 'REMINDER', 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; + DefaultReminderTok: Label 'urn:oasis:names:specification:ubl:schema:xsd:Reminder-2', Locked = true; begin EDocumentPurchaseHeader.InsertForEDocument(EDocument); XmlDocument.ReadFrom(TempBlob.CreateInStream(TextEncoding::UTF8), OIOUBLXml); - // Setup XML namespaces for OIOUBL v2.0 (UBL 2.1 based) - XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsLbl); - XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsLbl); - XmlNamespaces.AddNamespace('inv', DefaultInvoiceLbl); - XmlNamespaces.AddNamespace('cn', DefaultCreditNoteLbl); - XmlNamespaces.AddNamespace('rem', DefaultReminderLbl); + XmlNamespaces.AddNamespace('cac', CommonAggregateComponentsTok); + XmlNamespaces.AddNamespace('cbc', CommonBasicComponentsTok); + XmlNamespaces.AddNamespace('inv', DefaultInvoiceTok); + XmlNamespaces.AddNamespace('cn', DefaultCreditNoteTok); + XmlNamespaces.AddNamespace('rem', DefaultReminderTok); OIOUBLXml.GetRoot(XmlElement); case UpperCase(XmlElement.LocalName()) of - InvoiceLbl: + 'INVOICE': PopulateEDocumentForInvoice(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); - CreditNoteLbl: + 'CREDITNOTE': PopulateEDocumentForCreditNote(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); - ReminderLbl: + 'REMINDER': PopulateEDocumentForReminder(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument); end; @@ -70,11 +69,11 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader end; /// - /// Opens a page to view the readable purchase document content for the specified E-Document. - /// Displays purchase header and line information in a user-friendly format. + /// Displays a readable view of the processed E-Document purchase information. + /// This procedure opens a page showing the purchase header and lines in a user-friendly format for review. /// - /// The E-Document record to view. - /// The temporary blob containing the document content (not used in current implementation). + /// The E-Document record that contains the document to be displayed. + /// A temporary blob containing the document data (not used in current implementation). internal procedure View(EDocument: Record "E-Document"; TempBlob: Codeunit "Temp Blob") var EDocPurchaseHeader: Record "E-Document Purchase Header"; @@ -93,32 +92,21 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader VendorNo: Code[20]; begin EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Invoice"; -#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - // Basic document information +#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"); EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); - - // Order reference EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/inv:Invoice/cac:OrderReference/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Purchase Order No."), EDocumentPurchaseHeader."Purchase Order No."); #pragma warning restore AA0139 - - // Monetary totals 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"); - - // Calculate VAT EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; - - // Parse parties VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'inv:Invoice'); ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'inv:Invoice'); if VendorNo <> '' then EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; - - // Insert lines InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); end; @@ -128,32 +116,22 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader VendorNo: Code[20]; begin EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Purchase Credit Memo"; -#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - // Basic document information +#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/cbc:DueDate', EDocumentPurchaseHeader."Due Date"); EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/cn:CreditNote/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); - - // Order reference 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 Doc. No."), EDocumentPurchaseHeader."Applies-to Doc. No."); #pragma warning restore AA0139 - - // Monetary totals 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"); - - // Calculate VAT EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; - - // Parse parties VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'cn:CreditNote'); ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'cn:CreditNote'); if VendorNo <> '' then EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; - - // Insert lines InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); end; @@ -164,33 +142,25 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader begin EDocumentPurchaseHeader."E-Document Type" := "E-Document Type"::"Issued Reminder"; #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - // Basic document information EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cbc:ID', MaxStrLen(EDocumentPurchaseHeader."Sales Invoice No."), EDocumentPurchaseHeader."Sales Invoice No."); EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cbc:IssueDate', EDocumentPurchaseHeader."Document Date"); EDocumentXMLHelper.SetDateValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cac:PaymentMeans/cbc:PaymentDueDate', EDocumentPurchaseHeader."Due Date"); EDocumentXMLHelper.SetCurrencyValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cbc:DocumentCurrencyCode', MaxStrLen(EDocumentPurchaseHeader."Currency Code"), EDocumentPurchaseHeader."Currency Code"); #pragma warning restore AA0139 - - // Monetary totals for reminder EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount', EDocumentPurchaseHeader."Sub Total"); EDocumentXMLHelper.SetNumberValueInField(OIOUBLXml, XmlNamespaces, '/rem:Reminder/cac:LegalMonetaryTotal/cbc:PayableAmount', EDocumentPurchaseHeader.Total); - - // Calculate VAT EDocumentPurchaseHeader."Total VAT" := EDocumentPurchaseHeader."Total" - EDocumentPurchaseHeader."Sub Total" - EDocumentPurchaseHeader."Total Discount"; - - // Parse parties VendorNo := ParseAccountingSupplierParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, EDocument, 'rem:Reminder'); ParseAccountingCustomerParty(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader, 'rem:Reminder'); if VendorNo <> '' then EDocumentPurchaseHeader."[BC] Vendor No." := VendorNo; - - // Insert lines InsertOIOUBLPurchaseLines(OIOUBLXml, XmlNamespaces, EDocumentPurchaseHeader."E-Document Entry No.", EDocumentPurchaseHeader."E-Document Type"); 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 "EDocument XML Helper"; + DKCVRTok: Label 'DK:CVR', Locked = true; VendorName, VendorAddress, VendorParticipantId : Text; VATRegistrationNo: Text[20]; EndpointID, SchemeID : Text; @@ -200,27 +170,19 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader begin #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField BasePathTxt := '/' + DocumentType + '/cac:AccountingSupplierParty/cac:Party'; - - // Extract basic vendor information 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"); - - // Extract VAT registration number EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Vendor VAT Id"), EDocumentPurchaseHeader."Vendor VAT Id"); - - // Extract contact information EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:Contact/cbc:Name', MaxStrLen(EDocumentPurchaseHeader."Vendor Contact Name"), EDocumentPurchaseHeader."Vendor Contact Name"); #pragma warning restore AA0139 - - // Extract endpoint information for OIOUBL specific identification 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 - 'DK:CVR': + DKCVRTok: VATRegistrationNo := CopyStr(EndpointID, 1, MaxStrLen(VATRegistrationNo)); - 'GLN': + GLNTok: begin GLN := CopyStr(EndpointID, 1, MaxStrLen(GLN)); EDocumentPurchaseHeader."Vendor GLN" := GLN; @@ -228,8 +190,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader end; VendorParticipantId := SchemeID + ':' + EndpointID; end; - - // Vendor lookup strategy (multiple fallback methods) VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, 20); VendorName := EDocumentPurchaseHeader."Vendor Company Name"; VendorAddress := EDocumentPurchaseHeader."Vendor Address"; @@ -247,22 +207,15 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader begin #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField BasePathTxt := '/' + DocumentType + '/cac:AccountingCustomerParty/cac:Party'; - - // Extract basic customer information 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"); - - // Extract VAT registration number EDocumentXMLHelper.SetStringValueInField(OIOUBLXml, XmlNamespaces, BasePathTxt + '/cac:PartyTaxScheme/cbc:CompanyID', MaxStrLen(EDocumentPurchaseHeader."Customer VAT Id"), EDocumentPurchaseHeader."Customer VAT Id"); #pragma warning restore AA0139 - - // Extract GLN from endpoint information 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 = 'GLN' then + if SchemeID = GLNTok then EDocumentPurchaseHeader."Customer GLN" := CopyStr(EndpointID, 1, MaxStrLen(EDocumentPurchaseHeader."Customer GLN")); end; end; @@ -276,17 +229,16 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader LineXPath: Text; LineElementName: Text; begin - // Set up XPath and element name based on document type and actual XML structure case DocumentType of "E-Document Type"::"Purchase Invoice": begin LineXPath := '/inv:Invoice/cac:InvoiceLine'; - LineElementName := 'cac:InvoiceLine'; + LineElementName := InvoiceLineTok; end; "E-Document Type"::"Purchase Credit Memo": begin LineXPath := '/cn:CreditNote/cac:CreditNoteLine'; - LineElementName := 'cac:CreditNoteLine'; + LineElementName := CreditNoteLineTok; end; "E-Document Type"::"Issued Reminder": begin @@ -304,7 +256,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader EDocumentPurchaseLine."Line No." := EDocumentPurchaseLine.GetNextLineNo(EDocumentEntryNo); NewLineXML.ReplaceNodes(LineXMLNode); - // Call appropriate population method based on line element type if LineElementName = 'cac:ReminderLine' then PopulateOIOUBLReminderLine(NewLineXML, XmlNamespaces, EDocumentPurchaseLine) else @@ -320,35 +271,27 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader QuantityFieldName: Text; begin #pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - // Set up field names based on document type case LineElementName of - 'cac:InvoiceLine': + InvoiceLineTok: begin LineIdFieldName := 'cac:InvoiceLine/cac:Item/cac:SellersItemIdentification/cbc:ID'; QuantityFieldName := 'cac:InvoiceLine/cbc:InvoicedQuantity'; end; - 'cac:CreditNoteLine': + CreditNoteLineTok: begin LineIdFieldName := 'cac:CreditNoteLine/cac:Item/cac:SellersItemIdentification/cbc:ID'; QuantityFieldName := 'cac:CreditNoteLine/cbc:CreditedQuantity'; end; end; - // Product Code from Line ID EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, LineIdFieldName, MaxStrLen(EDocumentPurchaseLine."Product Code"), EDocumentPurchaseLine."Product Code"); - - // Line description and product information 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"); - - // Quantities and amounts 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"); - - // Currency and VAT 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 @@ -360,31 +303,22 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader DebitAmount, CreditAmount : Decimal; DebitAmountText, CreditAmountText : Text; begin -#pragma warning disable AA0139 // false positive: overflow handled by SetStringValueInField - // Line description and note +#pragma warning disable AA0139 EDocumentXMLHelper.SetStringValueInField(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:Note', MaxStrLen(EDocumentPurchaseLine.Description), EDocumentPurchaseLine.Description); #pragma warning restore AA0139 - - // Handle debit amount DebitAmountText := EDocumentXMLHelper.GetNodeValue(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:DebitLineAmount'); if DebitAmountText <> '' then begin Evaluate(DebitAmount, DebitAmountText, 9); EDocumentPurchaseLine."Unit Price" := DebitAmount; - // Get currency from debit amount EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:DebitLineAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); end; - - // Handle credit amount (negative) CreditAmountText := EDocumentXMLHelper.GetNodeValue(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:CreditLineAmount'); if CreditAmountText <> '' then begin Evaluate(CreditAmount, CreditAmountText, 9); - EDocumentPurchaseLine."Unit Price" := -CreditAmount; // Credit amounts should be negative - // Get currency from credit amount if debit currency wasn't set + EDocumentPurchaseLine."Unit Price" := -CreditAmount; if EDocumentPurchaseLine."Currency Code" = '' then EDocumentXMLHelper.SetCurrencyValueInField(LineXML, XmlNamespaces, 'cac:ReminderLine/cbc:CreditLineAmount/@currencyID', MaxStrLen(EDocumentPurchaseLine."Currency Code"), EDocumentPurchaseLine."Currency Code"); end; - - // Set quantity to 1 for reminder lines and calculate subtotal EDocumentPurchaseLine.Quantity := 1; EDocumentPurchaseLine."Sub Total" := EDocumentPurchaseLine."Unit Price" * EDocumentPurchaseLine.Quantity; end; @@ -393,7 +327,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader var Vendor: Record Vendor; begin - // Try to find vendor by VAT Registration Number if VATRegistrationNo <> '' then begin Vendor.Reset(); Vendor.SetLoadFields("VAT Registration No."); diff --git a/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al b/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al new file mode 100644 index 0000000000..d83d6ee7ff --- /dev/null +++ b/Apps/W1/EDocument/app/src/Document/EDocumentType.Enum.al @@ -0,0 +1,105 @@ +// ------------------------------------------------------------------------------------------------ +// 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; + +enum 6121 "E-Document Type" implements IEDocumentFinishDraft +{ + Extensible = true; + DefaultImplementation = IEDocumentFinishDraft = "E-Doc. Unspecified Impl."; + + value(0; "None") + { + Caption = 'None'; + } + value(1; "Sales Quote") + { + Caption = 'Sales Quote'; + } + value(2; "Sales Order") + { + Caption = 'Sales Order'; + } + value(3; "Sales Invoice") + { + Caption = 'Sales Invoice'; + } + value(4; "Sales Return Order") + { + Caption = 'Sales Return Order'; + } + value(5; "Sales Credit Memo") + { + Caption = 'Sales Credit Memo'; + } + value(6; "Purchase Quote") + { + Caption = 'Purchase Quote'; + } + value(7; "Purchase Order") + { + Caption = 'Purchase Order'; + } + value(8; "Purchase Invoice") + { + Caption = 'Purchase Invoice'; + Implementation = IEDocumentFinishDraft = "E-Doc. Create Purchase Invoice"; + } + value(9; "Purchase Return Order") + { + Caption = 'Purchase Return Order'; + } + value(10; "Purchase Credit Memo") + { + Caption = 'Purchase Credit Memo'; + } + value(11; "Service Order") + { + Caption = 'Service Order'; + } + value(12; "Service Invoice") + { + Caption = 'Service Invoice'; + } + value(13; "Service Credit Memo") + { + Caption = 'Service Credit Memo'; + } + value(14; "Finance Charge Memo") + { + Caption = 'Finance Charge Memo'; + } + value(15; "Issued Finance Charge Memo") + { + Caption = 'Issued Finance Charge Memo'; + } + value(16; "Reminder") + { + Caption = 'Reminder'; + } + value(17; "Issued Reminder") + { + Caption = 'Issued Reminder'; + Implementation = IEDocumentFinishDraft = "E-Doc. Create Purchase Invoice"; + } + value(18; "General Journal") + { + Caption = 'General Journal'; + } + value(19; "G/L Entry") + { + Caption = 'G/L Entry'; + } + value(20; "Sales Shipment") + { + Caption = 'Sales Shipment'; + } + value(21; "Transfer Shipment") + { + Caption = 'Transfer Shipment'; + } +} From 2e467fa34252ef7e4177a0a80392470ed3cb8fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Fri, 22 Aug 2025 17:32:30 +0300 Subject: [PATCH 8/9] Localizes helper and prevents VAT truncation Scopes the import helper to the parsing routine to reduce global state and tighten dependencies. Replaces a hardcoded length with MaxStrLen when copying the VAT identifier, avoiding magic numbers and preventing unintended truncation if the field size changes. --- .../app/src/EDocumentOIOUBLHandler.Codeunit.al | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al index 39a5ee4b18..ace889f308 100644 --- a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -19,7 +19,6 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader InherentPermissions = X; var - EDocumentImportHelper: Codeunit "E-Document Import Helper"; GLNTok: Label 'GLN', Locked = true; InvoiceLineTok: Label 'cac:InvoiceLine', Locked = true; CreditNoteLineTok: Label 'cac:CreditNoteLine', Locked = true; @@ -160,6 +159,7 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader 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 "EDocument XML Helper"; + EDocumentImportHelper: Codeunit "E-Document Import Helper"; DKCVRTok: Label 'DK:CVR', Locked = true; VendorName, VendorAddress, VendorParticipantId : Text; VATRegistrationNo: Text[20]; @@ -190,7 +190,7 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader end; VendorParticipantId := SchemeID + ':' + EndpointID; end; - VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, 20); + VATRegistrationNo := CopyStr(EDocumentPurchaseHeader."Vendor VAT Id", 1, MaxStrLen(VATRegistrationNo)); VendorName := EDocumentPurchaseHeader."Vendor Company Name"; VendorAddress := EDocumentPurchaseHeader."Vendor Address"; if not FindVendorByVATRegNoOrGLN(VendorNo, VATRegistrationNo, GLN) then From c6f0dffcdc6ad907c04a7bd942a71c185df5fee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrius=20Andrulevi=C4=8Dius?= Date: Wed, 27 Aug 2025 17:33:36 +0300 Subject: [PATCH 9/9] Adds OIOUBL tests and draft reset Introduces an integration test suite that validates end-to-end import of OIOUBL invoices into draft and finalized purchase documents, including UI-driven draft edits. Adds reusable XML parsing utilities (currency, string, number, date via XPath) to simplify readers and correctly handle non-LCY currency extraction. Adds a draft reset routine to clear purchase drafts linked to an e-document for clean retries. Improves coverage and robustness of structured import, reduces parser duplication, and supports consistent behavior across localizations. --- .../src/EDocumentOIOUBLHandler.Codeunit.al | 8 + .../.resources/oioubl/oioubl-invoice-0.xml | 201 +++ .../test/ExtensionLogo.png | Bin 0 -> 5446 bytes Apps/DK/EDocumentFormatOIOUBL/test/app.json | 66 + .../test/src/EDocFormatMock.Codeunit.al | 54 + .../test/src/EDocFormatMock.EnumExt.al | 7 + .../src/EDocumentStructuredTests.Codeunit.al | 272 +++++ .../test/src/LibraryEDocument.Codeunit.al | 1081 +++++++++++++++++ .../OIOUBLStructuredValidations.Codeunit.al | 107 ++ 9 files changed, 1796 insertions(+) create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-invoice-0.xml create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/ExtensionLogo.png create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/app.json create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.Codeunit.al create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.EnumExt.al create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/src/EDocumentStructuredTests.Codeunit.al create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/src/LibraryEDocument.Codeunit.al create mode 100644 Apps/DK/EDocumentFormatOIOUBL/test/src/OIOUBLStructuredValidations.Codeunit.al diff --git a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al index ace889f308..66d8ba814b 100644 --- a/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al +++ b/Apps/DK/EDocumentFormatOIOUBL/app/src/EDocumentOIOUBLHandler.Codeunit.al @@ -373,4 +373,12 @@ codeunit 13913 "E-Document OIOUBL Handler" implements IStructuredFormatReader VendorNo := EDocServiceParticipant.Participant; exit(true); end; + + procedure ResetDraft(EDocument: Record "E-Document") + var + EDocPurchaseHeader: Record "E-Document Purchase Header"; + begin + EDocPurchaseHeader.GetFromEDocument(EDocument); + EDocPurchaseHeader.Delete(true); + end; } diff --git a/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-invoice-0.xml b/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-invoice-0.xml new file mode 100644 index 0000000000..72316a124b --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/test/.resources/oioubl/oioubl-invoice-0.xml @@ -0,0 +1,201 @@ + + + 2.0 + OIOUBL-2.01 + Procurement-OrdSimR-BilSim-1.0 + 103033 + false + 01d4dd7d-ac2b-460d-ae44-5f20d540952a + 2026-01-22 + 12:00:00 + 2026-02-22 + 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 + + + + + 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/Apps/DK/EDocumentFormatOIOUBL/test/ExtensionLogo.png b/Apps/DK/EDocumentFormatOIOUBL/test/ExtensionLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..4d2c9a626cb9617350617c40cd73904129d4c108 GIT binary patch literal 5446 zcma)=S5VVywD$iAMIcgC2u+$uktV%J6$Dhev_NQrfC^Fsq!|cJ=^|Z`P&U*^N-uuwi#_w5i!*aB*89x5SkKKnv*ua91anhEW+omc005Zp+`e`1 zOsW4C1O3^nWxbYuCX9Z!?E(M*a`E2+jm<_J0|5K}om)4pLZ&wJ&3t(K(|ffcq#?ky z#^aeQO#|lO9vyUeb0ezqQtpipl3Sj#-xy!eh7lu@5+BnW zNhL-~3Zpw&1u=bMN*Q(sgYksq4dM>Iw7p&Qk_Su~b*PgEs#LK~^K}aDaTG_6Q?_tM<8wOS}`Z+?~Et8GB>T%(k7$9`DL!d5)f!ZoXco-vj+s_QLEs2cf zKM&F>#c9w|TmM9MFtl8L*cYQgl9khf5CYMR)DJOUf;M~a9|+ys@RYR zCusNC(CSlUk|r`qdS&ZKh$O=@#&e0>;W~S#|KjHdfLx!-J9r1JtP4RGIhS|Rm0eZ6 z7eOE~Zfo4Li~K^|&)d^-r?8Rh2Q}#ZjL=?VJZ7~hlp4(!U!0K%679I`OR&x54*0&4 znho|hKu)WR)4PUVA1}N;jXHg}AG+gSKQ6O_fEP^Y51!LwBERH09|t!GNx2KH4co>r zA%cgSHxh2Sezx-w!S5DTG#0zVCbnLM6BP}2P-G{8 zh**wJHj<652FS05bSQNx-0fS7^(wREYvZwpt;$!!k4H0U*iyhS8(syBDMv>L<)~LI zPl!Y^-cM{_J@{hY1=XJ#T=Ef(FD!I^r1^lca3c0ftVuvo-(%!Zn)C1bK{}-i*Jc); zIIc+o&iMgvboj&4`@5sF23MV!*zIVmA0>{1;*H*faMAG6EZ7XydTfaGyABAGx>)yl z@Y+|)SVxCx@!GWqspay7GBetK*s2@CJ?s{8v!(b|ShLb|O;3T1rAMB?DJ?Z`@013q zoyIvV84eYiS+?kRJOz`3AFcR~ZQ1Uq7wCnbSJ%-HZwhAnJ^4zDp2W8I)~WI7ush5> z&f3O)rj~2ZGr!c@=p3!n>jG-O#9`$7&WyF7bB}(rq4ldokUp5TY?E62r+YJbJp8Jf znDW3fYZ^nBQ9O}3?zH_*mZ9+G#HHnwop1Vfm!Df~{Z%D?5KzMN&RA>&#q8iCzTfAt zV#TyMeyyh8=M$8tyA|KeUwo_Q6Si)P)%n(W-*QE~08BG|>J!sQPq?IF;;%1ypP?Z` zK_0Un>p;9=9d675ELHboC0+fNMY&(;k(|=0TS>ka)BKI3q#)zx!Jp@zv0QfeEAjU< z=vI5@-d^A^-*#|P+b2QFiGxk4z<8Tp4p6{aOp88x>SQEa0M`VxX%IUb$bya!5EgRf6$fFw zp}jNTKUXjNe0x(;)Nu)Ij5K?QD0u6~mRHQ-!;6m#VP>)}=irAqy;f$e{W-EWnR75~ zm2b0u@r7ASk4x0oTqs9{f&F|eAmD*Gf^A;te7f}J{dXqLaH_4%D_(mnp0VmWhq>^E z&7>5*-mh>FX{w5SJf^#th&GrpOQk58U-+4 zq3$q~C4ySH7@lr>W+|c0`UF*ieC+3vC1$4m}F(ic|G7}QDt(t z7`#>$c4U-4LU_;nWHhdN9Fcv~L8h6M_}nW&EGTjgW(=c}uD9>eU^rDOrkNg_effOV z^8z_y=vNIt{`wOfgG2o^3ey`R!aP1=t7Mz@&MKK3>_BH_QkgNO@4IoQ-2d8EqsDg) zTMb-5lqlubRot-7!RD@+udO?O9_Da3XV5bvjW zXTb2psHUdeiIaI(lknQE_<+YlY31}R!VfoM_BuILQ{>Q89=LB5j;V|-yAW2gY82+~ zYlu~#*R(cHw2NO1h5xaiAD2oiIEQ-aQyA-D^y^z2ZHNfM{o(3M#SbqOP3>k9FOdDO z(t%c9hk)NCPe_8>=Y^U-_-6IwS-D0cE=pwdyLp!;r-fWiXtbUS$<dl!~WV$TR8 zP$KU?K>m?*O)mSGccn&kn|nj7NXFeo<0D=ue8s^~BK#P?J~gB}v5<0nK9GPipjT#9 zkm6yXFyLlgoUIDEVxw*0Z-WDqp8swCs(bcjAqdDLl1oUqYf#a`NjT6IO3?=P`FvUZ zlWC&lWb9_dexSz%N~-oscM`oC%b#KS|KS7AptwRX5h&1VDCKWzP{&??TFdF3h53&c zU(v)WhOr)#!V6Y6d7CzOO-@KF%@67>kh34@Exj7Rh}p5_0?yUeyC7@c7DHf+mW=~wpLeLYDA9#W-Ri*S|M@g zjPHH@qHrPuzq(+5y$V*UoFEg(g$$mRNUEF!C{IN3Rig{tU54W|OD_`M0G3u)B{WhC z*D?hTF7J+YdF8-Z-Uuw{3jBx`_!aus`uDDBecwuu&tsVpj2~DZJb2-!a2l??m{}er}lR6Lqu)-2+Vm)jr(g{nfQPx9-<^1d;k-d zkU{E^g7qwp+D`b+QtU5@+swaVKp9<`>sT~U)O!EEMBo!*)~s_<`6Yl z7fX2;ki>kVDfdietW1k;TYvaY({>?5X)&(d&_y<-J7Qa@b z(zwGCI=`P#^b>1>2#Y!9T5|AdtaU|zXxw9^KpIu6CAmQf$GzaeOJmYVsc3eh5%6lb z)t~(Ak2J`;KW_L6psME-h?xF6ryr4d{q;>-b`Q$L43T{r`{N?U6cqP(Q3f%kA8`c@ z<82KXjte|7u_Lo~MV!d%y$tYi(hzU$6t+*ml~Z&Mg{eK?@}^XEBK+-&j`Uv95x)=_ zZLs=Mpg_IuZenjm(~}b8Aggaaje8NX$A_7^G%-)!xtu)C{N|S<3hVOmU;{|i+q6zn zfr(1Ua*jF!%-dU3L}O2fvWAe%-4kxtXo_vJHF(AxSx)4AI8-$^uBQO_86Z_y%RZX4 zJpu5`pOAztxv?jXv9yx|r>#9!0|`71C-fli@v${6r+V$hgvcr|W_I`{=7*0s(PKQH zzn8r2+tSeD15stz|DIJ3%X%8EkyN?bsHhuq4(5D0Oewn_)-o)Nx$eNs{0V*ZTSVt4 z3ifXGGw5fBv+9b6d~Nl+08L4VbbZqf3DL^e?l@!uZVdWkdOpJPaE?{zF!ZI?c(vF3 zvX~OK4vktvm&R$MgNpiKA~&zT!1#H7!q1h7AQiuSNG9<=$64)Zym(UQ``(j#^hDzt}{aur0pS?mmBi&z4I0Jfieqh%Pa_A%N?_1OZHm-S{ zQ*)4(N_J;y7tRh0o>xs25-s9!M-)i;@I68#SGXB2XgS}N zx_r3%V)z1jLA_M&?)E^DT$kzdHMJF%e2w6BH@iI5tKWM+zcuhCsz@N0a_1RBvrdZx zjzD>V%;c4*$RkEv{zHuVyaB+ANl(iT8w{pJdziC7YcO2&(ciqGLhs@q-dNh! zkV_V_(_~$*>ND}j1yozMedYnu-_GKMh?IpP<@D+edeB4M%3@xr3oj{@mdFKoBVpm^)1_}Y^}rOWBSB|Uv)*-pTdiU ztW9~{qq5@iB+$QpbeJVKH^n^9vV})i>Z@2CHoY2$PC888c;#Yz-pHRK@EVheWhE!> zZzjPmy?0Ni8#=o_k6_s3DY7nS^&Bm}BW&ZfAuF7bQbDgAGM$dE)RM6RvdobKb&MhsYD4exRm9*jcHPjbz#rI?vj$u zPLF5Gjv|8}?ta9`&^H}Va3H;llghU-BC7pxo6?-eTP`7CUZHJrw{5 zhkDYeIYlhL%brQJ1X#<#fz#E}Z87Kj=Hde*f{l|A`9E my8jz0{9hgZgN;Rh%;ug!HJ{lE_@04L;EulOt!iDD=>G@$cU!Ii literal 0 HcmV?d00001 diff --git a/Apps/DK/EDocumentFormatOIOUBL/test/app.json b/Apps/DK/EDocumentFormatOIOUBL/test/app.json new file mode 100644 index 0000000000..c7aad7b661 --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/test/app.json @@ -0,0 +1,66 @@ +{ + "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": "27.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": "27.0.0.0" + }, + { + "id": "5d86850b-0d76-4eca-bd7b-951ad998e997", + "name": "Tests-TestLibraries", + "publisher": "Microsoft", + "version": "27.0.0.0" + }, + { + "id": "9856ae4f-d1a7-46ef-89bb-6ef056398228", + "name": "System Application Test Library", + "publisher": "Microsoft", + "version": "27.0.0.0" + }, + { + "id": "5095f467-0a01-4b99-99d1-9ff1237d286f", + "publisher": "Microsoft", + "name": "Library Variable Storage", + "version": "27.0.0.0" + }, + { + "id": "8228f99b-cce5-4b9c-b247-9ee1145b7470", + "name": "E-Document format for OIOUBL", + "publisher": "Microsoft", + "version": "27.0.0.0" + } + ], + "screenshots": [], + "platform": "27.0.0.0", + "features": [ + "TranslationFile" + ], + "idRanges": [ + { + "from": 13850, + "to": 13855 + } + ], + "resourceExposurePolicy": { + "allowDebugging": true, + "allowDownloadingSource": true, + "includeSourceInSymbolFile": true + }, + "application": "27.0.0.0", + "resourceFolders": [ + ".resources" + ] +} \ No newline at end of file diff --git a/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.Codeunit.al new file mode 100644 index 0000000000..2e8adfd9cc --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.Codeunit.al @@ -0,0 +1,54 @@ +codeunit 13853 "E-Doc. Format Mock" implements "E-Document" +{ + SingleInstance = true; + + procedure Check(var SourceDocumentHeader: RecordRef; EDocService: Record "E-Document Service"; EDocumentProcessingPhase: enum "E-Document Processing Phase"); + begin + OnCheck(SourceDocumentHeader, EDocService, EDocumentProcessingPhase); + end; + + procedure Create(EDocService: Record "E-Document Service"; var EDocument: Record "E-Document"; var SourceDocumentHeader: RecordRef; var SourceDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + OnCreate(EDocService, EDocument, SourceDocumentHeader, SourceDocumentLines, TempBlob); + end; + + procedure CreateBatch(EDocService: Record "E-Document Service"; var EDocuments: Record "E-Document"; var SourceDocumentHeaders: RecordRef; var SourceDocumentsLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + OnCreateBatch(EDocService, EDocuments, SourceDocumentHeaders, SourceDocumentsLines, TempBlob); + end; + + procedure GetBasicInfoFromReceivedDocument(var EDocument: Record "E-Document"; var TempBlob: codeunit "Temp Blob"); + begin + OnGetBasicInfoFromReceivedDocument(EDocument, TempBlob); + end; + + procedure GetCompleteInfoFromReceivedDocument(var EDocument: Record "E-Document"; var CreatedDocumentHeader: RecordRef; var CreatedDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + OnGetCompleteInfoFromReceivedDocument(EDocument, CreatedDocumentHeader, CreatedDocumentLines, TempBlob); + end; + + [IntegrationEvent(false, false)] + local procedure OnCheck(var SourceDocumentHeader: RecordRef; EDocService: Record "E-Document Service"; EDocumentProcessingPhase: enum "E-Document Processing Phase") + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnCreate(EDocService: Record "E-Document Service"; var EDocument: Record "E-Document"; var SourceDocumentHeader: RecordRef; var SourceDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnCreateBatch(EDocService: Record "E-Document Service"; var EDocuments: Record "E-Document"; var SourceDocumentHeaders: RecordRef; var SourceDocumentsLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnGetBasicInfoFromReceivedDocument(var EDocument: Record "E-Document"; var TempBlob: codeunit "Temp Blob"); + begin + end; + + [IntegrationEvent(false, false)] + local procedure OnGetCompleteInfoFromReceivedDocument(var EDocument: Record "E-Document"; var CreatedDocumentHeader: RecordRef; var CreatedDocumentLines: RecordRef; var TempBlob: codeunit "Temp Blob"); + begin + end; +} \ No newline at end of file diff --git a/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.EnumExt.al b/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.EnumExt.al new file mode 100644 index 0000000000..35eb10bc77 --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocFormatMock.EnumExt.al @@ -0,0 +1,7 @@ +enumextension 13850 "E-Doc. Format Mock" extends "E-Document Format" +{ + value(6160; "Mock") + { + Implementation = "E-Document" = "E-Doc. Format Mock"; + } +} \ No newline at end of file diff --git a/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocumentStructuredTests.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocumentStructuredTests.Codeunit.al new file mode 100644 index 0000000000..5ffa9ae787 --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/test/src/EDocumentStructuredTests.Codeunit.al @@ -0,0 +1,272 @@ +codeunit 13851 "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; + 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] + [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; +} \ No newline at end of file diff --git a/Apps/DK/EDocumentFormatOIOUBL/test/src/LibraryEDocument.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/test/src/LibraryEDocument.Codeunit.al new file mode 100644 index 0000000000..8867a62abf --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/test/src/LibraryEDocument.Codeunit.al @@ -0,0 +1,1081 @@ +codeunit 13850 "Library - E-Document" +{ + EventSubscriberInstance = Manual; + Permissions = tabledata "E-Document Service" = rimd, + tabledata "E-Doc. Service Supported Type" = rimd, + tabledata "E-Doc. Mapping" = rimd; + + var + StandardItem: Record Item; + VATPostingSetup: Record "VAT Posting Setup"; + Assert: Codeunit Assert; + LibraryUtility: Codeunit "Library - Utility"; + LibraryWorkflow: Codeunit "Library - Workflow"; + LibrarySales: Codeunit "Library - Sales"; + LibraryPurchase: Codeunit "Library - Purchase"; + LibraryERM: Codeunit "Library - ERM"; + LibraryRandom: Codeunit "Library - Random"; + LibraryInvt: Codeunit "Library - Inventory"; + LibraryJobQueue: Codeunit "Library - Job Queue"; + LibraryVariableStorage: Codeunit "Library - Variable Storage"; + LibraryFinChargeMemo: Codeunit "Library - Finance Charge Memo"; + LibraryInventory: Codeunit "Library - Inventory"; + + procedure SetupStandardVAT() + begin + if (VATPostingSetup."VAT Bus. Posting Group" = '') and (VATPostingSetup."VAT Prod. Posting Group" = '') then + LibraryERM.CreateVATPostingSetupWithAccounts(VATPostingSetup, Enum::"Tax Calculation Type"::"Normal VAT", 1); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "E-Document Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + SetupStandardSalesScenario(Customer, EDocService); + end; +#pragma warning restore AL0432 +#endif + + procedure SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + SetupStandardSalesScenario(Customer, EDocService); + end; + + procedure SetupStandardSalesScenario(var Customer: Record Customer; var EDocService: Record "E-Document Service") + var + CountryRegion: Record "Country/Region"; + DocumentSendingProfile: Record "Document Sending Profile"; + SalesSetup: Record "Sales & Receivables Setup"; + WorkflowSetup: Codeunit "Workflow Setup"; + WorkflowCode: Code[20]; + begin + LibraryWorkflow.DeleteAllExistingWorkflows(); + WorkflowSetup.InitWorkflow(); + SetupCompanyInfo(); + + CreateDocSendingProfile(DocumentSendingProfile); + WorkflowCode := CreateSimpleFlow(DocumentSendingProfile.Code, EDocService.Code); + DocumentSendingProfile."Electronic Document" := DocumentSendingProfile."Electronic Document"::"Extended E-Document Service Flow"; + DocumentSendingProfile."Electronic Service Flow" := WorkflowCode; + DocumentSendingProfile.Modify(); + + // Create Customer for sales scenario + LibrarySales.CreateCustomer(Customer); + LibraryERM.FindCountryRegion(CountryRegion); + Customer.Validate(Address, LibraryUtility.GenerateRandomCode(Customer.FieldNo(Address), DATABASE::Customer)); + Customer.Validate("Country/Region Code", CountryRegion.Code); + Customer.Validate(City, LibraryUtility.GenerateRandomCode(Customer.FieldNo(City), DATABASE::Customer)); + Customer.Validate("Post Code", LibraryUtility.GenerateRandomCode(Customer.FieldNo("Post Code"), DATABASE::Customer)); + Customer.Validate("VAT Bus. Posting Group", VATPostingSetup."VAT Bus. Posting Group"); + Customer."VAT Registration No." := LibraryERM.GenerateVATRegistrationNo(CountryRegion.Code); + Customer.Validate(GLN, '1234567890128'); + Customer."Document Sending Profile" := DocumentSendingProfile.Code; + Customer.Modify(true); + + // Create Item + if StandardItem."No." = '' then begin + VATPostingSetup.TestField("VAT Prod. Posting Group"); + CreateGenericItem(StandardItem); + StandardItem."VAT Prod. Posting Group" := VATPostingSetup."VAT Prod. Posting Group"; + StandardItem.Modify(); + end; + + SalesSetup.Get(); + SalesSetup."Invoice Rounding" := false; + SalesSetup.Modify(); + end; + + procedure GetGenericItem(var Item: Record Item) + begin + if StandardItem."No." = '' then + CreateGenericItem(StandardItem); + Item.Get(StandardItem."No."); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "E-Document Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + if EDocService.Code = '' then begin + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + end; + SetupStandardPurchaseScenario(Vendor, EDocService); + end; +#pragma warning restore AL0432 +#endif + + procedure SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service"; EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") + var + ServiceCode: Code[20]; + begin + // Create standard service and simple workflow + if EDocService.Code = '' then begin + ServiceCode := CreateService(EDocDocumentFormat, EDocIntegration); + EDocService.Get(ServiceCode); + end; + SetupStandardPurchaseScenario(Vendor, EDocService); + end; + + + procedure SetupStandardPurchaseScenario(var Vendor: Record Vendor; var EDocService: Record "E-Document Service") + var + CountryRegion: Record "Country/Region"; + ItemReference: Record "Item Reference"; + UnitOfMeasure: Record "Unit of Measure"; + ExtraItem: Record "Item"; + WorkflowSetup: Codeunit "Workflow Setup"; + LibraryItemReference: Codeunit "Library - Item Reference"; + begin + WorkflowSetup.InitWorkflow(); + SetupCompanyInfo(); + + // Create Customer for sales scenario + LibraryPurchase.CreateVendor(Vendor); + LibraryERM.FindCountryRegion(CountryRegion); + Vendor.Validate(Address, LibraryUtility.GenerateRandomCode(Vendor.FieldNo(Address), DATABASE::Vendor)); + Vendor.Validate("Country/Region Code", CountryRegion.Code); + Vendor.Validate(City, LibraryUtility.GenerateRandomCode(Vendor.FieldNo(City), DATABASE::Vendor)); + Vendor.Validate("Post Code", LibraryUtility.GenerateRandomCode(Vendor.FieldNo("Post Code"), DATABASE::Vendor)); + Vendor.Validate("VAT Bus. Posting Group", VATPostingSetup."VAT Bus. Posting Group"); + Vendor."VAT Registration No." := LibraryERM.GenerateVATRegistrationNo(CountryRegion.Code); + Vendor."Receive E-Document To" := Enum::"E-Document Type"::"Purchase Invoice"; + Vendor.Validate(GLN, '1234567890128'); + Vendor.Modify(true); + + // Create Item + if StandardItem."No." = '' then begin + VATPostingSetup.TestField("VAT Prod. Posting Group"); + CreateGenericItem(StandardItem, VATPostingSetup."VAT Prod. Posting Group"); + end; + + UnitOfMeasure.Init(); + UnitOfMeasure."International Standard Code" := 'PCS'; + UnitOfMeasure.Code := 'PCS'; + if UnitOfMeasure.Insert() then; + + CreateItemUnitOfMeasure(StandardItem."No.", UnitOfMeasure.Code); + LibraryItemReference.CreateItemReference(ItemReference, StandardItem."No.", '', 'PCS', Enum::"Item Reference Type"::Vendor, Vendor."No.", '1000'); + + CreateGenericItem(ExtraItem, VATPostingSetup."VAT Prod. Posting Group"); + CreateItemUnitOfMeasure(ExtraItem."No.", UnitOfMeasure.Code); + LibraryItemReference.CreateItemReference(ItemReference, ExtraItem."No.", '', 'PCS', Enum::"Item Reference Type"::Vendor, Vendor."No.", '2000'); + end; + + procedure PostSalesShipment(var Customer: Record Customer) SalesShipmentHeader: Record "Sales Shipment Header"; + var + SalesHeader: Record "Sales Header"; + begin + this.CreateSalesHeaderWithItem(Customer, SalesHeader, Enum::"Sales Document Type"::Order); + SalesShipmentHeader.Get(this.LibrarySales.PostSalesDocument(SalesHeader, true, false)); + end; + + procedure CreateInboundEDocument(var EDocument: Record "E-Document"; EDocService: Record "E-Document Service") + var + EDocumentServiceStatus: Record "E-Document Service Status"; + begin + EDocument.Insert(); + EDocumentServiceStatus."E-Document Entry No" := EDocument."Entry No"; + EDocumentServiceStatus."E-Document Service Code" := EDocService.Code; + EDocumentServiceStatus.Insert(); + end; + + procedure MockPurchaseDraftPrepared(EDocument: Record "E-Document") + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentProcessing: Codeunit "E-Document Processing"; + begin + EDocumentPurchaseHeader.InsertForEDocument(EDocument); + EDocumentPurchaseHeader."Sub Total" := 1000; + EDocumentPurchaseHeader."Total VAT" := 100; + EDocumentPurchaseHeader.Total := 1100; + EDocumentPurchaseHeader.Modify(); + EDocumentProcessing.ModifyEDocumentProcessingStatus(EDocument, "Import E-Doc. Proc. Status"::"Draft Ready"); + EDocument."Document Type" := "E-Document Type"::"Purchase Invoice"; + EDocument.Modify(); + end; + + procedure CreateInboundPEPPOLDocumentToState(var EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; FileName: Text; EDocImportParams: Record "E-Doc. Import Parameters"): Boolean + var + EDocImport: Codeunit "E-Doc. Import"; + InStream: InStream; + begin + NavApp.GetResource(FileName, InStream, TextEncoding::UTF8); + EDocImport.CreateFromType(EDocument, EDocumentService, Enum::"E-Doc. File Format"::XML, 'TestFile', InStream); + exit(EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams)); + end; + + /// + /// Given a purchase header with purchase lines created from an e-document, it modifies the required fields to make it ready for posting. + /// + procedure EditPurchaseDocumentFromEDocumentForPosting(var PurchaseHeader: Record "Purchase Header"; var EDocument: Record "E-Document") + var + PurchaseLine: Record "Purchase Line"; + GLAccount: Record "G/L Account"; + GeneralPostingSetup: Record "General Posting Setup"; + GenBusinessPostingGroup: Record "Gen. Business Posting Group"; + GenProductPostingGroup: Record "Gen. Product Posting Group"; + VATBusinessPostingGroup: Record "VAT Business Posting Group"; + VATProductPostingGroup: Record "VAT Product Posting Group"; + LocalVATPostingSetup: Record "VAT Posting Setup"; + begin + Assert.AreEqual(EDocument.SystemId, PurchaseHeader."E-Document Link", 'The purchase header has no link to the e-document.'); + LibraryERM.CreateGenBusPostingGroup(GenBusinessPostingGroup); + LibraryERM.CreateGenProdPostingGroup(GenProductPostingGroup); + LibraryERM.CreateGeneralPostingSetup(GeneralPostingSetup, GenBusinessPostingGroup.Code, GenProductPostingGroup.Code); + LibraryERM.CreateVATBusinessPostingGroup(VATBusinessPostingGroup); + LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); + LibraryERM.CreateVATPostingSetup(LocalVATPostingSetup, VATBusinessPostingGroup.Code, VATProductPostingGroup.Code); + LibraryERM.CreateGLAccount(GLAccount); + LocalVATPostingSetup."Purchase VAT Account" := GLAccount."No."; + LocalVATPostingSetup.Modify(); + LibraryERM.CreateGLAccount(GLAccount); + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseLine.FindSet(); + repeat + PurchaseLine.Type := PurchaseLine.Type::"G/L Account"; + PurchaseLine."No." := GLAccount."No."; + PurchaseLine."Gen. Bus. Posting Group" := GenBusinessPostingGroup.Code; + PurchaseLine."Gen. Prod. Posting Group" := GenProductPostingGroup.Code; + PurchaseLine."VAT Bus. Posting Group" := VATBusinessPostingGroup.Code; + PurchaseLine."VAT Prod. Posting Group" := VATProductPostingGroup.Code; + PurchaseLine.UpdateAmounts(); + PurchaseLine.Modify(); + until PurchaseLine.Next() = 0; + PurchaseHeader.CalcFields("Amount Including VAT"); + EDocument."Amount Incl. VAT" := PurchaseHeader."Amount Including VAT"; + EDocument.Modify(); + end; + + procedure CreateDocSendingProfile(var DocumentSendingProfile: Record "Document Sending Profile") + begin + DocumentSendingProfile.Init(); + DocumentSendingProfile.Code := LibraryUtility.GenerateRandomCode(DocumentSendingProfile.FieldNo(Code), DATABASE::"Document Sending Profile"); + DocumentSendingProfile.Insert(); + end; + + + procedure CreateSimpleFlow(DocSendingProfileCode: Code[20]; ServiceCode: Code[20]): Code[20] + var + Workflow: Record Workflow; + WorkflowStepResponse: Record "Workflow Step"; + WorkflowStepArgument: Record "Workflow Step Argument"; + WorkflowStep: Record "Workflow Step"; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID, SendEDocResponseEventID : Integer; + begin + // Create a simple workflow + // Send to Service 'ServiceCode' when using Document Sending Profile 'DocSendingProfile' + WorkflowStep.SetRange("Function Name", EDocWorkflowSetup.EDocCreated()); + WorkflowStep.SetRange("Entry Point", true); + if WorkflowStep.FindSet() then + repeat + Workflow.Get(WorkflowStep."Workflow Code"); + if not Workflow.Template then + exit; + until WorkflowStep.Next() = 0; + + LibraryWorkflow.CreateWorkflow(Workflow); + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + SendEDocResponseEventID := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventID); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + + WorkflowStepArgument."E-Document Service" := ServiceCode; + WorkflowStepArgument.Modify(); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + procedure CreateCustomerNoWithEDocSendingProfile(var DocumentSendingProfile: Code[20]): Code[20] + var + CustomerNo: Code[20]; + begin + CustomerNo := LibrarySales.CreateCustomerNo(); + DocumentSendingProfile := CreateDocumentSendingProfileForWorkflow(CustomerNo, ''); + exit(CustomerNo); + end; + + procedure CreateEDocumentFromSales(var EDocument: Record "E-Document") + begin + CreateEDocumentFromSales(EDocument, LibrarySales.CreateCustomerNo()); + end; + + procedure CreateEDocumentFromSales(var EDocument: Record "E-Document"; CustomerNo: Code[20]) + var + SalesInvHeader: Record "Sales Invoice Header"; + SalesHeader: Record "Sales Header"; + begin + LibrarySales.CreateSalesInvoiceForCustomerNo(SalesHeader, CustomerNo); + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + EDocument.FindLast(); + end; + + local procedure CreateGenericSalesHeader(var Cust: Record Customer; var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type") + begin + LibrarySales.CreateSalesHeader(SalesHeader, DocumentType, Cust."No."); + SalesHeader.Validate("Your Reference", LibraryUtility.GenerateRandomCode(SalesHeader.FieldNo("Your Reference"), DATABASE::"Sales Header")); + + if DocumentType = SalesHeader."Document Type"::"Credit Memo" then + SalesHeader.Validate("Shipment Date", WorkDate()); + + SalesHeader.Modify(true); + end; + + procedure CreateGenericItem(var Item: Record Item; VATProdPostingGroupCode: Code[20]) + begin + CreateGenericItem(Item); + Item."VAT Prod. Posting Group" := VATPostingSetup."VAT Prod. Posting Group"; + Item.Modify(); + end; + + procedure CreateGenericItem(var Item: Record Item) + var + UOM: Record "Unit of Measure"; + ItemUOM: Record "Item Unit of Measure"; + QtyPerUnit: Integer; + begin + QtyPerUnit := LibraryRandom.RandInt(10); + + LibraryInvt.CreateUnitOfMeasureCode(UOM); + UOM.Validate("International Standard Code", + LibraryUtility.GenerateRandomCode(UOM.FieldNo("International Standard Code"), DATABASE::"Unit of Measure")); + UOM.Modify(true); + + CreateItemWithPrice(Item, LibraryRandom.RandInt(10)); + + LibraryInvt.CreateItemUnitOfMeasure(ItemUOM, Item."No.", UOM.Code, QtyPerUnit); + + Item.Validate("Sales Unit of Measure", UOM.Code); + Item.Modify(true); + end; + + local procedure CreateItemWithPrice(var Item: Record Item; UnitPrice: Decimal) + begin + LibraryInvt.CreateItem(Item); + Item."Unit Price" := UnitPrice; + Item.Modify(); + end; + + procedure SetupCompanyInfo() + var + CompanyInfo: Record "Company Information"; + CountryRegion: Record "Country/Region"; + begin + LibraryERM.FindCountryRegion(CountryRegion); + + CompanyInfo.Get(); + CompanyInfo.Validate(IBAN, 'GB33BUKB20201555555555'); + CompanyInfo.Validate("SWIFT Code", 'MIDLGB22Z0K'); + CompanyInfo.Validate("Bank Branch No.", '1234'); + CompanyInfo.Validate(Address, CopyStr(LibraryUtility.GenerateRandomXMLText(MaxStrLen(CompanyInfo.Address)), 1, MaxStrLen(CompanyInfo.Address))); + CompanyInfo.Validate("Post Code", CopyStr(LibraryUtility.GenerateRandomXMLText(MaxStrLen(CompanyInfo."Post Code")), 1, MaxStrLen(CompanyInfo."Post Code"))); + CompanyInfo.Validate("City", CopyStr(LibraryUtility.GenerateRandomXMLText(MaxStrLen(CompanyInfo."City")), 1, MaxStrLen(CompanyInfo."Post Code"))); + CompanyInfo."Country/Region Code" := CountryRegion.Code; + + if CompanyInfo."VAT Registration No." = '' then + CompanyInfo."VAT Registration No." := LibraryERM.GenerateVATRegistrationNo(CompanyInfo."Country/Region Code"); + + CompanyInfo.Modify(true); + end; + + procedure CreateSalesHeaderWithItem(Customer: Record Customer; var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type") + var + SalesLine: Record "Sales Line"; + begin + CreateGenericSalesHeader(Customer, SalesHeader, DocumentType); + + if StandardItem."No." = '' then + CreateGenericItem(StandardItem); + + LibrarySales.CreateSalesLine(SalesLine, SalesHeader, SalesLine.Type::Item, StandardItem."No.", 1); + end; + + procedure CreatePurchaseOrderWithLine(var Vendor: Record Vendor; var PurchaseHeader: Record "Purchase Header"; var PurchaseLine: Record "Purchase Line"; Quantity: Decimal) + begin + LibraryPurchase.CreatePurchHeader(PurchaseHeader, Enum::"Purchase Document Type"::Order, Vendor."No."); + if StandardItem."No." = '' then + CreateGenericItem(StandardItem); + LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, StandardItem."No.", Quantity); + end; + + procedure PostSalesDocument(var SalesHeader: Record "Sales Header"; var SalesInvHeader: Record "Sales Invoice Header") + begin + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + end; + + procedure PostSalesDocument(var SalesHeader: Record "Sales Header"; var SalesInvHeader: Record "Sales Invoice Header"; Ship: Boolean) + begin + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, Ship, true)); + end; + + procedure CreateReminderWithLine(Customer: Record Customer; var ReminderHeader: Record "Reminder Header") + var + ReminderLine: Record "Reminder Line"; + begin + LibraryERM.CreateReminderHeader(ReminderHeader); + ReminderHeader.Validate("Customer No.", Customer."No."); + ReminderHeader."Your Reference" := LibraryRandom.RandText(35); + ReminderHeader.Modify(false); + + LibraryERM.CreateReminderLine(ReminderLine, ReminderHeader."No.", Enum::"Reminder Source Type"::"G/L Account"); + ReminderLine.Validate("Remaining Amount", this.LibraryRandom.RandInt(100)); + ReminderLine.Description := LibraryRandom.RandText(100); + ReminderLine.Modify(false); + end; + + procedure CreateFinChargeMemoWithLine(Customer: Record Customer; var FinChargeMemoHeader: Record "Finance Charge Memo Header") + var + FinChargeMemoLine: Record "Finance Charge Memo Line"; + FinanceChargeTerms: Record "Finance Charge Terms"; + begin + LibraryERM.CreateFinanceChargeMemoHeader(FinChargeMemoHeader, Customer."No."); + LibraryFinChargeMemo.CreateFinanceChargeTermAndText(FinanceChargeTerms); + FinChargeMemoHeader.Validate("Fin. Charge Terms Code", FinanceChargeTerms.Code); + FinChargeMemoHeader."Your Reference" := LibraryRandom.RandText(35); + FinChargeMemoHeader.Modify(false); + + LibraryERM.CreateFinanceChargeMemoLine(FinChargeMemoLine, FinChargeMemoHeader."No.", FinChargeMemoLine.Type::"G/L Account"); + FinChargeMemoLine.Validate("Remaining Amount", this.LibraryRandom.RandInt(100)); + FinChargeMemoLine.Description := LibraryRandom.RandText(100); + FinChargeMemoLine.Modify(false); + end; + + procedure IssueReminder(Customer: Record Customer) IssuedReminderHeader: Record "Issued Reminder Header" + var + ReminderHeader: Record "Reminder Header"; + ReminderIssue: Codeunit "Reminder-Issue"; + begin + CreateReminderWithLine(Customer, ReminderHeader); + + ReminderHeader.SetRange("No.", ReminderHeader."No."); + ReminderIssue.Set(ReminderHeader, false, 0D); + ReminderIssue.Run(); + + ReminderIssue.GetIssuedReminder(IssuedReminderHeader); + end; + + procedure IssueFinChargeMemo(Customer: Record Customer) IssuedFinChargeMemoHeader: Record "Issued Fin. Charge Memo Header" + var + FinChargeMemoHeader: Record "Finance Charge Memo Header"; + FinChargeMemoIssue: Codeunit "FinChrgMemo-Issue"; + begin + CreateFinChargeMemoWithLine(Customer, FinChargeMemoHeader); + + FinChargeMemoHeader.SetRange("No.", FinChargeMemoHeader."No."); + FinChargeMemoIssue.Set(FinChargeMemoHeader, false, 0D); + FinChargeMemoIssue.Run(); + + FinChargeMemoIssue.GetIssuedFinChrgMemo(IssuedFinChargeMemoHeader); + end; + + procedure SetupReminderNoSeries() + var + SalesSetup: Record "Sales & Receivables Setup"; + begin + SalesSetup.Get(); + SalesSetup.Validate("Reminder Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Validate("Issued Reminder Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Modify(false); + end; + + procedure SetupFinChargeMemoNoSeries() + var + SalesSetup: Record "Sales & Receivables Setup"; + begin + SalesSetup.Get(); + SalesSetup.Validate("Fin. Chrg. Memo Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Validate("Issued Fin. Chrg. M. Nos.", this.LibraryERM.CreateNoSeriesCode()); + SalesSetup.Modify(false); + end; + + procedure Initialize() + var + DocumentSendingProfile: Record "Document Sending Profile"; + EDocService: Record "E-Document Service"; + EDocServiceStatus: Record "E-Document Service Status"; + EDocServiceSupportedType: Record "E-Doc. Service Supported Type"; + EDocMapping: Record "E-Doc. Mapping"; + EDocLogs: Record "E-Document Log"; + EDocMappingLogs: Record "E-Doc. Mapping Log"; + EDocDataStorage: Record "E-Doc. Data Storage"; + EDocument: Record "E-Document"; + WorkflowSetup: Codeunit "Workflow Setup"; + begin + LibraryWorkflow.DeleteAllExistingWorkflows(); + WorkflowSetup.InitWorkflow(); + DocumentSendingProfile.DeleteAll(); + EDocService.DeleteAll(); + EDocServiceSupportedType.DeleteAll(); + EDocument.DeleteAll(); + EDocServiceStatus.DeleteAll(); + EDocDataStorage.DeleteAll(); + EDocMapping.DeleteAll(); + EDocLogs.DeleteAll(); + EDocMappingLogs.DeleteAll(); + Commit(); + end; + + procedure PostSalesDocument(CustomerNo: Code[20]): Code[20] + var + SalesInvHeader: Record "Sales Invoice Header"; + SalesHeader: Record "Sales Header"; + begin + LibrarySales.CreateSalesInvoiceForCustomerNo(SalesHeader, CustomerNo); + SalesInvHeader.Get(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + exit(SalesInvHeader."No."); + end; + + procedure PostSalesDocument(): Code[20] + begin + PostSalesDocument(''); + end; + + procedure CreateDocumentSendingProfileForWorkflow(CustomerNo: Code[20]; WorkflowCode: Code[20]): Code[20] + var + Customer: Record Customer; + DocumentSendingProfile: Record "Document Sending Profile"; + begin + DocumentSendingProfile.Init(); + DocumentSendingProfile.Code := LibraryUtility.GenerateRandomCode20(DocumentSendingProfile.FieldNo(Code), Database::"Document Sending Profile"); + DocumentSendingProfile."Electronic Document" := Enum::"Doc. Sending Profile Elec.Doc."::"Extended E-Document Service Flow"; + DocumentSendingProfile."Electronic Service Flow" := WorkflowCode; + DocumentSendingProfile.Insert(); + + Customer.Get(CustomerNo); + Customer.Validate("Document Sending Profile", DocumentSendingProfile.Code); + Customer.Modify(); + exit(DocumentSendingProfile.Code); + end; + + procedure UpdateWorkflowOnDocumentSendingProfile(DocSendingProfile: Code[20]; WorkflowCode: Code[20]) + var + DocumentSendingProfile: Record "Document Sending Profile"; + begin + DocumentSendingProfile.Get(DocSendingProfile); + DocumentSendingProfile.Validate("Electronic Service Flow", WorkflowCode); + DocumentSendingProfile.Modify(); + end; + + procedure CreateFlowWithService(DocSendingProfile: Code[20]; ServiceCode: Code[20]): Code[20] + var + Workflow: Record Workflow; + WorkflowStepResponse: Record "Workflow Step"; + WorkflowStepArgument: Record "Workflow Step Argument"; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID, SendEDocResponseEventID : Integer; + EventConditions: Text; + begin + LibraryWorkflow.CreateWorkflow(Workflow); + EventConditions := CreateWorkflowEventConditionDocSendingProfileFilter(DocSendingProfile); + + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + LibraryWorkflow.InsertEventArgument(EDocCreatedEventID, EventConditions); + SendEDocResponseEventID := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventID); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + + WorkflowStepArgument.Validate("E-Document Service", ServiceCode); + WorkflowStepArgument.Modify(); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + procedure CreateEmptyFlow(): Code[20] + var + Workflow: Record Workflow; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID: Integer; + begin + LibraryWorkflow.CreateWorkflow(Workflow); + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + procedure CreateFlowWithServices(DocSendingProfile: Code[20]; ServiceCodeA: Code[20]; ServiceCodeB: Code[20]): Code[20] + var + Workflow: Record Workflow; + WorkflowStepResponse: Record "Workflow Step"; + WorkflowStepArgument: Record "Workflow Step Argument"; + EDocWorkflowSetup: Codeunit "E-Document Workflow Setup"; + EDocCreatedEventID, SendEDocResponseEventIDA, SendEDocResponseEventIDB : Integer; + EventConditionsDocProfile, EventConditionsService : Text; + begin + LibraryWorkflow.CreateWorkflow(Workflow); + EventConditionsDocProfile := CreateWorkflowEventConditionDocSendingProfileFilter(DocSendingProfile); + EventConditionsService := CreateWorkflowEventConditionServiceFilter(ServiceCodeA); + + EDocCreatedEventID := LibraryWorkflow.InsertEntryPointEventStep(Workflow, EDocWorkflowSetup.EDocCreated()); + LibraryWorkflow.InsertEventArgument(EDocCreatedEventID, EventConditionsDocProfile); + SendEDocResponseEventIDA := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), EDocCreatedEventID); + SendEDocResponseEventIDB := LibraryWorkflow.InsertResponseStep(Workflow, EDocWorkflowSetup.EDocSendEDocResponseCode(), SendEDocResponseEventIDA); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventIDA); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + WorkflowStepArgument."E-Document Service" := ServiceCodeA; + WorkflowStepArgument.Modify(); + + WorkflowStepResponse.Get(Workflow.Code, SendEDocResponseEventIDB); + WorkflowStepArgument.Get(WorkflowStepResponse.Argument); + WorkflowStepArgument."E-Document Service" := ServiceCodeB; + WorkflowStepArgument.Modify(); + + LibraryWorkflow.EnableWorkflow(Workflow); + exit(Workflow.Code); + end; + + local procedure DeleteEDocumentRelatedEntities() + var + DynamicRequestPageEntity: Record "Dynamic Request Page Entity"; + begin + DynamicRequestPageEntity.SetRange("Table ID", DATABASE::"E-Document"); + DynamicRequestPageEntity.DeleteAll(true); + end; + + local procedure CreateWorkflowEventConditionDocSendingProfileFilter(DocSendingProfile: Code[20]): Text + var + RequestPageParametersHelper: Codeunit "Request Page Parameters Helper"; + FilterPageBuilder: FilterPageBuilder; + EntityName: Code[20]; + begin + EntityName := CreateDynamicRequestPageEntity(DATABASE::"E-Document", Database::"Document Sending Profile"); + CreateEDocumentDocSendingProfileDataItem(FilterPageBuilder, DocSendingProfile); + exit(RequestPageParametersHelper.GetViewFromDynamicRequestPage(FilterPageBuilder, EntityName, Database::"E-Document")); + end; + + local procedure CreateWorkflowEventConditionServiceFilter(ServiceCode: Code[20]): Text + var + RequestPageParametersHelper: Codeunit "Request Page Parameters Helper"; + FilterPageBuilder: FilterPageBuilder; + EntityName: Code[20]; + begin + EntityName := CreateDynamicRequestPageEntity(DATABASE::"E-Document", Database::"E-Document Service"); + CreateEDocServiceDataItem(FilterPageBuilder, ServiceCode); + exit(RequestPageParametersHelper.GetViewFromDynamicRequestPage(FilterPageBuilder, EntityName, Database::"E-Document Service")); + end; + + local procedure CreateEDocumentDocSendingProfileDataItem(var FilterPageBuilder: FilterPageBuilder; DocumentSendingProfile: Code[20]) + var + EDocument: Record "E-Document"; + EDocumentDataItem: Text; + begin + EDocumentDataItem := FilterPageBuilder.AddTable(EDocument.TableCaption, DATABASE::"E-Document"); + FilterPageBuilder.AddField(EDocumentDataItem, EDocument."Document Sending Profile", DocumentSendingProfile); + end; + + local procedure CreateEDocServiceDataItem(var FilterPageBuilder: FilterPageBuilder; ServiceCode: Code[20]) + var + EDocService: Record "E-Document Service"; + EDocumentDataItem: Text; + begin + EDocumentDataItem := FilterPageBuilder.AddTable(EDocService.TableCaption, DATABASE::"E-Document Service"); + FilterPageBuilder.AddField(EDocumentDataItem, EDocService.Code, ServiceCode); + end; + + local procedure CreateDynamicRequestPageEntity(TableID: Integer; RelatedTable: Integer): Code[20] + var + EntityName: Code[20]; + begin + DeleteEDocumentRelatedEntities(); + EntityName := LibraryUtility.GenerateGUID(); + LibraryWorkflow.CreateDynamicRequestPageEntity(EntityName, TableID, RelatedTable); + exit(EntityName); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure CreateService(Integration: Enum "E-Document Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; +#pragma warning restore AL0432 +#endif + + procedure CreateService(Integration: Enum "Service Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration") instead', '26.0')] + procedure CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "E-Document Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := EDocDocumentFormat; + EDocService."Service Integration" := EDocIntegration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; +#pragma warning restore AL0432 +#endif + + procedure CreateService(EDocDocumentFormat: Enum "E-Document Format"; EDocIntegration: Enum "Service Integration"): Code[20] + var + EDocService: Record "E-Document Service"; + begin + EDocService.Init(); + EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + EDocService."Document Format" := EDocDocumentFormat; + EDocService."Service Integration V2" := EDocIntegration; + EDocService.Insert(); + + CreateSupportedDocTypes(EDocService); + + exit(EDocService.Code); + end; + + + procedure CreateServiceMapping(EDocService: Record "E-Document Service") + var + TransformationRule: Record "Transformation Rule"; + EDocMapping: Record "E-Doc. Mapping"; + SalesInvHeader: Record "Sales Invoice Header"; + begin + TransformationRule.Get(TransformationRule.GetLowercaseCode()); + // Lower case mapping + CreateTransformationMapping(EDocMapping, TransformationRule, EDocService.Code); + EDocMapping."Table ID" := Database::"Sales Invoice Header"; + EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Name"); + EDocMapping.Modify(); + CreateTransformationMapping(EDocMapping, TransformationRule, EDocService.Code); + EDocMapping."Table ID" := Database::"Sales Invoice Header"; + EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Address"); + EDocMapping.Modify(); + end; + + procedure DeleteServiceMapping(EDocService: Record "E-Document Service") + var + EDocMapping: Record "E-Doc. Mapping"; + begin + EDocMapping.SetRange(Code, EDocService.Code); + EDocMapping.DeleteAll(); + end; + + + // procedure CreateServiceWithMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule"; Integration: Enum "E-Document Integration"): Code[20] + // begin + // exit(CreateServiceWithMapping(EDocMapping, TransformationRule, false, Integration)); + // end; + + // procedure CreateServiceWithMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule"; UseBatching: Boolean; Integration: Enum "E-Document Integration"): Code[20] + // var + // SalesInvHeader: Record "Sales Invoice Header"; + // EDocService: Record "E-Document Service"; + // begin + // EDocService.Init(); + // EDocService.Code := LibraryUtility.GenerateRandomCode20(EDocService.FieldNo(Code), Database::"E-Document Service"); + // EDocService."Document Format" := "E-Document Format"::Mock; + // EDocService."Service Integration" := Integration; + // EDocService."Use Batch Processing" := UseBatching; + // EDocService.Insert(); + + // CreateSupportedDocTypes(EDocService); + + // // Lower case mapping + // CreateTransformationMapping(EDocMapping, TransformationRule, EDocService.Code); + // EDocMapping."Table ID" := Database::"Sales Invoice Header"; + // EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Name"); + // EDocMapping.Modify(); + // CreateTransformationMapping(EDocMapping, TransformationRule, EDocService.Code); + // EDocMapping."Table ID" := Database::"Sales Invoice Header"; + // EDocMapping."Field ID" := SalesInvHeader.FieldNo("Bill-to Address"); + // EDocMapping.Modify(); + + // exit(EDocService.Code); + // end; + + procedure CreateSupportedDocTypes(EDocService: Record "E-Document Service") + var + EDocServiceSupportedType: Record "E-Doc. Service Supported Type"; + begin + EDocServiceSupportedType.Init(); + EDocServiceSupportedType."E-Document Service Code" := EDocService.Code; + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Sales Invoice"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Sales Credit Memo"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Service Invoice"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Service Credit Memo"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Issued Finance Charge Memo"; + EDocServiceSupportedType.Insert(); + + EDocServiceSupportedType."Source Document Type" := EDocServiceSupportedType."Source Document Type"::"Issued Reminder"; + EDocServiceSupportedType.Insert(); + end; + + procedure AddEDocServiceSupportedType(EDocService: Record "E-Document Service"; EDocumentType: Enum "E-Document Type") + var + EDocServiceSupportedType: Record "E-Doc. Service Supported Type"; + begin + if not EDocService.Get(EDocService.Code) then + exit; + + EDocServiceSupportedType.Init(); + EDocServiceSupportedType."E-Document Service Code" := EDocService.Code; + EDocServiceSupportedType."Source Document Type" := EDocumentType; + if EDocServiceSupportedType.Insert() then; + end; + + procedure CreateTestReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") + begin + if not EDocService.Get('TESTRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'TESTRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + end; + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateTestReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") instead', '26.0')] + procedure CreateTestReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "E-Document Integration") + begin + if not EDocService.Get('TESTRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'TESTRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + end; + end; +#pragma warning restore AL0432 +#endif + + procedure CreateGetBasicInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") + begin + if not EDocService.Get('BIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'BIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + end; + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateGetBasicInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") instead', '26.0')] + procedure CreateGetBasicInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "E-Document Integration") + begin + if not EDocService.Get('BIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'BIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + end; + end; +#pragma warning restore AL0432 +#endif + + procedure CreateGetCompleteInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") + begin + if not EDocService.Get('CIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'CIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration V2" := Integration; + EDocService.Insert(); + end; + end; + +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Use CreateGetCompleteInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "Service Integration") instead', '26.0')] + procedure CreateGetCompleteInfoErrorReceiveServiceForEDoc(var EDocService: Record "E-Document Service"; Integration: Enum "E-Document Integration") + begin + if not EDocService.Get('CIERRRECEIVE') then begin + EDocService.Init(); + EDocService.Code := 'CIERRRECEIVE'; + EDocService."Document Format" := "E-Document Format"::Mock; + EDocService."Service Integration" := Integration; + EDocService.Insert(); + end; + end; +#pragma warning restore AL0432 +#endif + + procedure CreateDirectMapping(var EDocMapping: Record "E-Doc. Mapping"; EDocService: Record "E-Document Service"; FindValue: Text; ReplaceValue: Text) + begin + CreateDirectMapping(EDocMapping, EDocService, FindValue, ReplaceValue, 0, 0); + end; + + procedure CreateTransformationMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule") + begin + CreateTransformationMapping(EDocMapping, TransformationRule, ''); + end; + + procedure CreateTransformationMapping(var EDocMapping: Record "E-Doc. Mapping"; TransformationRule: Record "Transformation Rule"; ServiceCode: Code[20]) + begin + EDocMapping.Init(); + EDocMapping.Code := ServiceCode; + EDocMapping."Entry No." := 0; + EDocMapping."Transformation Rule" := TransformationRule.Code; + EDocMapping.Insert(); + end; + + procedure CreateDirectMapping(var EDocMapping: Record "E-Doc. Mapping"; EDocService: Record "E-Document Service"; FindValue: Text; ReplaceValue: Text; TableId: Integer; FieldId: Integer) + begin + EDocMapping.Init(); + EDocMapping."Entry No." := 0; + EDocMapping.Code := EDocService.Code; + EDocMapping."Table ID" := TableId; + EDocMapping."Field ID" := FieldId; + EDocMapping."Find Value" := CopyStr(FindValue, 1, LibraryUtility.GetFieldLength(DATABASE::"E-Doc. Mapping", EDocMapping.FieldNo("Find Value"))); + EDocMapping."Replace Value" := CopyStr(ReplaceValue, 1, LibraryUtility.GetFieldLength(DATABASE::"E-Doc. Mapping", EDocMapping.FieldNo("Replace Value"))); + EDocMapping.Insert(); + end; + + local procedure CreateItemUnitOfMeasure(ItemNo: Code[20]; UnitOfMeasureCode: Code[10]) + var + ItemUnitOfMeasure: Record "Item Unit of Measure"; + begin + ItemUnitOfMeasure.Init(); + ItemUnitOfMeasure.Validate("Item No.", ItemNo); + ItemUnitOfMeasure.Validate(Code, UnitOfMeasureCode); + ItemUnitOfMeasure."Qty. per Unit of Measure" := 1; + if ItemUnitOfMeasure.Insert() then; + end; + + procedure TempBlobToTxt(var TempBlob: Codeunit "Temp Blob"): Text + var + InStr: InStream; + Content: Text; + begin + TempBlob.CreateInStream(InStr); + InStr.Read(Content); + exit(Content); + end; + + internal procedure CreateLocationsWithPostingSetups( + var FromLocation: Record Location; + var ToLocation: Record Location; + var InTransitLocation: Record Location; + var InventoryPostingGroup: Record "Inventory Posting Group") + var + InventoryPostingSetupFromLocation: Record "Inventory Posting Setup"; + InventoryPostingSetupToLocation: Record "Inventory Posting Setup"; + InventoryPostingSetupInTransitLocation: Record "Inventory Posting Setup"; + LibraryWarehouse: Codeunit "Library - Warehouse"; + begin + LibraryWarehouse.CreateLocation(FromLocation); + LibraryWarehouse.CreateLocation(ToLocation); + LibraryWarehouse.CreateInTransitLocation(InTransitLocation); + + LibraryInventory.CreateInventoryPostingGroup(InventoryPostingGroup); + LibraryInventory.CreateInventoryPostingSetup(InventoryPostingSetupFromLocation, FromLocation.Code, InventoryPostingGroup.Code); + LibraryInventory.UpdateInventoryPostingSetup(FromLocation, InventoryPostingGroup.Code); + + InventoryPostingSetupFromLocation.Get(FromLocation.Code, InventoryPostingGroup.Code); + InventoryPostingSetupToLocation := InventoryPostingSetupFromLocation; + InventoryPostingSetupToLocation."Location Code" := ToLocation.Code; + InventoryPostingSetupToLocation.Insert(false); + + InventoryPostingSetupInTransitLocation := InventoryPostingSetupFromLocation; + InventoryPostingSetupInTransitLocation."Location Code" := InTransitLocation.Code; + InventoryPostingSetupInTransitLocation.Insert(false); + end; + + internal procedure CreateItemWithInventoryPostingGroup(var Item: Record Item; InventoryPostingGroupCode: Code[20]) + begin + LibraryInventory.CreateItem(Item); + Item."Inventory Posting Group" := InventoryPostingGroupCode; + Item.Modify(false); + end; + + internal procedure CreateItemWIthInventoryStock(var Item: Record Item; var FromLocation: Record Location; var InventoryPostingGroup: Record "Inventory Posting Group") + var + ItemJournalLine: Record "Item Journal Line"; + begin + CreateItemWithInventoryPostingGroup(Item, InventoryPostingGroup.Code); + LibraryInventory.CreateItemJournalLineInItemTemplate( + ItemJournalLine, Item."No.", FromLocation.Code, '', LibraryRandom.RandIntInRange(10, 20)); + LibraryInventory.PostItemJournalLine( + ItemJournalLine."Journal Template Name", ItemJournalLine."Journal Batch Name"); + end; + + // Verify procedures + + procedure AssertEDocumentLogs(EDocument: Record "E-Document"; EDocumentService: Record "E-Document Service"; EDocLogList: List of [Enum "E-Document Service Status"]) + var + EDocLog: Record "E-Document Log"; + Count: Integer; + begin + EDocLog.SetRange("E-Doc. Entry No", EDocument."Entry No"); + EDocLog.SetRange("Service Code", EDocumentService.Code); + Assert.AreEqual(EDocLogList.Count(), EDocLog.Count(), 'Wrong number of logs'); + Count := 1; + EDocLog.SetCurrentKey("Entry No."); + EDocLog.SetAscending("Entry No.", true); + if EDocLog.FindSet() then + repeat + Assert.AreEqual(EDocLogList.Get(Count), EDocLog.Status, 'Wrong status'); + Count := Count + 1; + until EDocLog.Next() = 0; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Export", 'OnAfterCreateEDocument', '', false, false)] + local procedure OnAfterCreateEDocument(var EDocument: Record "E-Document") + begin + LibraryVariableStorage.Enqueue(EDocument); + end; +} \ No newline at end of file diff --git a/Apps/DK/EDocumentFormatOIOUBL/test/src/OIOUBLStructuredValidations.Codeunit.al b/Apps/DK/EDocumentFormatOIOUBL/test/src/OIOUBLStructuredValidations.Codeunit.al new file mode 100644 index 0000000000..72c82f5c2e --- /dev/null +++ b/Apps/DK/EDocumentFormatOIOUBL/test/src/OIOUBLStructuredValidations.Codeunit.al @@ -0,0 +1,107 @@ +codeunit 13852 "OIOUBL Structured Validations" +{ + var + Assert: Codeunit Assert; + UnitOfMeasureCodeTok: Label 'PCS', Locked = true; + SalesInvoiceNoTok: Label '103033', Locked = true; + PurchaseorderNoTok: Label '2', Locked = true; + MockDate: Date; + MockCurrencyCode: Code[10]; + MockDataMismatchErr: Label 'The %1 in %2 does not align with the mock data. Expected: %3, Actual: %4', Locked = true, Comment = '%1 = Field caption, %2 = Table caption, %3 = Expected value, %4 = Actual value'; + + + internal procedure AssertFullEDocumentContentExtracted(EDocumentEntryNo: Integer) + var + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + begin + EDocumentPurchaseHeader.Get(EDocumentEntryNo); + Assert.AreEqual(SalesInvoiceNoTok, EDocumentPurchaseHeader."Sales Invoice No.", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Sales Invoice No."), EDocumentPurchaseHeader.TableCaption(), SalesInvoiceNoTok, EDocumentPurchaseHeader."Sales Invoice No.")); + Assert.AreEqual(MockDate, EDocumentPurchaseHeader."Document Date", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Document Date"), EDocumentPurchaseHeader.TableCaption(), MockDate, EDocumentPurchaseHeader."Document Date")); + Assert.AreEqual(CalcDate('<+1M>', MockDate), EDocumentPurchaseHeader."Due Date", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Due Date"), EDocumentPurchaseHeader.TableCaption(), CalcDate('<+1M>', MockDate), EDocumentPurchaseHeader."Due Date")); + Assert.AreEqual(MockCurrencyCode, EDocumentPurchaseHeader."Currency Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Currency Code"), EDocumentPurchaseHeader.TableCaption(), MockCurrencyCode, EDocumentPurchaseHeader."Currency Code")); + Assert.AreEqual(PurchaseorderNoTok, EDocumentPurchaseHeader."Purchase Order No.", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Purchase Order No."), EDocumentPurchaseHeader.TableCaption(), PurchaseorderNoTok, EDocumentPurchaseHeader."Purchase Order No.")); + Assert.AreEqual('CRONUS International', EDocumentPurchaseHeader."Vendor Company Name", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor Company Name"), EDocumentPurchaseHeader.TableCaption(), 'CRONUS International', EDocumentPurchaseHeader."Vendor Company Name")); + Assert.AreEqual('Main Street, 14', EDocumentPurchaseHeader."Vendor Address", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor Address"), EDocumentPurchaseHeader.TableCaption(), 'Main Street, 14', EDocumentPurchaseHeader."Vendor Address")); + Assert.AreEqual('GB123456789', EDocumentPurchaseHeader."Vendor VAT Id", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor VAT Id"), EDocumentPurchaseHeader.TableCaption(), 'GB123456789', EDocumentPurchaseHeader."Vendor VAT Id")); + Assert.AreEqual('Jim Olive', EDocumentPurchaseHeader."Vendor Contact Name", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Vendor Contact Name"), EDocumentPurchaseHeader.TableCaption(), 'Jim Olive', EDocumentPurchaseHeader."Vendor Contact Name")); + Assert.AreEqual('The Cannon Group PLC', EDocumentPurchaseHeader."Customer Company Name", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Customer Company Name"), EDocumentPurchaseHeader.TableCaption(), 'The Cannon Group PLC', EDocumentPurchaseHeader."Customer Company Name")); + Assert.AreEqual('GB789456278', EDocumentPurchaseHeader."Customer VAT Id", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Customer VAT Id"), EDocumentPurchaseHeader.TableCaption(), 'GB789456278', EDocumentPurchaseHeader."Customer VAT Id")); + Assert.AreEqual('192 Market Square', EDocumentPurchaseHeader."Customer Address", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseHeader.FieldCaption("Customer Address"), EDocumentPurchaseHeader.TableCaption(), '192 Market Square', EDocumentPurchaseHeader."Customer Address")); + + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentEntryNo); + EDocumentPurchaseLine.FindSet(); + Assert.AreEqual(1, EDocumentPurchaseLine."Quantity", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Quantity"), EDocumentPurchaseLine.TableCaption(), 1, EDocumentPurchaseLine."Quantity")); + Assert.AreEqual(UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit of Measure"), EDocumentPurchaseLine.TableCaption(), UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure")); + Assert.AreEqual(4000, EDocumentPurchaseLine."Sub Total", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Sub Total"), EDocumentPurchaseLine.TableCaption(), 4000, EDocumentPurchaseLine."Sub Total")); + Assert.AreEqual(MockCurrencyCode, EDocumentPurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Currency Code"), EDocumentPurchaseLine.TableCaption(), MockCurrencyCode, EDocumentPurchaseLine."Currency Code")); + Assert.AreEqual(0, EDocumentPurchaseLine."Total Discount", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Total Discount"), EDocumentPurchaseLine.TableCaption(), 0, EDocumentPurchaseLine."Total Discount")); + Assert.AreEqual('Bicycle', EDocumentPurchaseLine.Description, StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption(Description), EDocumentPurchaseLine.TableCaption(), 'Bicycle', EDocumentPurchaseLine.Description)); + Assert.AreEqual('1000', EDocumentPurchaseLine."Product Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Product Code"), EDocumentPurchaseLine.TableCaption(), '1000', EDocumentPurchaseLine."Product Code")); + Assert.AreEqual(25, EDocumentPurchaseLine."VAT Rate", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("VAT Rate"), EDocumentPurchaseLine.TableCaption(), 25, EDocumentPurchaseLine."VAT Rate")); + Assert.AreEqual(4000, EDocumentPurchaseLine."Unit Price", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit Price"), EDocumentPurchaseLine.TableCaption(), 4000, EDocumentPurchaseLine."Unit Price")); + + EDocumentPurchaseLine.Next(); + Assert.AreEqual(2, EDocumentPurchaseLine."Quantity", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Quantity"), EDocumentPurchaseLine.TableCaption(), 2, EDocumentPurchaseLine."Quantity")); + Assert.AreEqual(UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit of Measure"), EDocumentPurchaseLine.TableCaption(), UnitOfMeasureCodeTok, EDocumentPurchaseLine."Unit of Measure")); + Assert.AreEqual(10000, EDocumentPurchaseLine."Sub Total", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Sub Total"), EDocumentPurchaseLine.TableCaption(), 10000, EDocumentPurchaseLine."Sub Total")); + Assert.AreEqual(MockCurrencyCode, EDocumentPurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Currency Code"), EDocumentPurchaseLine.TableCaption(), MockCurrencyCode, EDocumentPurchaseLine."Currency Code")); + Assert.AreEqual(0, EDocumentPurchaseLine."Total Discount", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Total Discount"), EDocumentPurchaseLine.TableCaption(), 0, EDocumentPurchaseLine."Total Discount")); + Assert.AreEqual('Bicycle v2', EDocumentPurchaseLine.Description, StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption(Description), EDocumentPurchaseLine.TableCaption(), 'Bicycle v2', EDocumentPurchaseLine.Description)); + Assert.AreEqual('2000', EDocumentPurchaseLine."Product Code", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Product Code"), EDocumentPurchaseLine.TableCaption(), '2000', EDocumentPurchaseLine."Product Code")); + Assert.AreEqual(25, EDocumentPurchaseLine."VAT Rate", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("VAT Rate"), EDocumentPurchaseLine.TableCaption(), 25, EDocumentPurchaseLine."VAT Rate")); + Assert.AreEqual(5000, EDocumentPurchaseLine."Unit Price", StrSubstNo(MockDataMismatchErr, EDocumentPurchaseLine.FieldCaption("Unit Price"), EDocumentPurchaseLine.TableCaption(), 5000, EDocumentPurchaseLine."Unit Price")); + end; + + internal procedure AssertPurchaseDocument(VendorNo: Code[20]; PurchaseHeader: Record "Purchase Header"; Item: Record Item) + var + PurchaseLine: Record "Purchase Line"; + Item1NoTok: Label 'GL00000001', Locked = true; + Item2NoTok: Label 'GL00000003', Locked = true; + begin + Assert.AreEqual(SalesInvoiceNoTok, PurchaseHeader."Vendor Invoice No.", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Vendor Invoice No."), PurchaseHeader.TableCaption(), SalesInvoiceNoTok, PurchaseHeader."Vendor Invoice No.")); + Assert.AreEqual(MockDate, PurchaseHeader."Document Date", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Document Date"), PurchaseHeader.TableCaption(), MockDate, PurchaseHeader."Document Date")); + Assert.AreEqual(CalcDate('<+1M>', MockDate), PurchaseHeader."Due Date", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Due Date"), PurchaseHeader.TableCaption(), CalcDate('<+1M>', MockDate), PurchaseHeader."Due Date")); + Assert.AreEqual(MockCurrencyCode, PurchaseHeader."Currency Code", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Currency Code"), PurchaseHeader.TableCaption(), MockCurrencyCode, PurchaseHeader."Currency Code")); + Assert.AreEqual(PurchaseorderNoTok, PurchaseHeader."Vendor Order No.", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Vendor Order No."), PurchaseHeader.TableCaption(), PurchaseorderNoTok, PurchaseHeader."Vendor Order No.")); + Assert.AreEqual(VendorNo, PurchaseHeader."Buy-from Vendor No.", StrSubstNo(MockDataMismatchErr, PurchaseHeader.FieldCaption("Buy-from Vendor No."), PurchaseHeader.TableCaption(), VendorNo, PurchaseHeader."Buy-from Vendor No.")); + + PurchaseLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseLine.FindSet(); + Assert.AreEqual(1, PurchaseLine.Quantity, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Quantity), PurchaseLine.TableCaption(), 1, PurchaseLine.Quantity)); + Assert.AreEqual(4000, PurchaseLine."Direct Unit Cost", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Direct Unit Cost"), PurchaseLine.TableCaption(), 4000, PurchaseLine."Direct Unit Cost")); + Assert.AreEqual(MockCurrencyCode, PurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Currency Code"), PurchaseLine.TableCaption(), MockCurrencyCode, PurchaseLine."Currency Code")); + Assert.AreEqual(0, PurchaseLine."Line Discount Amount", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Line Discount Amount"), PurchaseLine.TableCaption(), 0, PurchaseLine."Line Discount Amount")); + // In the import file we have a name 'Bicycle' but because of Item Cross Reference validation Item description is being used + if Item."No." <> '' then begin + Assert.AreEqual('Bicycle', PurchaseLine.Description, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Description), PurchaseLine.TableCaption(), Item."No.", PurchaseLine.Description)); + Assert.AreEqual(Item."No.", PurchaseLine."No.", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("No."), PurchaseLine.TableCaption(), Item."No.", PurchaseLine."No.")); + Assert.AreEqual(Item."Purch. Unit of Measure", PurchaseLine."Unit of Measure Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Unit of Measure Code"), PurchaseLine.TableCaption(), UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code")); + end else begin + Assert.AreEqual(Item1NoTok, PurchaseLine.Description, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Description), PurchaseLine.TableCaption(), Item1NoTok, PurchaseLine.Description)); + Assert.AreEqual(Item1NoTok, PurchaseLine."No.", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("No."), PurchaseLine.TableCaption(), Item1NoTok, PurchaseLine."No.")); + Assert.AreEqual(UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Unit of Measure Code"), PurchaseLine.TableCaption(), UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code")); + end; + + PurchaseLine.Next(); + Assert.AreEqual(2, PurchaseLine.Quantity, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Quantity), PurchaseLine.TableCaption(), 2, PurchaseLine.Quantity)); + Assert.AreEqual(UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Unit of Measure Code"), PurchaseLine.TableCaption(), UnitOfMeasureCodeTok, PurchaseLine."Unit of Measure Code")); + Assert.AreEqual(5000, PurchaseLine."Direct Unit Cost", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Direct Unit Cost"), PurchaseLine.TableCaption(), 5000, PurchaseLine."Direct Unit Cost")); + Assert.AreEqual(MockCurrencyCode, PurchaseLine."Currency Code", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Currency Code"), PurchaseLine.TableCaption(), MockCurrencyCode, PurchaseLine."Currency Code")); + Assert.AreEqual(0, PurchaseLine."Line Discount Amount", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("Line Discount Amount"), PurchaseLine.TableCaption(), 0, PurchaseLine."Line Discount Amount")); + // In the import file we have a name 'Bicycle v2' but because of Item Cross Reference validation Item description is being used + Assert.AreEqual(Item2NoTok, PurchaseLine.Description, StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption(Description), PurchaseLine.TableCaption(), Item2NoTok, PurchaseLine.Description)); + Assert.AreEqual(Item2NoTok, PurchaseLine."No.", StrSubstNo(MockDataMismatchErr, PurchaseLine.FieldCaption("No."), PurchaseLine.TableCaption(), Item2NoTok, PurchaseLine."No.")); + end; + + procedure SetMockDate(MockDate: Date) + begin + this.MockDate := MockDate; + end; + + procedure SetMockCurrencyCode(MockCurrencyCode: Code[10]) + begin + this.MockCurrencyCode := MockCurrencyCode; + end; +} \ No newline at end of file