diff --git a/Apps/DE/EDocumentDE/app/src/ZUGFeRD/ExportZUGFeRDDocument.Codeunit.al b/Apps/DE/EDocumentDE/app/src/ZUGFeRD/ExportZUGFeRDDocument.Codeunit.al index 7357b0168d..c77c12d36d 100644 --- a/Apps/DE/EDocumentDE/app/src/ZUGFeRD/ExportZUGFeRDDocument.Codeunit.al +++ b/Apps/DE/EDocumentDE/app/src/ZUGFeRD/ExportZUGFeRDDocument.Codeunit.al @@ -634,17 +634,7 @@ codeunit 13917 "Export ZUGFeRD Document" BuyerTradePartyElement.Add(XmlElement.Create('Name', XmlNamespaceRAM, CustomerName)); // Buyer Contact - if PhoneNumber <> '' then begin - ContactElement := XmlElement.Create('DefinedTradeContact', XmlNamespaceRAM); - ContactElement.Add(XmlElement.Create('PersonName', XmlNamespaceRAM, Contact)); - ContactElement.Add(XmlElement.Create('TelephoneUniversalCommunication', XmlNamespaceRAM, - XmlElement.Create('CompleteNumber', XmlNamespaceRAM, PhoneNumber))); - if CustomerEmail <> '' then - ContactElement.Add(XmlElement.Create('EmailURIUniversalCommunication', XmlNamespaceRAM, - XmlElement.Create('URIID', XmlNamespaceRAM, CustomerEmail))); - BuyerTradePartyElement.Add(ContactElement); - end; - + InsertBuyerContact(BuyerTradePartyElement, Contact, PhoneNumber, CustomerEmail); // Buyer Address PostalTradeAddressElement := XmlElement.Create('PostalTradeAddress', XmlNamespaceRAM); @@ -776,6 +766,29 @@ codeunit 13917 "Export ZUGFeRD Document" RootXMLNode.Add(SettlementElement); end; + local procedure InsertBuyerContact(var BuyerTradePartyElement: XmlElement; ContactName: Text; PhoneNumber: Text; EmailAddress: Text) + var + ContactElement: XmlElement; + begin + if (ContactName = '') and (PhoneNumber = '') and (EmailAddress = '') then + exit; + + ContactElement := XmlElement.Create('DefinedTradeContact', XmlNamespaceRAM); + + if ContactName <> '' then + ContactElement.Add(XmlElement.Create('PersonName', XmlNamespaceRAM, ContactName)); + + if PhoneNumber <> '' then + ContactElement.Add(XmlElement.Create('TelephoneUniversalCommunication', XmlNamespaceRAM, + XmlElement.Create('CompleteNumber', XmlNamespaceRAM, PhoneNumber))); + + if EmailAddress <> '' then + ContactElement.Add(XmlElement.Create('EmailURIUniversalCommunication', XmlNamespaceRAM, + XmlElement.Create('URIID', XmlNamespaceRAM, EmailAddress))); + + BuyerTradePartyElement.Add(ContactElement); + end; + local procedure InsertTradeTax(var SettlementElement: XmlElement; var SalesInvLine: Record "Sales Invoice Line"; var LineAmount: Dictionary of [Decimal, Decimal]; var LineVATAmount: Dictionary of [Decimal, Decimal]) begin if SalesInvLine.FindSet() then diff --git a/Apps/DE/EDocumentDE/test/src/ZUGFeRDXMLDocumentTests.Codeunit.al b/Apps/DE/EDocumentDE/test/src/ZUGFeRDXMLDocumentTests.Codeunit.al index c10315bae6..ea3f6d78c0 100644 --- a/Apps/DE/EDocumentDE/test/src/ZUGFeRDXMLDocumentTests.Codeunit.al +++ b/Apps/DE/EDocumentDE/test/src/ZUGFeRDXMLDocumentTests.Codeunit.al @@ -222,6 +222,63 @@ codeunit 13922 "ZUGFeRD XML Document Tests" VerifyBuyerData(SalesInvoiceHeader, TempXMLBuffer); end; + [Test] + procedure ExportPostedSalesInvoiceInZUGFeRDFormatVerifyBuyerContactWithAllFields(); + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + TempXMLBuffer: Record "XML Buffer" temporary; + begin + // [SCENARIO 556034] Export posted sales invoice with all buyer contact fields populated + Initialize(); + + // [GIVEN] Create and Post Sales Invoice with contact, phone, and email. + SalesInvoiceHeader.Get(CreateAndPostSalesDocument("Sales Document Type"::Invoice, Enum::"Sales Line Type"::Item, false)); + + // [WHEN] Export ZUGFeRD Electronic Document. + ExportInvoice(SalesInvoiceHeader, TempXMLBuffer); + + // [THEN] ZUGFeRD Electronic Document is created with all buyer contact fields + VerifyBuyerContactData(SalesInvoiceHeader, TempXMLBuffer, true, true, true); + end; + + [Test] + procedure ExportPostedSalesInvoiceInZUGFeRDFormatVerifyBuyerContactWithoutPhone(); + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + TempXMLBuffer: Record "XML Buffer" temporary; + begin + // [SCENARIO 556034] Export posted sales invoice without buyer phone number + Initialize(); + + // [GIVEN] Create and Post Sales Invoice without phone number. + SalesInvoiceHeader.Get(CreateAndPostSalesDocumentWithoutPhone("Sales Document Type"::Invoice, Enum::"Sales Line Type"::Item)); + + // [WHEN] Export ZUGFeRD Electronic Document. + ExportInvoice(SalesInvoiceHeader, TempXMLBuffer); + + // [THEN] ZUGFeRD Electronic Document is created with contact and email, but no phone + VerifyBuyerContactData(SalesInvoiceHeader, TempXMLBuffer, true, false, true); + end; + + [Test] + procedure ExportPostedSalesInvoiceInZUGFeRDFormatVerifyBuyerContactWithoutContactName(); + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + TempXMLBuffer: Record "XML Buffer" temporary; + begin + // [SCENARIO 556034] Export posted sales invoice without buyer contact name + Initialize(); + + // [GIVEN] Create and Post Sales Invoice without contact name. + SalesInvoiceHeader.Get(CreateAndPostSalesDocumentWithoutContact("Sales Document Type"::Invoice, Enum::"Sales Line Type"::Item)); + + // [WHEN] Export ZUGFeRD Electronic Document. + ExportInvoice(SalesInvoiceHeader, TempXMLBuffer); + + // [THEN] ZUGFeRD Electronic Document is created with phone and email, but no contact name + VerifyBuyerContactData(SalesInvoiceHeader, TempXMLBuffer, false, true, true); + end; + [Test] procedure ExportPostedSalesInvoiceInZUGFeRDFormatVerifyPaymentMeans(); var @@ -1321,6 +1378,22 @@ codeunit 13922 "ZUGFeRD XML Document Tests" exit(LibrarySales.PostSalesDocument(SalesHeader, true, true)); end; + local procedure CreateAndPostSalesDocumentWithoutPhone(DocumentType: Enum "Sales Document Type"; LineType: Enum "Sales Line Type"): Code[20] + var + SalesHeader: Record "Sales Header"; + begin + SalesHeader.Get(DocumentType, CreateSalesDocumentWithoutPhone(DocumentType, LineType)); + exit(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + end; + + local procedure CreateAndPostSalesDocumentWithoutContact(DocumentType: Enum "Sales Document Type"; LineType: Enum "Sales Line Type"): Code[20] + var + SalesHeader: Record "Sales Header"; + begin + SalesHeader.Get(DocumentType, CreateSalesDocumentWithoutContact(DocumentType, LineType)); + exit(LibrarySales.PostSalesDocument(SalesHeader, true, true)); + end; + local procedure CreateAndPostSalesDocumentWithTwoLines(DocumentType: Enum "Sales Document Type"; LineType: Enum "Sales Line Type"; InvoiceDiscount: Boolean): Code[20]; var SalesHeader: Record "Sales Header"; @@ -1441,6 +1514,30 @@ codeunit 13922 "ZUGFeRD XML Document Tests" exit(CreateSalesDocumentWithLine(DocumentType, LineType, InvoiceDiscount, '')); end; + local procedure CreateSalesDocumentWithoutPhone(DocumentType: Enum "Sales Document Type"; LineType: Enum "Sales Line Type"): Code[20] + var + SalesHeader: Record "Sales Header"; + begin + CreateSalesHeader(SalesHeader, DocumentType); + SalesHeader.Validate("Sell-to Phone No.", ''); + SalesHeader.Modify(true); + CreateSalesLine(SalesHeader, LineType, false); + exit(SalesHeader."No."); + end; + + local procedure CreateSalesDocumentWithoutContact(DocumentType: Enum "Sales Document Type"; LineType: Enum "Sales Line Type"): Code[20] + var + SalesHeader: Record "Sales Header"; + begin + CreateSalesHeader(SalesHeader, DocumentType); + SalesHeader.SetHideValidationDialog(true); + SalesHeader.Validate("Sell-to Contact", ''); + SalesHeader.SetHideValidationDialog(false); + SalesHeader.Modify(true); + CreateSalesLine(SalesHeader, LineType, false); + exit(SalesHeader."No."); + end; + local procedure CreateSalesDocumentWithLine(DocumentType: Enum "Sales Document Type"; LineType: Enum "Sales Line Type"; InvoiceDiscount: Boolean; RespCenterCode: Code[20]): Code[20] var SalesHeader: Record "Sales Header"; @@ -1514,6 +1611,7 @@ codeunit 13922 "ZUGFeRD XML Document Tests" SalesHeader.Validate("Ship-to City", PostCode.City); SalesHeader.Validate("Sell-to Address", LibraryUtility.GenerateGUID()); SalesHeader.Validate("Sell-to City", PostCode.City); + SalesHeader.Validate("Sell-to Phone No.", LibraryUtility.GenerateRandomPhoneNo()); SalesHeader.Validate("Your Reference", LibraryUtility.GenerateRandomText(20)); SalesHeader.Validate("Payment Terms Code", PaymentTermsCode); SalesHeader.Validate("Payment Method Code", PaymentMethod.Code); @@ -1882,6 +1980,41 @@ codeunit 13922 "ZUGFeRD XML Document Tests" Assert.AreEqual(GetVATRegistrationNo(SalesInvoiceHeader."VAT Registration No.", CompanyInformation."Country/Region Code"), GetNodeByPathWithError(TempXMLBuffer, Path), StrSubstNo(IncorrectValueErr, Path)); end; + local procedure VerifyBuyerContactData(SalesInvoiceHeader: Record "Sales Invoice Header"; var TempXMLBuffer: Record "XML Buffer" temporary; ExpectContactName: Boolean; ExpectPhone: Boolean; ExpectEmail: Boolean); + var + DocumentBuyerContactTok: Label '/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty/ram:DefinedTradeContact', Locked = true; + Path: Text; + NodeValue: Text; + begin + // Check if DefinedTradeContact element exists when any field is populated + if ExpectContactName or ExpectPhone or ExpectEmail then + Assert.IsTrue(NodeExistsByPath(TempXMLBuffer, DocumentBuyerContactTok), 'DefinedTradeContact element should exist when contact fields are populated'); + + // Verify PersonName + Path := DocumentBuyerContactTok + '/ram:PersonName'; + if ExpectContactName then begin + NodeValue := GetNodeByPathWithError(TempXMLBuffer, Path); + Assert.AreEqual(SalesInvoiceHeader."Sell-to Contact", NodeValue, StrSubstNo(IncorrectValueErr, Path)); + end else + Assert.IsFalse(NodeExistsByPath(TempXMLBuffer, Path), 'PersonName should not exist when contact name is empty'); + + // Verify TelephoneUniversalCommunication/CompleteNumber + Path := DocumentBuyerContactTok + '/ram:TelephoneUniversalCommunication/ram:CompleteNumber'; + if ExpectPhone then begin + NodeValue := GetNodeByPathWithError(TempXMLBuffer, Path); + Assert.AreEqual(SalesInvoiceHeader."Sell-to Phone No.", NodeValue, StrSubstNo(IncorrectValueErr, Path)); + end else + Assert.IsFalse(NodeExistsByPath(TempXMLBuffer, Path), 'TelephoneUniversalCommunication/CompleteNumber should not exist when phone is empty'); + + // Verify EmailURIUniversalCommunication/URIID + Path := DocumentBuyerContactTok + '/ram:EmailURIUniversalCommunication/ram:URIID'; + if ExpectEmail then begin + NodeValue := GetNodeByPathWithError(TempXMLBuffer, Path); + Assert.AreEqual(SalesInvoiceHeader."Sell-to E-Mail", NodeValue, StrSubstNo(IncorrectValueErr, Path)); + end else + Assert.IsFalse(NodeExistsByPath(TempXMLBuffer, Path), 'EmailURIUniversalCommunication/URIID should not exist when email is empty'); + end; + local procedure VerifyBuyerData(SalesCrMemoHeader: Record "Sales Cr.Memo Header"; var TempXMLBuffer: Record "XML Buffer" temporary); var DocumentBuyerTradePartyTok: Label '/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty', Locked = true; @@ -2332,6 +2465,14 @@ codeunit 13922 "ZUGFeRD XML Document Tests" Error('Node not found: %1', XPath); end; + local procedure NodeExistsByPath(var TempXMLBuffer: Record "XML Buffer" temporary; XPath: Text): Boolean + begin + TempXMLBuffer.Reset(); + TempXMLBuffer.SetRange(Type, TempXMLBuffer.Type::Element); + TempXMLBuffer.SetRange(Path, XPath); + exit(TempXMLBuffer.FindFirst()); + end; + local procedure GetLastNodeByPathWithError(var TempXMLBuffer: Record "XML Buffer" temporary; XPath: Text): Text begin TempXMLBuffer.Reset();