diff --git a/src/Apps/W1/EDocument/App/app.json b/src/Apps/W1/EDocument/App/app.json index 42a39ca645..c7b0e49a5b 100644 --- a/src/Apps/W1/EDocument/App/app.json +++ b/src/Apps/W1/EDocument/App/app.json @@ -57,6 +57,10 @@ { "from": 6234, "to": 6234 + }, + { + "from": 6401, + "to": 6410 } ], "resourceExposurePolicy": { diff --git a/src/Apps/W1/EDocument/App/src/Processing/EDocImport.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/EDocImport.Codeunit.al index c5d9bf88eb..a349265ed3 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/EDocImport.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/EDocImport.Codeunit.al @@ -136,13 +136,16 @@ codeunit 6140 "E-Doc. Import" var EDocDraftSessionTelemetry: Codeunit "E-Doc. Imp. Session Telemetry"; EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + EDocImportErrorContext: Codeunit "E-Doc. Import Error Context"; LastErrorText: Text; begin EDocumentErrorHelper.ClearErrorMessages(EDocument); Commit(); + BindSubscription(EDocImportErrorContext); if not ImportEDocumentProcess.Run() then begin LastErrorText := GetLastErrorText(); if LastErrorText <> '' then begin // We don't insert an error when empty, following the convention of empty error meaning "operation cancelled by user" + LastErrorText := EDocImportErrorContext.WrapErrorMessage(LastErrorText); EDocument.SetRecFilter(); EDocument.FindFirst(); @@ -154,8 +157,10 @@ codeunit 6140 "E-Doc. Import" end; EDocDraftSessionTelemetry.SetText('Step', Format(ImportEDocumentProcess.GetStep())); EDocDraftSessionTelemetry.SetBool('Success', false); + UnbindSubscription(EDocImportErrorContext); exit(false); end; + UnbindSubscription(EDocImportErrorContext); exit(true); end; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/EDocImportErrorContext.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/EDocImportErrorContext.Codeunit.al new file mode 100644 index 0000000000..0464aded9d --- /dev/null +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/EDocImportErrorContext.Codeunit.al @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Processing.Import; + +codeunit 6199 "E-Doc. Import Error Context" +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + EventSubscriberInstance = Manual; + + var + CurrentContext: Text; + AdditionalFieldContextLbl: Label 'While applying additional field "%1" (ID %2) with value ''%3''', Comment = '%1 = Field Name, %2 = Field Number, %3 = Value'; + ValidatingFieldLbl: Label 'While validating field "%1"', Comment = '%1 = Field Caption'; + WrapErrorLbl: Label '%1: %2', Comment = '%1 = Context, %2 = Original Error'; + + /// + /// Returns whether a context message is currently set. + /// + /// True if a context message is set, false otherwise. + procedure HasContext(): Boolean + begin + exit(CurrentContext <> ''); + end; + + /// + /// Wraps the original error message with the current context, if one is set. + /// + /// The original error message to wrap. + /// The error message prefixed with the current context, or the original message if no context is set. + procedure WrapErrorMessage(OriginalError: Text): Text + begin + if CurrentContext = '' then + exit(OriginalError); + exit(StrSubstNo(WrapErrorLbl, CurrentContext, OriginalError)); + end; + + /// + /// Clears the additional field context + /// + procedure ClearAdditionalFieldContext() + begin + CurrentContext := ''; + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Import Error Context", OnValidateFieldWithContext, '', false, false)] + local procedure ValidateFieldWithContextSubscriber(FieldCaption: Text) + begin + CurrentContext := StrSubstNo(ValidatingFieldLbl, FieldCaption); + end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"E-Doc. Import Error Context", OnSetAdditionalFieldContext, '', false, false)] + local procedure SetAdditionalFieldContext(FieldName: Text; FieldNo: Integer; Value: Text) + begin + CurrentContext := StrSubstNo(AdditionalFieldContextLbl, FieldName, FieldNo, Value); + end; + + /// + /// Sets the context to describe a field being validated during e-document import. + /// + /// The caption of the field being validated. + [IntegrationEvent(false, false)] + procedure OnValidateFieldWithContext(FieldCaption: Text) + begin + end; + + /// + /// Sets the context to describe an additional field being applied + /// + /// The name of the additional field being applied. + /// The ID of the additional field being applied. + /// The value being applied to the field. + [IntegrationEvent(false, false)] + procedure OnSetAdditionalFieldContext(FieldName: Text; FieldNo: Integer; Value: Text) + begin + end; +} diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al index 5744937013..b684b1fcfd 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocCreatePurchaseInvoice.Codeunit.al @@ -112,6 +112,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, EDocumentPurchaseLine: Record "E-Document Purchase Line"; PurchaseLine: Record "Purchase Line"; EDocRecordLink: Record "E-Doc. Record Link"; + EDocPurchaseDocumentHelper: Codeunit "E-Doc. Purch. Doc. Helper"; PurchCalcDiscByType: Codeunit "Purch - Calc Disc. By Type"; EDocLineByReceipt: Query "E-Doc. Line by Receipt"; LastReceiptNo: Code[20]; @@ -132,9 +133,9 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, PurchaseHeader."Pay-to Vendor No." := EDocumentPurchaseHeader."[BC] Vendor No."; PurchaseHeader."Posting Description" := EDocumentPurchaseHeader."Posting Description"; if EDocumentPurchaseHeader."Document Date" <> 0D then - PurchaseHeader.Validate("Document Date", EDocumentPurchaseHeader."Document Date"); + EDocPurchaseDocumentHelper.ValidateFieldWithContext(PurchaseHeader, PurchaseHeader.FieldNo("Document Date"), EDocumentPurchaseHeader."Document Date"); if EDocumentPurchaseHeader."Due Date" <> 0D then - PurchaseHeader.Validate("Due Date", EDocumentPurchaseHeader."Due Date"); + EDocPurchaseDocumentHelper.ValidateFieldWithContext(PurchaseHeader, PurchaseHeader.FieldNo("Due Date"), EDocumentPurchaseHeader."Due Date"); VendorInvoiceNo := CopyStr(EDocumentPurchaseHeader."Sales Invoice No.", 1, MaxStrLen(PurchaseHeader."Vendor Invoice No.")); VendorLedgerEntry.SetLoadFields("Entry No."); @@ -145,7 +146,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, Error(InvoiceAlreadyExistsErr, VendorInvoiceNo, EDocumentPurchaseHeader."[BC] Vendor No."); end; - PurchaseHeader.Validate("Vendor Invoice No.", VendorInvoiceNo); + EDocPurchaseDocumentHelper.ValidateFieldWithContext(PurchaseHeader, PurchaseHeader.FieldNo("Vendor Invoice No."), VendorInvoiceNo); PurchaseHeader.Insert(true); PurchaseHeader."Invoice Received Date" := PurchaseHeader."Document Date"; @@ -154,7 +155,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, // Validate of currency has to happen after insert. GLSetup.GetRecordOnce(); if EDocumentPurchaseHeader."Currency Code" <> GLSetup.GetCurrencyCode('') then begin - PurchaseHeader.Validate("Currency Code", EDocumentPurchaseHeader."Currency Code"); + EDocPurchaseDocumentHelper.ValidateFieldWithContext(PurchaseHeader, PurchaseHeader.FieldNo("Currency Code"), EDocumentPurchaseHeader."Currency Code"); PurchaseHeader.Modify(); end; EDocRecordLink.InsertEDocumentHeaderLink(EDocumentPurchaseHeader, PurchaseHeader); diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al new file mode 100644 index 0000000000..007eb6dd13 --- /dev/null +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/FinishDraft/EDocPurchDocHelper.Codeunit.al @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Processing.Import; + +using Microsoft.Purchases.Document; + +/// +/// Shared logic for creating BC purchase documents (invoices and credit memos) from e-document draft data. +/// +codeunit 6402 "E-Doc. Purch. Doc. Helper" +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + procedure ValidateFieldWithContext(var Rec: Record "Purchase Header"; FieldNo: Integer; Value: Variant) + var + VariantRec: Variant; + begin + VariantRec := Rec; + ValidateFieldWithContext(VariantRec, FieldNo, Value); + Rec := VariantRec; + end; + + local procedure ValidateFieldWithContext(var RecVariant: Variant; FieldNo: Integer; Value: Variant) + var + EDocImportErrorContext: Codeunit "E-Doc. Import Error Context"; + RecRef: RecordRef; + FldRef: FieldRef; + begin + RecRef.GetTable(RecVariant); + FldRef := RecRef.Field(FieldNo); + EDocImportErrorContext.OnValidateFieldWithContext(FldRef.Caption()); + FldRef.Validate(Value); + RecRef.SetTable(RecVariant); + end; +} diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocPurchaseHistMapping.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocPurchaseHistMapping.Codeunit.al index 8b2e8ffbdb..80786575f1 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocPurchaseHistMapping.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocPurchaseHistMapping.Codeunit.al @@ -212,8 +212,10 @@ codeunit 6120 "E-Doc. Purchase Hist. Mapping" var EDocPurchLineFieldSetup: Record "ED Purchase Line Field Setup"; EDocPurchLineField: Record "E-Document Line - Field"; + EDocImportErrorContext: Codeunit "E-Doc. Import Error Context"; NewPurchLineRecordRef: RecordRef; NewPurchLineFieldRef: FieldRef; + FieldValue: Variant; begin if not EDocPurchLineFieldSetup.FindSet() then exit; @@ -223,7 +225,10 @@ codeunit 6120 "E-Doc. Purchase Hist. Mapping" continue; EDocPurchLineField.Get(EDocumentPurchaseLine, EDocPurchLineFieldSetup); NewPurchLineFieldRef := NewPurchLineRecordRef.Field(EDocPurchLineFieldSetup."Field No."); - NewPurchLineFieldRef.Validate(EDocPurchLineField.GetValue()); + FieldValue := EDocPurchLineField.GetValue(); + EDocImportErrorContext.OnSetAdditionalFieldContext(NewPurchLineFieldRef.Name(), EDocPurchLineFieldSetup."Field No.", EDocPurchLineField.GetValueAsText()); + NewPurchLineFieldRef.Validate(FieldValue); + EDocImportErrorContext.ClearAdditionalFieldContext(); until EDocPurchLineFieldSetup.Next() = 0; NewPurchLineRecordRef.SetTable(PurchaseLine); end; diff --git a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al index 82e79a65fb..508b6732ee 100644 --- a/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Processing/EDocProcessTest.Codeunit.al @@ -25,11 +25,13 @@ using Microsoft.Purchases.Vendor; using Microsoft.Sales.Customer; using System.IO; using System.TestLibraries.Utilities; +using System.Utilities; codeunit 139883 "E-Doc Process Test" { Subtype = Test; TestType = IntegrationTest; + TestPermissions = Disabled; var Customer: Record Customer; @@ -581,6 +583,333 @@ codeunit 139883 "E-Doc Process Test" Assert.AreNotEqual(Location.Code, PurchaseLine."Location Code", 'The location code should not be set on the purchase line.'); end; + [Test] + procedure AdditionalFieldWithInvalidValueEnrichesErrorMessage() + var + EDocPurchLineFieldSetup: Record "ED Purchase Line Field Setup"; + PurchaseInvoiceLine: Record "Purch. Inv. Line"; + EDocument: Record "E-Document"; + EDocImportParams: Record "E-Doc. Import Parameters"; + PurchaseHeader: Record "Purchase Header"; + EDocPurchLineField: Record "E-Document Line - Field"; + EDocPurchaseLine: Record "E-Document Purchase Line"; + ErrorMessage: Record "Error Message"; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + begin + // [SCENARIO] An additional field is configured with an invalid value that fails FieldRef.Validate. + // The error message should contain the additional field name, ID, and value. + Initialize(Enum::"Service Integration"::"Mock"); + + // [GIVEN] An additional field is configured for Location Code (Code[10]) + EDocPurchLineFieldSetup."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineFieldSetup.Insert(); + + // [GIVEN] An inbound e-document is received and a draft created + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + Assert.IsTrue(LibraryEDoc.CreateInboundPEPPOLDocumentToState(EDocument, EDocumentService, 'peppol/peppol-invoice-0.xml', EDocImportParams), 'The draft for the e-document should be created'); + + // [GIVEN] A value that does not exist as a Location Code + EDocPurchLineField."E-Document Entry No." := EDocument."Entry No"; + EDocPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); + EDocPurchaseLine.FindFirst(); + EDocPurchLineField."Line No." := EDocPurchaseLine."Line No."; + EDocPurchLineField."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineField."Code Value" := 'INVALID'; + EDocPurchLineField.Insert(); + + // [WHEN] Finalizing the draft + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams); + + // [THEN] The e-document should have an error + EDocument.Get(EDocument."Entry No"); + Assert.IsTrue(EDocumentErrorHelper.HasErrors(EDocument), 'The e-document should have errors'); + + // [THEN] The error message should reference the additional field name, ID, and value + ErrorMessage.SetRange("Context Record ID", EDocument.RecordId()); + ErrorMessage.SetRange("Message Type", ErrorMessage."Message Type"::Error); + ErrorMessage.FindFirst(); + Assert.ExpectedMessage('While applying additional field "Location Code"', ErrorMessage."Message"); + Assert.ExpectedMessage(Format(PurchaseInvoiceLine.FieldNo("Location Code")), ErrorMessage."Message"); + Assert.ExpectedMessage('INVALID', ErrorMessage."Message"); + + // [THEN] No purchase invoice should have been created + PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); + Assert.RecordIsEmpty(PurchaseHeader); + end; + + [Test] + procedure AdditionalFieldValueExceedingFieldLengthEnrichesErrorMessage() + var + EDocPurchLineFieldSetup: Record "ED Purchase Line Field Setup"; + PurchaseInvoiceLine: Record "Purch. Inv. Line"; + EDocument: Record "E-Document"; + EDocImportParams: Record "E-Doc. Import Parameters"; + PurchaseHeader: Record "Purchase Header"; + EDocPurchLineField: Record "E-Document Line - Field"; + EDocPurchaseLine: Record "E-Document Purchase Line"; + ErrorMessage: Record "Error Message"; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + FieldValue: Code[2048]; + begin + // [SCENARIO] An additional field is configured with a value that exceeds the target field's maximum length. + // The error message should reference the additional field name, ID, and the overlong value. + Initialize(Enum::"Service Integration"::"Mock"); + + // [GIVEN] An additional field is configured for Location Code (Code[10]) + EDocPurchLineFieldSetup."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineFieldSetup.Insert(); + + // [GIVEN] An inbound e-document is received and a draft created + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + Assert.IsTrue(LibraryEDoc.CreateInboundPEPPOLDocumentToState(EDocument, EDocumentService, 'peppol/peppol-invoice-0.xml', EDocImportParams), 'The draft for the e-document should be created'); + + // [GIVEN] A value that exceeds the target field length (Code[10]) + FieldValue := 'LONGLOCCODE1'; // 12 characters, exceeds Code[10] + EDocPurchLineField."E-Document Entry No." := EDocument."Entry No"; + EDocPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); + EDocPurchaseLine.FindFirst(); + EDocPurchLineField."Line No." := EDocPurchaseLine."Line No."; + EDocPurchLineField."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineField."Code Value" := FieldValue; + EDocPurchLineField.Insert(); + + // [WHEN] Finalizing the draft + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams); + + // [THEN] The e-document should have an error + EDocument.Get(EDocument."Entry No"); + Assert.IsTrue(EDocumentErrorHelper.HasErrors(EDocument), 'The e-document should have errors'); + + // [THEN] The error message should reference the additional field name and value + ErrorMessage.SetRange("Context Record ID", EDocument.RecordId()); + ErrorMessage.SetRange("Message Type", ErrorMessage."Message Type"::Error); + ErrorMessage.FindFirst(); + Assert.ExpectedMessage('While applying additional field "Location Code"', ErrorMessage."Message"); + Assert.ExpectedMessage(FieldValue, ErrorMessage."Message"); + + // [THEN] No purchase invoice should have been created + PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); + Assert.RecordIsEmpty(PurchaseHeader); + end; + + [Test] + procedure StandardFieldValidationFailureEnrichesErrorMessage() + var + EDocument: Record "E-Document"; + EDocImportParams: Record "E-Doc. Import Parameters"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + PurchaseHeader: Record "Purchase Header"; + ErrorMessage: Record "Error Message"; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + begin + // [SCENARIO] A standard field validation fails during purchase invoice creation. + // The error message should contain the field caption. + Initialize(Enum::"Service Integration"::"Mock"); + + // [GIVEN] An inbound e-document is received and a draft created + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + Assert.IsTrue(LibraryEDoc.CreateInboundPEPPOLDocumentToState(EDocument, EDocumentService, 'peppol/peppol-invoice-0.xml', EDocImportParams), 'The draft for the e-document should be created'); + + // [GIVEN] The draft has an invalid currency code + EDocumentPurchaseHeader.GetFromEDocument(EDocument); + EDocumentPurchaseHeader."Currency Code" := 'INVCURR'; + EDocumentPurchaseHeader.Modify(); + + // [WHEN] Finalizing the draft + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams); + + // [THEN] The e-document should have an error + EDocument.Get(EDocument."Entry No"); + Assert.IsTrue(EDocumentErrorHelper.HasErrors(EDocument), 'The e-document should have errors'); + + // [THEN] The error message should reference the Currency Code field + ErrorMessage.SetRange("Context Record ID", EDocument.RecordId()); + ErrorMessage.SetRange("Message Type", ErrorMessage."Message Type"::Error); + ErrorMessage.FindFirst(); + Assert.ExpectedMessage('While validating field "Currency Code"', ErrorMessage."Message"); + + // [THEN] No purchase invoice should have been created + PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); + Assert.RecordIsEmpty(PurchaseHeader); + end; + + [Test] + procedure SuccessfulImportWithAdditionalFieldsHasNoErrors() + var + EDocPurchLineFieldSetup: Record "ED Purchase Line Field Setup"; + PurchaseInvoiceLine: Record "Purch. Inv. Line"; + EDocument: Record "E-Document"; + EDocImportParams: Record "E-Doc. Import Parameters"; + PurchaseHeader: Record "Purchase Header"; + EDocPurchLineField: Record "E-Document Line - Field"; + EDocPurchaseLine: Record "E-Document Purchase Line"; + ErrorMessage: Record "Error Message"; + Location: Record Location; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + begin + // [SCENARIO] Additional fields are configured with valid values. + // The import should succeed with no errors or warnings. + Initialize(Enum::"Service Integration"::"Mock"); + + // [GIVEN] An additional field is configured for Location Code + EDocPurchLineFieldSetup."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineFieldSetup.Insert(); + + // [GIVEN] A valid location exists + Location.Code := 'VALIDLOC'; + if Location.Insert() then; + + // [GIVEN] An inbound e-document is received and a draft created + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + Assert.IsTrue(LibraryEDoc.CreateInboundPEPPOLDocumentToState(EDocument, EDocumentService, 'peppol/peppol-invoice-0.xml', EDocImportParams), 'The draft for the e-document should be created'); + + // [GIVEN] The additional field has a valid value + EDocPurchLineField."E-Document Entry No." := EDocument."Entry No"; + EDocPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); + EDocPurchaseLine.FindFirst(); + EDocPurchLineField."Line No." := EDocPurchaseLine."Line No."; + EDocPurchLineField."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineField."Code Value" := 'VALIDLOC'; + EDocPurchLineField.Insert(); + + // [WHEN] Finalizing the draft + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + Assert.IsTrue(EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams), 'The finalization should succeed'); + + // [THEN] The e-document should have no errors + EDocument.Get(EDocument."Entry No"); + Assert.IsFalse(EDocumentErrorHelper.HasErrors(EDocument), 'The e-document should not have errors'); + + // [THEN] No error or warning messages should exist + ErrorMessage.SetRange("Context Record ID", EDocument.RecordId()); + Assert.RecordIsEmpty(ErrorMessage); + + // [THEN] A purchase invoice should have been created + PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); + Assert.RecordIsNotEmpty(PurchaseHeader); + end; + + [Test] + procedure MultipleAdditionalFieldsFailureOnSecondHasCorrectContext() + var + EDocPurchLineFieldSetup: Record "ED Purchase Line Field Setup"; + PurchaseInvoiceLine: Record "Purch. Inv. Line"; + EDocument: Record "E-Document"; + EDocImportParams: Record "E-Doc. Import Parameters"; + PurchaseHeader: Record "Purchase Header"; + EDocPurchLineField: Record "E-Document Line - Field"; + EDocPurchaseLine: Record "E-Document Purchase Line"; + ErrorMessage: Record "Error Message"; + Location: Record Location; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + begin + // [SCENARIO] Two additional fields are configured. The first has a valid value, the second has an invalid value. + // The error message should reference the second field, not the first. + Initialize(Enum::"Service Integration"::"Mock"); + + // [GIVEN] Two additional fields configured: Location Code and Bin Code + EDocPurchLineFieldSetup."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineFieldSetup.Insert(); + Clear(EDocPurchLineFieldSetup); + EDocPurchLineFieldSetup."Field No." := PurchaseInvoiceLine.FieldNo("Bin Code"); + EDocPurchLineFieldSetup.Insert(); + + // [GIVEN] A valid location exists + Location.Code := 'MULTILOC'; + if Location.Insert() then; + + // [GIVEN] An inbound e-document is received and a draft created + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + Assert.IsTrue(LibraryEDoc.CreateInboundPEPPOLDocumentToState(EDocument, EDocumentService, 'peppol/peppol-invoice-0.xml', EDocImportParams), 'The draft for the e-document should be created'); + + // [GIVEN] First field (Location Code) has a valid value, second field (Bin Code) has an invalid value + EDocPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); + EDocPurchaseLine.FindFirst(); + + EDocPurchLineField."E-Document Entry No." := EDocument."Entry No"; + EDocPurchLineField."Line No." := EDocPurchaseLine."Line No."; + EDocPurchLineField."Field No." := PurchaseInvoiceLine.FieldNo("Location Code"); + EDocPurchLineField."Code Value" := 'MULTILOC'; + EDocPurchLineField.Insert(); + + Clear(EDocPurchLineField); + EDocPurchLineField."E-Document Entry No." := EDocument."Entry No"; + EDocPurchLineField."Line No." := EDocPurchaseLine."Line No."; + EDocPurchLineField."Field No." := PurchaseInvoiceLine.FieldNo("Bin Code"); + EDocPurchLineField."Code Value" := 'INVALIDBIN'; + EDocPurchLineField.Insert(); + + // [WHEN] Finalizing the draft + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams); + + // [THEN] The e-document should have an error + EDocument.Get(EDocument."Entry No"); + Assert.IsTrue(EDocumentErrorHelper.HasErrors(EDocument), 'The e-document should have errors'); + + // [THEN] The error message should reference the second field (Bin Code), not the first (Location Code) + ErrorMessage.SetRange("Context Record ID", EDocument.RecordId()); + ErrorMessage.SetRange("Message Type", ErrorMessage."Message Type"::Error); + ErrorMessage.FindFirst(); + Assert.ExpectedMessage('Bin Code', ErrorMessage."Message"); + Assert.ExpectedMessage('INVALIDBIN', ErrorMessage."Message"); + + // [THEN] No purchase invoice should have been created + PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); + Assert.RecordIsEmpty(PurchaseHeader); + end; + + [Test] + procedure NoAdditionalFieldsStandardFieldFailureStillEnriched() + var + EDocument: Record "E-Document"; + EDocImportParams: Record "E-Doc. Import Parameters"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + PurchaseHeader: Record "Purchase Header"; + ErrorMessage: Record "Error Message"; + EDocImport: Codeunit "E-Doc. Import"; + EDocumentErrorHelper: Codeunit "E-Document Error Helper"; + begin + // [SCENARIO] No additional fields are configured. A standard field validation fails. + // The error message should still be enriched with the field context. + Initialize(Enum::"Service Integration"::"Mock"); + + // [GIVEN] An inbound e-document is received and a draft created (no additional fields configured) + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Prepare draft"; + Assert.IsTrue(LibraryEDoc.CreateInboundPEPPOLDocumentToState(EDocument, EDocumentService, 'peppol/peppol-invoice-0.xml', EDocImportParams), 'The draft for the e-document should be created'); + + // [GIVEN] The draft has an invalid currency code + EDocumentPurchaseHeader.GetFromEDocument(EDocument); + EDocumentPurchaseHeader."Currency Code" := 'BADCURR'; + EDocumentPurchaseHeader.Modify(); + + // [WHEN] Finalizing the draft + EDocImportParams."Step to Run" := "Import E-Document Steps"::"Finish draft"; + EDocImport.ProcessIncomingEDocument(EDocument, EDocImportParams); + + // [THEN] The e-document should have an error + EDocument.Get(EDocument."Entry No"); + Assert.IsTrue(EDocumentErrorHelper.HasErrors(EDocument), 'The e-document should have errors'); + + // [THEN] The error message should contain the Currency Code field context + ErrorMessage.SetRange("Context Record ID", EDocument.RecordId()); + ErrorMessage.SetRange("Message Type", ErrorMessage."Message Type"::Error); + ErrorMessage.FindFirst(); + Assert.ExpectedMessage('Currency Code', ErrorMessage."Message"); + + // [THEN] No purchase invoice should have been created + PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); + Assert.RecordIsEmpty(PurchaseHeader); + end; + [Test] procedure PreparingPurchaseDraftFindsItemReference() var @@ -792,7 +1121,7 @@ codeunit 139883 "E-Doc Process Test" Currency.Init(); Currency.Validate(Code, 'XYZ'); if Currency.Insert(true) then - LibraryERM.CreateExchangeRate(Currency.Code, WorkDate(), 1.0, 1.0); + LibraryERM.CreateExchangeRate(Currency.Code, Today(), 1.0, 1.0); EDocument.DeleteAll(); EDocumentServiceStatus.DeleteAll();