diff --git a/Apps/DE/EDocumentDE/app/src/XRechnung/ExportXRechnungDocument.Codeunit.al b/Apps/DE/EDocumentDE/app/src/XRechnung/ExportXRechnungDocument.Codeunit.al index 2b10c9c477..8ccad4abd9 100644 --- a/Apps/DE/EDocumentDE/app/src/XRechnung/ExportXRechnungDocument.Codeunit.al +++ b/Apps/DE/EDocumentDE/app/src/XRechnung/ExportXRechnungDocument.Codeunit.al @@ -1162,28 +1162,35 @@ codeunit 13916 "Export XRechnung Document" AttachmentElement: XmlElement; OutStream: OutStream; InStream: InStream; + MimeCode: Text; + FileName: Text; begin + MimeCode := GetMimeCode(DocumentAttachment); + if not IsValidMimeCode(MimeCode) then + exit; + TempBlob.CreateOutStream(OutStream); DocumentAttachment.ExportToStream(OutStream); TempBlob.CreateInStream(InStream); + FileName := DocumentAttachment."File Name" + '.' + DocumentAttachment."File Extension"; AttachmentElement := XmlElement.Create('AdditionalDocumentReference', XmlNamespaceCAC); - AttachmentElement.Add(XmlElement.Create('ID', XmlNamespaceCBC, DocumentAttachment."File Name" + '.' + DocumentAttachment."File Extension")); + AttachmentElement.Add(XmlElement.Create('ID', XmlNamespaceCBC, FileName)); AttachmentElement.Add(XmlElement.Create('DocumentDescription', XmlNamespaceCBC, DocumentAttachment."File Name")); - AddAttachmentObject(AttachmentElement, InStream, DocumentAttachment); + AddAttachmentObject(AttachmentElement, InStream, MimeCode, FileName); RootElement.Add(AttachmentElement); end; - local procedure AddAttachmentObject(var AttachmentElement: XmlElement; var InStream: InStream; var DocumentAttachment: Record "Document Attachment"); + local procedure AddAttachmentObject(var AttachmentElement: XmlElement; var InStream: InStream; MimeCode: Text; FileName: Text); var Base64Convert: Codeunit "Base64 Convert"; AttachmentObjectElement: XmlElement; begin AttachmentObjectElement := XmlElement.Create('Attachment', XmlNamespaceCAC); AttachmentObjectElement.Add(XmlElement.Create('EmbeddedDocumentBinaryObject', XmlNamespaceCBC, - XmlAttribute.Create('mimeCode', GetMimeCode(DocumentAttachment)), - XmlAttribute.Create('filename', DocumentAttachment."File Name" + '.' + DocumentAttachment."File Extension"), + XmlAttribute.Create('mimeCode', MimeCode), + XmlAttribute.Create('filename', FileName), Base64Convert.ToBase64(InStream))); AttachmentElement.Add(AttachmentObjectElement); end; @@ -1192,9 +1199,14 @@ codeunit 13916 "Export XRechnung Document" begin case DocumentAttachment."File Type" of "Document Attachment File Type"::Image: - exit('image/' + LowerCase(DocumentAttachment."File Extension")); + case LowerCase(DocumentAttachment."File Extension") of + 'png': + exit('image/png'); + 'jpeg', 'jpg': + exit('image/jpeg'); + end; "Document Attachment File Type"::PDF: - exit('application/' + LowerCase(DocumentAttachment."File Extension")); + exit('application/pdf'); "Document Attachment File Type"::Excel: exit('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); "Document Attachment File Type"::Other: @@ -1212,6 +1224,20 @@ codeunit 13916 "Export XRechnung Document" end; end; + local procedure IsValidMimeCode(MimeCode: Text): Boolean + begin + case MimeCode of + 'application/pdf', + 'image/png', + 'image/jpeg', + 'text/csv', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.oasis.opendocument.spreadsheet': + exit(true); + end; + exit(false); + end; + local procedure CalculateLineAmounts(SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesInvLine: Record "Sales Invoice Line"; Currency: Record Currency; var LineAmounts: Dictionary of [Text, Decimal]) var TotalInvDiscountAmount: Decimal; diff --git a/Apps/DE/EDocumentDE/test/.resources/CRONUS.jpg b/Apps/DE/EDocumentDE/test/.resources/CRONUS.jpg new file mode 100644 index 0000000000..e562b8d54e Binary files /dev/null and b/Apps/DE/EDocumentDE/test/.resources/CRONUS.jpg differ diff --git a/Apps/DE/EDocumentDE/test/.resources/d365businesscentral.bmp b/Apps/DE/EDocumentDE/test/.resources/d365businesscentral.bmp new file mode 100644 index 0000000000..2f6e69fd29 Binary files /dev/null and b/Apps/DE/EDocumentDE/test/.resources/d365businesscentral.bmp differ diff --git a/Apps/DE/EDocumentDE/test/app.json b/Apps/DE/EDocumentDE/test/app.json index 8c60e454b8..14374e709e 100644 --- a/Apps/DE/EDocumentDE/test/app.json +++ b/Apps/DE/EDocumentDE/test/app.json @@ -70,5 +70,8 @@ "allowDownloadingSource": true, "includeSourceInSymbolFile": true }, + "resourceFolders": [ + ".resources" + ], "application": "29.0.0.0" } diff --git a/Apps/DE/EDocumentDE/test/src/XRechnungXMLDocumentTests.Codeunit.al b/Apps/DE/EDocumentDE/test/src/XRechnungXMLDocumentTests.Codeunit.al index 134b4b2517..3b41cbbe89 100644 --- a/Apps/DE/EDocumentDE/test/src/XRechnungXMLDocumentTests.Codeunit.al +++ b/Apps/DE/EDocumentDE/test/src/XRechnungXMLDocumentTests.Codeunit.al @@ -1394,6 +1394,67 @@ codeunit 13918 "XRechnung XML Document Tests" end; #endregion + #region DocumentAttachmentFiltering + [Test] + procedure ExportPostedSalesInvoiceInXRechnungFormatVerifyUnsupportedAttachmentIsSkipped(); + var + SalesInvoiceHeader: Record "Sales Invoice Header"; + TempXMLBuffer: Record "XML Buffer" temporary; + RecRef: RecordRef; + CSVText: Text; + begin + // [SCENARIO] Attachments with unsupported MIME types are not exported in XRechnung format + Initialize(); + + // [GIVEN] Create and Post Sales Invoice + SalesInvoiceHeader.Get(CreateAndPostSalesDocument("Sales Document Type"::Invoice, "Sales Line Type"::Item, false)); + RecRef.GetTable(SalesInvoiceHeader); + + // [GIVEN] Create one supported CSV attachment and one unsupported TXT attachment + CSVText := CreateCSVDocumentAttachment(RecRef, 'data.csv'); + CreateDocumentAttachment(RecRef, 'report.txt', 'Some text content'); + + // [WHEN] Export XRechnung Electronic Document + ExportInvoice(SalesInvoiceHeader, TempXMLBuffer); + + // [THEN] Only the CSV attachment (supported) is present; TXT is skipped + VerifyAdditionalDocumentReferenceCount(TempXMLBuffer, 1); + VerifyCSVAttachmentInXML(TempXMLBuffer, 'data.csv', 'text/csv', CSVText); + end; + + [Test] + procedure ExportPostedSalesInvoiceInXRechnungFormatVerifyUnsupportedImageExtensionIsSkipped(); + var + DocumentAttachment: Record "Document Attachment"; + SalesInvoiceHeader: Record "Sales Invoice Header"; + TempXMLBuffer: Record "XML Buffer" temporary; + Base64Convert: Codeunit "Base64 Convert"; + TempBlob: Codeunit "Temp Blob"; + RecRef: RecordRef; + begin + // [SCENARIO] Image attachments with unsupported extensions (e.g. bmp) are skipped; supported ones (jpg) are exported + Initialize(); + + // [GIVEN] Create and Post Sales Invoice + SalesInvoiceHeader.Get(CreateAndPostSalesDocument("Sales Document Type"::Invoice, "Sales Line Type"::Item, false)); + RecRef.GetTable(SalesInvoiceHeader); + + // [GIVEN] Create one unsupported BMP image and one supported JGP image attachment + LoadFileFromResourceFolders('d365businesscentral.bmp', TempBlob); + DocumentAttachment.SaveAttachment(RecRef, 'd365businesscentral.bmp', TempBlob); + LoadFileFromResourceFolders('CRONUS.jpg', TempBlob); + Clear(DocumentAttachment); + DocumentAttachment.SaveAttachment(RecRef, 'CRONUS.jpg', TempBlob); + + // [WHEN] Export XRechnung Electronic Document + ExportInvoice(SalesInvoiceHeader, TempXMLBuffer); + + // [THEN] Only the JPG attachment (supported) is present; BMP is skipped + VerifyAdditionalDocumentReferenceCount(TempXMLBuffer, 1); + VerifyAttachmentInXML(TempXMLBuffer, 'CRONUS.jpg', 'image/jpeg', ''); + end; + #endregion + local procedure CreateAndPostSalesDocument(DocumentType: Enum "Sales Document Type"; LineType: Enum "Sales Line Type"; InvoiceDiscount: Boolean): Code[20]; var SalesHeader: Record "Sales Header"; @@ -2421,10 +2482,10 @@ codeunit 13918 "XRechnung XML Document Tests" VerifyAdditionalDocumentReferenceCount(TempXMLBuffer, 2); // [THEN] First attachment is verified in XML with correct ID, MIME type, and content - VerifyAttachmentInXML(TempXMLBuffer, FileName1, 'text/csv', CSVText1); + VerifyCSVAttachmentInXML(TempXMLBuffer, FileName1, 'text/csv', CSVText1); // [THEN] Second attachment is verified in XML with correct ID, MIME type, and content - VerifyAttachmentInXML(TempXMLBuffer, FileName2, 'text/csv', CSVText2); + VerifyCSVAttachmentInXML(TempXMLBuffer, FileName2, 'text/csv', CSVText2); end; @@ -2436,9 +2497,17 @@ codeunit 13918 "XRechnung XML Document Tests" Assert.AreEqual(ExpectedCount, TempXMLBuffer.Count, 'Incorrect number of AdditionalDocumentReference nodes'); end; - local procedure VerifyAttachmentInXML(var TempXMLBuffer: Record "XML Buffer" temporary; AttachmentID: Text; ExpectedMIMEType: Text; ExpectedCSVText: Text) + + local procedure VerifyCSVAttachmentInXML(var TempXMLBuffer: Record "XML Buffer" temporary; AttachmentID: Text; ExpectedMIMEType: Text; ExpectedCSVText: Text) var Base64Convert: Codeunit "Base64 Convert"; + Base64EncodedContent: Text; + begin + Base64EncodedContent := Base64Convert.ToBase64(ExpectedCSVText); + end; + + local procedure VerifyAttachmentInXML(var TempXMLBuffer: Record "XML Buffer" temporary; AttachmentID: Text; ExpectedMIMEType: Text; ExpectedBase64Content: Text) + var TempXMLBufferAttachment: Record "XML Buffer" temporary; TempXMLBufferChild: Record "XML Buffer" temporary; DecodedText: Text; @@ -2488,9 +2557,8 @@ codeunit 13918 "XRechnung XML Document Tests" if TempXMLBufferChild.FindFirst() then Assert.AreEqual(ExpectedMIMEType, TempXMLBufferChild.Value, 'Incorrect MIME type'); - // Verify decoded content - DecodedText := Base64Convert.FromBase64(EncodedContent); - Assert.AreEqual(ExpectedCSVText, DecodedText, 'Decoded attachment content does not match original CSV text'); + if ExpectedBase64Content <> '' then + Assert.AreEqual(ExpectedBase64Content, EncodedContent, 'Attachment content does not match original value'); end else Error('EmbeddedDocumentBinaryObject not found for attachment %1', AttachmentID); end else @@ -2767,6 +2835,28 @@ codeunit 13918 "XRechnung XML Document Tests" exit(CSVText); end; + local procedure CreateDocumentAttachment(RecRef: RecordRef; FileName: Text; ContentText: Text) + var + DocumentAttachment: Record "Document Attachment"; + TempBlob: Codeunit "Temp Blob"; + OutStream: OutStream; + begin + TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8); + OutStream.WriteText(ContentText); + DocumentAttachment.SaveAttachment(RecRef, FileName, TempBlob); + end; + + local procedure LoadFileFromResourceFolders(FilePath: Text; var TempBlob: Codeunit "Temp Blob") + var + ImageOutStream: OutStream; + FileInStream: InStream; + begin + Clear(TempBlob); + NavApp.GetResource(FilePath, FileInStream); + ImageOutStream := TempBlob.CreateOutStream(); + CopyStream(ImageOutStream, FileInStream); + end; + local procedure GetCurrencyCode(DocumentCurrencyCode: Code[10]; var Currency: Record Currency): Code[10] begin if DocumentCurrencyCode = '' then begin