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 fb69e5d04b..68f9f08726 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 @@ -14,7 +14,7 @@ using Microsoft.Purchases.Document; using Microsoft.Purchases.Payables; using Microsoft.Purchases.Posting; using System.Telemetry; -using System.Utilities; +using Microsoft.eServices.EDocument.Processing; /// /// Dealing with the creation of the purchase invoice after the draft has been populated. @@ -38,24 +38,25 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, TempPOMatchWarnings: Record "E-Doc PO Match Warning" temporary; EDocPOMatching: Codeunit "E-Doc. PO Matching"; DocumentAttachmentMgt: Codeunit "Document Attachment Mgmt"; - ConfirmManagement: Codeunit "Confirm Management"; IEDocumentFinishPurchaseDraft: Interface IEDocumentCreatePurchaseInvoice; YourMatchedLinesAreNotValidErr: Label 'The purchase invoice cannot be created because one or more of its matched lines are not valid matches. Review if your configuration allows for receiving at invoice.'; - SomeLinesNotYetReceivedMsg: Label 'Some of the matched purchase order lines have not yet been received, when posting the invoice, receipts will be created if needed. Do you want to proceed with creating the purchase invoice?'; + SomeLinesNotYetReceivedErr: Label 'Some of the matched purchase order lines have not yet been received, you need to either receive the lines or remove the matches.'; begin EDocumentPurchaseHeader.GetFromEDocument(EDocument); if not EDocPOMatching.VerifyEDocumentMatchedLinesAreValidMatches(EDocumentPurchaseHeader) then Error(YourMatchedLinesAreNotValidErr); + EDocPOMatching.SuggestReceiptsForMatchedOrderLines(EDocumentPurchaseHeader); EDocPOMatching.CalculatePOMatchWarnings(EDocumentPurchaseHeader, TempPOMatchWarnings); TempPOMatchWarnings.SetRange("Warning Type", "E-Doc PO Match Warning"::NotYetReceived); if not TempPOMatchWarnings.IsEmpty() then - if not ConfirmManagement.GetResponse(SomeLinesNotYetReceivedMsg) then - Error(''); // User cancelled the operation + Error(SomeLinesNotYetReceivedErr); IEDocumentFinishPurchaseDraft := EDocImportParameters."Processing Customizations"; PurchaseHeader := IEDocumentFinishPurchaseDraft.CreatePurchaseInvoice(EDocument); + + EDocPOMatching.TransferPOMatchesFromEDocumentToInvoice(EDocument, PurchaseHeader); PurchaseHeader.SetRecFilter(); PurchaseHeader.FindFirst(); PurchaseHeader."Doc. Amount Incl. VAT" := EDocumentPurchaseHeader.Total; @@ -78,11 +79,13 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, procedure RevertDraftActions(EDocument: Record "E-Document") var PurchaseHeader: Record "Purchase Header"; + EDocPOMatching: Codeunit "E-Doc. PO Matching"; DocumentAttachmentMgt: Codeunit "Document Attachment Mgmt"; begin PurchaseHeader.SetRange("E-Document Link", EDocument.SystemId); if not PurchaseHeader.FindFirst() then exit; + EDocPOMatching.TransferPOMatchesFromInvoiceToEDocument(PurchaseHeader, EDocument); DocumentAttachmentMgt.CopyAttachments(PurchaseHeader, EDocument); DocumentAttachmentMgt.DeleteAttachedDocuments(PurchaseHeader); PurchaseHeader.TestField("Document Type", "Purchase Document Type"::Invoice); @@ -102,6 +105,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, DimensionManagement: Codeunit DimensionManagement; PurchCalcDiscByType: Codeunit "Purch - Calc Disc. By Type"; PurchaseLineCombinedDimensions: array[10] of Integer; + PurchaseLineNo: Integer; StopCreatingPurchaseInvoice: Boolean; VendorInvoiceNo: Code[35]; GlobalDim1, GlobalDim2 : Code[20]; @@ -142,16 +146,16 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, PurchaseHeader.Modify(); end; - // Track changes for history - EDocumentPurchaseHistMapping.TrackRecord(EDocument, EDocumentPurchaseHeader, PurchaseHeader); - - PurchaseLine."Line No." := GetLastLineNumberOnPurchaseInvoice(PurchaseHeader."No."); // We get the last line number, even if this is a new document since recurrent lines get inserted on the header's creation + LinkEDocumentHeaderToPurchaseHeader(EDocumentPurchaseHeader, PurchaseHeader); + PurchaseLineNo := GetLastLineNumberOnPurchaseInvoice(PurchaseHeader."No."); // We get the last line number, even if this is a new document since recurrent lines get inserted on the header's creation EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); if EDocumentPurchaseLine.FindSet() then repeat + Clear(PurchaseLine); PurchaseLine."Document Type" := PurchaseHeader."Document Type"; PurchaseLine."Document No." := PurchaseHeader."No."; - PurchaseLine."Line No." += 10000; + PurchaseLineNo += 10000; + PurchaseLine."Line No." := PurchaseLineNo; PurchaseLine."Unit of Measure Code" := CopyStr(EDocumentPurchaseLine."[BC] Unit of Measure", 1, MaxStrLen(PurchaseLine."Unit of Measure Code")); PurchaseLine."Variant Code" := EDocumentPurchaseLine."[BC] Variant Code"; PurchaseLine.Type := EDocumentPurchaseLine."[BC] Purchase Line Type"; @@ -177,10 +181,7 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, PurchaseLine.Validate("Shortcut Dimension 2 Code", EDocumentPurchaseLine."[BC] Shortcut Dimension 2 Code"); EDocumentPurchaseHistMapping.ApplyAdditionalFieldsFromHistoryToPurchaseLine(EDocumentPurchaseLine, PurchaseLine); PurchaseLine.Insert(); - - // Track changes for history - EDocumentPurchaseHistMapping.TrackRecord(EDocument, EDocumentPurchaseLine, PurchaseLine); - + LinkEDocumentLineToPurchaseLine(EDocumentPurchaseLine, PurchaseLine); until EDocumentPurchaseLine.Next() = 0; PurchaseHeader.Modify(); PurchCalcDiscByType.ApplyInvDiscBasedOnAmt(EDocumentPurchaseHeader."Total Discount", PurchaseHeader); @@ -226,4 +227,28 @@ codeunit 6117 "E-Doc. Create Purchase Invoice" implements IEDocumentFinishDraft, exit(PurchaseLine."Line No."); end; + local procedure LinkEDocumentHeaderToPurchaseHeader(EDocumentPurchaseHeader: Record "E-Document Purchase Header"; PurchaseHeader: Record "Purchase Header") + var + EDocRecordLink: Record "E-Doc. Record Link"; + begin + EDocRecordLink."Source Table No." := Database::"E-Document Purchase Header"; + EDocRecordLink."Source SystemId" := EDocumentPurchaseHeader.SystemId; + EDocRecordLink."Target Table No." := Database::"Purchase Header"; + EDocRecordLink."Target SystemId" := PurchaseHeader.SystemId; + EDocRecordLink."E-Document Entry No." := EDocumentPurchaseHeader."E-Document Entry No."; + EDocRecordLink.Insert(); + end; + + local procedure LinkEDocumentLineToPurchaseLine(EDocumentPurchaseLine: Record "E-Document Purchase Line"; PurchaseLine: Record "Purchase Line") + var + EDocRecordLink: Record "E-Doc. Record Link"; + begin + EDocRecordLink."Source Table No." := Database::"E-Document Purchase Line"; + EDocRecordLink."Source SystemId" := EDocumentPurchaseLine.SystemId; + EDocRecordLink."Target Table No." := Database::"Purchase Line"; + EDocRecordLink."Target SystemId" := PurchaseLine.SystemId; + EDocRecordLink."E-Document Entry No." := EDocumentPurchaseLine."E-Document Entry No."; + EDocRecordLink.Insert(); + end; + } diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al index d4122bf26d..dbd722a1dd 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al @@ -166,12 +166,12 @@ page 6183 "E-Doc. Purchase Draft Subform" group("Order Matching") { Caption = 'Order matching'; - action(MatchToOrderLines) + action(MatchToOrderLine) { ApplicationArea = All; - Caption = 'Match to order lines'; + Caption = 'Match to order line'; Image = LinkWithExisting; - ToolTip = 'Match this incoming invoice line to purchase order lines.'; + ToolTip = 'Match this incoming invoice line to a purchase order line.'; Scope = Repeater; trigger OnAction() @@ -192,9 +192,9 @@ page 6183 "E-Doc. Purchase Draft Subform" action(SpecifyReceiptLines) { ApplicationArea = All; - Caption = 'Specify receipt lines'; + Caption = 'Specify receipt line'; Image = ReceiptLines; - ToolTip = 'Specify the corresponding receipt lines to the matched order line.'; + ToolTip = 'Specify the corresponding receipt line to the matched order line.'; Scope = Repeater; Enabled = IsLineMatchedToOrderLine; diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al index 119ea96b88..e57b5d2c90 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocumentPurchaseLine.Table.al @@ -12,6 +12,7 @@ using Microsoft.Finance.Dimension; using Microsoft.Finance.GeneralLedger.Account; using Microsoft.FixedAssets.FixedAsset; using Microsoft.Foundation.UOM; +using Microsoft.eServices.EDocument.Processing; using Microsoft.Inventory.Item; using Microsoft.Inventory.Item.Catalog; using Microsoft.Projects.Resources.Resource; @@ -336,6 +337,31 @@ table 6101 "E-Document Purchase Line" exit(OldDimSetID <> "[BC] Dimension Set ID"); end; + internal procedure GetFromLinkedPurchaseLine(PurchaseLine: Record "Purchase Line"): Boolean + var + EDocumentRecordLink: Record "E-Doc. Record Link"; + begin + Clear(Rec); + EDocumentRecordLink.SetRange("Source Table No.", Database::"E-Document Purchase Line"); + EDocumentRecordLink.SetRange("Target Table No.", Database::"Purchase Line"); + EDocumentRecordLink.SetRange("Target SystemId", PurchaseLine.SystemId); + if EDocumentRecordLink.FindFirst() then + exit(Rec.GetBySystemId(EDocumentRecordLink."Source SystemId")); + end; + + internal procedure GetLinkedPurchaseLine(): Record "Purchase Line" + var + EDocumentRecordLink: Record "E-Doc. Record Link"; + PurchaseLine: Record "Purchase Line"; + begin + EDocumentRecordLink.SetRange("Source Table No.", Database::"E-Document Purchase Line"); + EDocumentRecordLink.SetRange("Target Table No.", Database::"Purchase Line"); + EDocumentRecordLink.SetRange("Source SystemId", Rec.SystemId); + if EDocumentRecordLink.FindFirst() then + if PurchaseLine.GetBySystemId(EDocumentRecordLink."Target SystemId") then + exit(PurchaseLine); + end; + procedure GetEDocumentPurchaseHeader() EDocumentPurchaseHeader: Record "E-Document Purchase Header" begin if EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then; 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 4cdedc04ca..32c26f0673 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 @@ -24,7 +24,6 @@ codeunit 6120 "E-Doc. Purchase Hist. Mapping" var EDocImpSessionTelemetry: Codeunit "E-Doc. Imp. Session Telemetry"; - WrongVariantTypeErr: Label 'Only record types are allowed.'; procedure FindRelatedPurchaseHeaderInHistory(EDocument: Record "E-Document"; var EDocVendorAssignmentHistory: Record "E-Doc. Vendor Assign. History"): Boolean var @@ -204,28 +203,6 @@ codeunit 6120 "E-Doc. Purchase Hist. Mapping" EDocActivityLogSession.Set(ActivityLogSessionToken, ActivityLog); end; - /// - /// Track header and line mapping between source and target records. - /// - procedure TrackRecord(EDocument: Record "E-Document"; SourceRecord: Variant; TargetRecord: Variant) - var - EDocRecordLink: Record "E-Doc. Record Link"; - SourceRecordRef, TargetRecordRef : RecordRef; - begin - if (not SourceRecord.IsRecord()) or (not TargetRecord.IsRecord()) then - Error(WrongVariantTypeErr); - - SourceRecordRef.GetTable(SourceRecord); - TargetRecordRef.GetTable(TargetRecord); - - EDocRecordLink."Source Table No." := SourceRecordRef.Number(); - EDocRecordLink."Source SystemId" := SourceRecordRef.Field(SourceRecordRef.SystemIdNo).Value(); - EDocRecordLink."Target Table No." := TargetRecordRef.Number(); - EDocRecordLink."Target SystemId" := TargetRecordRef.Field(TargetRecordRef.SystemIdNo).Value(); - EDocRecordLink."E-Document Entry No." := EDocument."Entry No"; - if EDocRecordLink.Insert() then; - end; - /// /// Applies the values configured as additional fields in the posted line, if the line had a historic match the values are retrieved from the Purchase Invoice Line. /// diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/PurchaseOrderMatching/EDocPOMatching.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/PurchaseOrderMatching/EDocPOMatching.Codeunit.al index e3b659d5da..4f4b9f8e1a 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/PurchaseOrderMatching/EDocPOMatching.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/PurchaseOrderMatching/EDocPOMatching.Codeunit.al @@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.Inventory.Item; using Microsoft.Purchases.Document; using Microsoft.Purchases.History; +using Microsoft.eServices.EDocument; using Microsoft.Purchases.Vendor; codeunit 6196 "E-Doc. PO Matching" @@ -139,22 +140,38 @@ codeunit 6196 "E-Doc. PO Matching" procedure LoadReceiptsMatchedToEDocumentLine(EDocumentPurchaseLine: Record "E-Document Purchase Line"; var TempPurchaseReceiptHeader: Record "Purch. Rcpt. Header" temporary) var PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + TempPurchaseReceiptLine: Record "Purch. Rcpt. Line" temporary; + begin + Clear(TempPurchaseReceiptHeader); + TempPurchaseReceiptHeader.DeleteAll(); + LoadReceiptLinesMatchedToEDocumentLine(EDocumentPurchaseLine, TempPurchaseReceiptLine); + if TempPurchaseReceiptLine.FindSet() then + repeat + if PurchaseReceiptHeader.Get(TempPurchaseReceiptLine."Document No.") then begin + Clear(TempPurchaseReceiptHeader); + TempPurchaseReceiptHeader := PurchaseReceiptHeader; + if TempPurchaseReceiptHeader.Insert() then; + end; + until TempPurchaseReceiptLine.Next() = 0; + end; + + procedure LoadReceiptLinesMatchedToEDocumentLine(EDocumentPurchaseLine: Record "E-Document Purchase Line"; var TempPurchaseReceiptLine: Record "Purch. Rcpt. Line" temporary) + var PurchaseReceiptLine: Record "Purch. Rcpt. Line"; EDocPurchaseLinePOMatch: Record "E-Doc. Purchase Line PO Match"; NullGuid: Guid; begin - Clear(TempPurchaseReceiptHeader); - TempPurchaseReceiptHeader.DeleteAll(); + Clear(TempPurchaseReceiptLine); + TempPurchaseReceiptLine.DeleteAll(); EDocPurchaseLinePOMatch.SetRange("E-Doc. Purchase Line SystemId", EDocumentPurchaseLine.SystemId); EDocPurchaseLinePOMatch.SetFilter("Receipt Line SystemId", '<>%1', NullGuid); if EDocPurchaseLinePOMatch.FindSet() then repeat - if PurchaseReceiptLine.GetBySystemId(EDocPurchaseLinePOMatch."Receipt Line SystemId") then - if PurchaseReceiptHeader.Get(PurchaseReceiptLine."Document No.") then begin - Clear(TempPurchaseReceiptHeader); - TempPurchaseReceiptHeader := PurchaseReceiptHeader; - if TempPurchaseReceiptHeader.Insert() then; - end; + if PurchaseReceiptLine.GetBySystemId(EDocPurchaseLinePOMatch."Receipt Line SystemId") then begin + Clear(TempPurchaseReceiptLine); + TempPurchaseReceiptLine := PurchaseReceiptLine; + TempPurchaseReceiptLine.Insert(); + end; until EDocPurchaseLinePOMatch.Next() = 0; end; @@ -183,12 +200,9 @@ codeunit 6196 "E-Doc. PO Matching" /// local procedure AppendPOMatchWarnings(EDocumentPurchaseLine: Record "E-Document Purchase Line"; var POMatchWarnings: Record "E-Doc PO Match Warning" temporary) var - Item: Record Item; - ItemUnitOfMeasure: Record "Item Unit of Measure"; TempPurchaseLine: Record "Purchase Line" temporary; EDocLineQuantity: Decimal; PurchaseLinesQuantityInvoiced, PurchaseLinesQuantityReceived : Decimal; - ItemFound, ItemUoMFound : Boolean; begin LoadPOLinesMatchedToEDocumentLine(EDocumentPurchaseLine, TempPurchaseLine); PurchaseLinesQuantityInvoiced := 0; @@ -199,18 +213,12 @@ codeunit 6196 "E-Doc. PO Matching" PurchaseLinesQuantityInvoiced += TempPurchaseLine."Qty. Invoiced (Base)"; PurchaseLinesQuantityReceived += TempPurchaseLine."Qty. Received (Base)"; until TempPurchaseLine.Next() = 0; - if EDocumentPurchaseLine."[BC] Purchase Line Type" = Enum::"Purchase Line Type"::Item then begin - Item.SetLoadFields("No."); - ItemFound := Item.Get(EDocumentPurchaseLine."[BC] Purchase Type No."); - ItemUnitOfMeasure.SetLoadFields("Item No.", Code, "Qty. per Unit of Measure"); - ItemUoMFound := ItemUnitOfMeasure.Get(Item."No.", EDocumentPurchaseLine."[BC] Unit of Measure"); - if not (ItemFound and ItemUoMFound) then begin - POMatchWarnings."E-Doc. Purchase Line SystemId" := EDocumentPurchaseLine.SystemId; - POMatchWarnings."Warning Type" := "E-Doc PO Match Warning"::MissingInformationForMatch; - POMatchWarnings.Insert(); - exit; - end; - EDocLineQuantity := EDocumentPurchaseLine.Quantity * ItemUnitOfMeasure."Qty. per Unit of Measure" + + if not GetEDocumentLineQuantityInBaseUoM(EDocumentPurchaseLine, EDocLineQuantity) then begin + POMatchWarnings."E-Doc. Purchase Line SystemId" := EDocumentPurchaseLine.SystemId; + POMatchWarnings."Warning Type" := "E-Doc PO Match Warning"::MissingInformationForMatch; + POMatchWarnings.Insert(); + exit; end; if EDocLineQuantity <> EDocumentPurchaseLine.Quantity then begin POMatchWarnings."E-Doc. Purchase Line SystemId" := EDocumentPurchaseLine.SystemId; @@ -388,6 +396,7 @@ codeunit 6196 "E-Doc. PO Matching" Vendor: Record Vendor; EDocPurchaseLinePOMatch: Record "E-Doc. Purchase Line PO Match"; TempMatchWarnings: Record "E-Doc PO Match Warning" temporary; + MatchesToMultiplePOLinesNotSupportedErr: Label 'Matching an e-document line to multiple purchase order lines is not currently supported.'; NotLinkedToVendorErr: Label 'The e-document line is not matched to any vendor.'; AlreadyMatchedErr: Label 'A selected purchase order line is already matched to another e-document line. E-Document: %1, Purchase document: %2 %3.', Comment = '%1 - E-Document No., %2 - Purchase Document Type, %3 - Purchase Document No.'; OrderLineAndEDocFromDifferentVendorsErr: Label 'All selected purchase order lines must belong to orders for the same vendor as the e-document line.'; @@ -399,6 +408,8 @@ codeunit 6196 "E-Doc. PO Matching" begin if SelectedPOLines.IsEmpty() then exit; + if SelectedPOLines.Count() > 1 then + Error(MatchesToMultiplePOLinesNotSupportedErr); RemoveAllMatchesForEDocumentLine(EDocumentPurchaseLine); MatchedPOLineVendorNo := ''; MatchedPOLineTypeNo := ''; @@ -470,10 +481,14 @@ codeunit 6196 "E-Doc. PO Matching" NullGuid: Guid; ReceiptLineNotMatchedErr: Label 'A selected receipt line is not matched to any of the purchase order lines matched to the e-document line.'; ReceiptLinesDontCoverErr: Label 'The selected receipt lines do not cover the full quantity of the e-document line.'; + MatchesToMultipleReceiptLinesNotSupportedErr: Label 'Matching an e-document line to multiple receipt lines is not currently supported.'; QuantityCovered: Decimal; begin - if not SelectedReceiptLines.FindSet() then + if SelectedReceiptLines.IsEmpty() then exit; + if SelectedReceiptLines.Count() > 1 then + Error(MatchesToMultipleReceiptLinesNotSupportedErr); + // Remove existing receipt line matches EDocPurchaseLinePOMatch.SetRange("E-Doc. Purchase Line SystemId", EDocumentPurchaseLine.SystemId); EDocPurchaseLinePOMatch.SetFilter("Receipt Line SystemId", '<>%1', NullGuid); @@ -481,6 +496,7 @@ codeunit 6196 "E-Doc. PO Matching" // Create new matches LoadPOLinesMatchedToEDocumentLine(EDocumentPurchaseLine, TempMatchedPurchaseLines); + SelectedReceiptLines.FindSet(); repeat TempMatchedPurchaseLines.SetRange("Document No.", SelectedReceiptLines."Order No."); TempMatchedPurchaseLines.SetRange("Line No.", SelectedReceiptLines."Order Line No."); @@ -497,6 +513,18 @@ codeunit 6196 "E-Doc. PO Matching" Error(ReceiptLinesDontCoverErr); end; + procedure ConfigureDefaultPOMatchingSettings() + var + DefaultSetup: Record "E-Doc. PO Matching Setup"; + DefaultConfiguration: Enum "E-Doc. PO M. Configuration"; + VendorNos: List of [Code[20]]; + begin + DefaultSetup."PO Matching Config. Receipt" := Enum::"E-Doc. PO M. Config. Receipt"::"Always ask"; + DefaultSetup."Receive G/L Account Lines" := false; + DefaultConfiguration := Enum::"E-Doc. PO M. Configuration"::"Always ask"; + ConfigurePOMatchingSettings(DefaultSetup, DefaultConfiguration, VendorNos); + end; + /// /// Stores the configuration selected by the user. /// @@ -545,6 +573,133 @@ codeunit 6196 "E-Doc. PO Matching" end; end; + procedure SuggestReceiptsForMatchedOrderLines(EDocumentPurchaseHeader: Record "E-Document Purchase Header") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + EDocPurchaseLinePOMatch: Record "E-Doc. Purchase Line PO Match"; + PurchaseOrderLine: Record "Purchase Line"; + PurchaseReceiptLine: Record "Purch. Rcpt. Line"; + TempPurchaseReceiptLine: Record "Purch. Rcpt. Line" temporary; + EDocLineQuantity: Decimal; + NullGuid: Guid; + begin + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocumentPurchaseHeader."E-Document Entry No."); + if EDocumentPurchaseLine.FindSet() then + repeat + Clear(EDocPurchaseLinePOMatch); + EDocPurchaseLinePOMatch.SetRange("E-Doc. Purchase Line SystemId", EDocumentPurchaseLine.SystemId); + EDocPurchaseLinePOMatch.SetRange("Receipt Line SystemId", NullGuid); + if not EDocPurchaseLinePOMatch.FindFirst() then + continue; // No PO lines matched, so no receipt can be suggested + if not PurchaseOrderLine.GetBySystemId(EDocPurchaseLinePOMatch."Purchase Line SystemId") then + continue; // Should not happen, but we skip in case it does, this procedure doesn't error out + EDocPurchaseLinePOMatch.SetRange("Purchase Line SystemId", PurchaseOrderLine.SystemId); + EDocPurchaseLinePOMatch.SetFilter("Receipt Line SystemId", '<> %1', NullGuid); + if not EDocPurchaseLinePOMatch.IsEmpty() then + continue; // There's already at least one receipt line matched, so no suggestion is needed + Session.LogMessage('0000QQI', 'Suggesting receipt line for draft line matched to PO line', Verbosity::Verbose, DataClassification::SystemMetadata, TelemetryScope::All, 'Category', 'E-Document'); + PurchaseReceiptLine.SetRange("Order No.", PurchaseOrderLine."Document No."); + PurchaseReceiptLine.SetRange("Order Line No.", PurchaseOrderLine."Line No."); + PurchaseReceiptLine.SetFilter(Quantity, '> 0'); + if PurchaseReceiptLine.FindSet() then + repeat + if GetEDocumentLineQuantityInBaseUoM(EDocumentPurchaseLine, EDocLineQuantity) then + if PurchaseReceiptLine.Quantity >= EDocLineQuantity then begin + // We suggest the first receipt line that can cover the full quantity of the E-Document line + Session.LogMessage('0000QQJ', 'Suggested covering receipt line for draft line matched to PO line', Verbosity::Verbose, DataClassification::SystemMetadata, TelemetryScope::All, 'Category', 'E-Document'); + Clear(TempPurchaseReceiptLine); + TempPurchaseReceiptLine.DeleteAll(); + TempPurchaseReceiptLine.Copy(PurchaseReceiptLine); + TempPurchaseReceiptLine.Insert(); + MatchReceiptLinesToEDocumentLine(TempPurchaseReceiptLine, EDocumentPurchaseLine); + break; // We only suggest a single receipt line + end; + until PurchaseReceiptLine.Next() = 0; + until EDocumentPurchaseLine.Next() = 0; + end; + + local procedure GetEDocumentLineQuantityInBaseUoM(EDocumentPurchaseLine: Record "E-Document Purchase Line"; var Quantity: Decimal): Boolean + var + Item: Record Item; + ItemUnitOfMeasure: Record "Item Unit of Measure"; + ItemFound, ItemUoMFound : Boolean; + begin + Clear(Quantity); + if EDocumentPurchaseLine."[BC] Purchase Line Type" <> Enum::"Purchase Line Type"::Item then begin + Quantity := EDocumentPurchaseLine.Quantity; + exit(true); + end; + Item.SetLoadFields("No."); + ItemFound := Item.Get(EDocumentPurchaseLine."[BC] Purchase Type No."); + ItemUnitOfMeasure.SetLoadFields("Item No.", Code, "Qty. per Unit of Measure"); + ItemUoMFound := ItemUnitOfMeasure.Get(Item."No.", EDocumentPurchaseLine."[BC] Unit of Measure"); + if not (ItemFound and ItemUoMFound) then + exit(false); + Quantity := EDocumentPurchaseLine.Quantity * ItemUnitOfMeasure."Qty. per Unit of Measure"; + exit(true); + end; + + procedure TransferPOMatchesFromEDocumentToInvoice(EDocument: Record "E-Document"; PurchaseHeader: Record "Purchase Header") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + PurchaseLine: Record "Purchase Line"; + TempPurchaseReceiptLine: Record "Purch. Rcpt. Line" temporary; + begin + EDocumentPurchaseLine.SetRange("E-Document Entry No.", EDocument."Entry No"); + if EDocumentPurchaseLine.FindSet() then + repeat + LoadReceiptLinesMatchedToEDocumentLine(EDocumentPurchaseLine, TempPurchaseReceiptLine); + if not TempPurchaseReceiptLine.FindFirst() then // We only support a single receipt line match in BaseApp + continue; + PurchaseLine := EDocumentPurchaseLine.GetLinkedPurchaseLine(); + if IsNullGuid(PurchaseLine.SystemId) then + continue; + PurchaseLine."Receipt No." := TempPurchaseReceiptLine."Document No."; + PurchaseLine."Receipt Line No." := TempPurchaseReceiptLine."Line No."; + PurchaseLine.Modify(); + RemoveAllMatchesForEDocumentLine(EDocumentPurchaseLine); + until EDocumentPurchaseLine.Next() = 0; + end; + + procedure TransferPOMatchesFromInvoiceToEDocument(PurchaseHeader: Record "Purchase Header"; EDocument: Record "E-Document") + var + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + PurchaseInvoiceLine: Record "Purchase Line"; + PurchaseOrderLine: Record "Purchase Line"; + PurchaseReceiptLine: Record "Purch. Rcpt. Line"; + TempPOLineToMatch: Record "Purchase Line" temporary; + TempReceiptLineToMatch: Record "Purch. Rcpt. Line" temporary; + begin + PurchaseInvoiceLine.SetRange("Document Type", PurchaseHeader."Document Type"); + PurchaseInvoiceLine.SetRange("Document No.", PurchaseHeader."No."); + PurchaseInvoiceLine.SetFilter("Receipt No.", '<>%1', ''); + PurchaseInvoiceLine.SetFilter("Receipt Line No.", '<>%1', 0); + if PurchaseInvoiceLine.IsEmpty() then + exit; + PurchaseInvoiceLine.FindSet(); + repeat + if not EDocumentPurchaseLine.GetFromLinkedPurchaseLine(PurchaseInvoiceLine) then + continue; + if not PurchaseReceiptLine.Get(PurchaseInvoiceLine."Receipt No.", PurchaseInvoiceLine."Receipt Line No.") then + continue; + if not PurchaseOrderLine.Get(Enum::"Purchase Document Type"::Order, PurchaseReceiptLine."Order No.", PurchaseReceiptLine."Order Line No.") then + continue; + TempPOLineToMatch.DeleteAll(); + TempPOLineToMatch.Copy(PurchaseOrderLine); + TempPOLineToMatch.Insert(); + MatchPOLinesToEDocumentLine(TempPOLineToMatch, EDocumentPurchaseLine); + + TempReceiptLineToMatch.DeleteAll(); + TempReceiptLineToMatch.Copy(PurchaseReceiptLine); + TempReceiptLineToMatch.Insert(); + MatchReceiptLinesToEDocumentLine(TempReceiptLineToMatch, EDocumentPurchaseLine); + until PurchaseInvoiceLine.Next() = 0; + PurchaseInvoiceLine.SetRange("Receipt No."); + PurchaseInvoiceLine.SetRange("Receipt Line No."); + PurchaseInvoiceLine.ModifyAll("Receipt No.", ''); + PurchaseInvoiceLine.ModifyAll("Receipt Line No.", 0); + end; + /// /// Loads the settings for purchase order matching. /// diff --git a/src/Apps/W1/EDocument/Test/DisabledTests/EDocPOMatchingUnitTests.DisabledTest.json b/src/Apps/W1/EDocument/Test/DisabledTests/EDocPOMatchingUnitTests.DisabledTest.json deleted file mode 100644 index 3c564abe2d..0000000000 --- a/src/Apps/W1/EDocument/Test/DisabledTests/EDocPOMatchingUnitTests.DisabledTest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "codeunitId": 133508, - "codeunitName": "E-Doc. PO Matching Unit Tests", - "method": "*", - "bug": "610664" -} \ No newline at end of file diff --git a/src/Apps/W1/EDocument/Test/src/Matching/EDocPOMatchingUnitTests.Codeunit.al b/src/Apps/W1/EDocument/Test/src/Matching/EDocPOMatchingUnitTests.Codeunit.al index f1b4d05f51..d2b72a7ac6 100644 --- a/src/Apps/W1/EDocument/Test/src/Matching/EDocPOMatchingUnitTests.Codeunit.al +++ b/src/Apps/W1/EDocument/Test/src/Matching/EDocPOMatchingUnitTests.Codeunit.al @@ -6,15 +6,15 @@ namespace Microsoft.eServices.EDocument.Test; using Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Integration; +using Microsoft.eServices.EDocument.Processing; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.eServices.EDocument.Processing.Import.Purchase; -using Microsoft.Finance.VAT.Setup; -using Microsoft.Foundation.Enums; using Microsoft.Foundation.UOM; using Microsoft.Inventory.Item; using Microsoft.Purchases.Document; using Microsoft.Purchases.History; using Microsoft.Purchases.Vendor; +using Microsoft.Purchases.Setup; codeunit 133508 "E-Doc. PO Matching Unit Tests" { @@ -251,51 +251,6 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" Assert.IsTrue(TempPurchaseLine.IsEmpty(), 'Expected no purchase lines when E-Document line has no matches'); end; - [Test] - procedure LoadPOLinesMatchedToEDocLineReturnsAllMatchedPOLines() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - PurchaseHeader: Record "Purchase Header"; - PurchaseLine1, PurchaseLine2, PurchaseLine3 : Record "Purchase Line"; - TempPurchaseLine: Record "Purchase Line" temporary; - Item: Record Item; - begin - Initialize(); - // [SCENARIO] Loading PO lines matched to an E-Document line returns all matched PO lines - // [GIVEN] An E-Document line with multiple matched purchase order lines - LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); - - // Create E-Document Purchase Header and Line - EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; - EDocumentPurchaseHeader.Modify(); - EDocumentPurchaseLine := LibraryEDocument.InsertPurchaseDraftLine(EDocument); - - // Create Purchase Order with multiple lines for the same vendor - LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); - LibraryEDocument.GetGenericItem(Item); - LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader, PurchaseLine1.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader, PurchaseLine2.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - LibraryPurchase.CreatePurchaseLine(PurchaseLine3, PurchaseHeader, PurchaseLine3.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - - // Match first two PO lines to the E-Document line - MatchEDocumentLineToTwoPOLines(EDocumentPurchaseLine, PurchaseLine1, PurchaseLine2); - - // [WHEN] LoadPOLinesMatchedToEDocumentLine is called - EDocPOMatching.LoadPOLinesMatchedToEDocumentLine(EDocumentPurchaseLine, TempPurchaseLine); - - // [THEN] All matched purchase order lines should be loaded into the temporary record - Assert.AreEqual(2, TempPurchaseLine.Count(), 'Expected 2 matched purchase lines to be loaded'); - TempPurchaseLine.SetRange(SystemId, PurchaseLine1.SystemId); - Assert.IsFalse(TempPurchaseLine.IsEmpty(), 'First matched purchase line should be included'); - TempPurchaseLine.SetRange(SystemId, PurchaseLine2.SystemId); - Assert.IsFalse(TempPurchaseLine.IsEmpty(), 'Second matched purchase line should be included'); - TempPurchaseLine.SetRange(SystemId, PurchaseLine3.SystemId); - Assert.IsTrue(TempPurchaseLine.IsEmpty(), 'Unmatched purchase line should not be included'); - end; - [Test] procedure LoadPOsMatchedToEDocLineWithNoMatchedPOLines() var @@ -322,50 +277,6 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" Assert.IsTrue(TempPurchaseHeader.IsEmpty(), 'Expected no purchase headers when E-Document line has no matched PO lines'); end; - [Test] - procedure LoadPOsMatchedToEDocLineReturnsUniquePurchaseHeaders() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - PurchaseHeader1, PurchaseHeader2 : Record "Purchase Header"; - PurchaseLine1, PurchaseLine2, PurchaseLine3 : Record "Purchase Line"; - TempPurchaseHeader: Record "Purchase Header" temporary; - Item: Record Item; - begin - Initialize(); - // [SCENARIO] Loading POs matched to an E-Document line returns unique purchase headers - // [GIVEN] An E-Document line matched to multiple PO lines from different purchase orders - LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); - - // Create E-Document Purchase Header and Line - EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; - EDocumentPurchaseHeader.Modify(); - EDocumentPurchaseLine := LibraryEDocument.InsertPurchaseDraftLine(EDocument); - - // Create two Purchase Orders with lines for the same vendor - LibraryPurchase.CreatePurchHeader(PurchaseHeader1, PurchaseHeader1."Document Type"::Order, Vendor."No."); - LibraryPurchase.CreatePurchHeader(PurchaseHeader2, PurchaseHeader2."Document Type"::Order, Vendor."No."); - LibraryEDocument.GetGenericItem(Item); - LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader1, PurchaseLine1.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader1, PurchaseLine2.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - LibraryPurchase.CreatePurchaseLine(PurchaseLine3, PurchaseHeader2, PurchaseLine3.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - - // Match lines from both purchase orders to the E-Document line - MatchEDocumentLineToThreePOLines(EDocumentPurchaseLine, PurchaseLine1, PurchaseLine2, PurchaseLine3); - - // [WHEN] LoadPOsMatchedToEDocumentLine is called - EDocPOMatching.LoadPOsMatchedToEDocumentLine(EDocumentPurchaseLine, TempPurchaseHeader); - - // [THEN] All unique purchase headers should be loaded into the temporary record - Assert.AreEqual(2, TempPurchaseHeader.Count(), 'Expected 2 unique purchase headers to be loaded'); - TempPurchaseHeader.SetRange("No.", PurchaseHeader1."No."); - Assert.IsFalse(TempPurchaseHeader.IsEmpty(), 'First purchase header should be included'); - TempPurchaseHeader.SetRange("No.", PurchaseHeader2."No."); - Assert.IsFalse(TempPurchaseHeader.IsEmpty(), 'Second purchase header should be included'); - end; - [Test] procedure LoadAvailableReceiptLinesForEDocLineWithNoMatchedPOLines() var @@ -393,106 +304,6 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" Assert.IsTrue(TempPurchaseReceiptLine.IsEmpty(), 'Expected no receipt lines when E-Document line has no matched PO lines'); end; - [Test] - procedure LoadAvailableReceiptLinesReturnsReceiptLinesForMatchedPOLines() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - PurchaseHeader: Record "Purchase Header"; - PurchaseLine1, PurchaseLine2 : Record "Purchase Line"; - PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; - PurchaseReceiptLine1, PurchaseReceiptLine2 : Record "Purch. Rcpt. Line"; - TempPurchaseReceiptLine: Record "Purch. Rcpt. Line" temporary; - Item: Record Item; - begin - Initialize(); - ClearPurchaseDocumentsForVendor(); - // [SCENARIO] Loading available receipt lines returns receipt lines for matched PO lines - // [GIVEN] An E-Document line matched to PO lines that have associated receipt lines - LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); - - // Create E-Document Purchase Header and Line - EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; - EDocumentPurchaseHeader.Modify(); - EDocumentPurchaseLine := LibraryEDocument.InsertPurchaseDraftLine(EDocument); - - // Create Purchase Order with lines - LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); - LibraryEDocument.GetGenericItem(Item); - LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader, PurchaseLine1.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader, PurchaseLine2.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - - // Match PO lines to E-Document line - MatchEDocumentLineToTwoPOLines(EDocumentPurchaseLine, PurchaseLine1, PurchaseLine2); - - // Create receipt header and receipt lines for the purchase order - CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); - CreateMockReceiptLine(PurchaseReceiptLine1, PurchaseReceiptHeader, Item."No.", LibraryRandom.RandDec(5, 2), PurchaseLine1); - CreateMockReceiptLine(PurchaseReceiptLine2, PurchaseReceiptHeader, Item."No.", LibraryRandom.RandDec(5, 2), PurchaseLine2); - - // [WHEN] LoadAvailableReceiptLinesForEDocumentLine is called - EDocPOMatching.LoadAvailableReceiptLinesForEDocumentLine(EDocumentPurchaseLine, TempPurchaseReceiptLine); - - // [THEN] All receipt lines for the matched PO lines should be loaded - Assert.AreEqual(2, TempPurchaseReceiptLine.Count(), 'Expected 2 receipt lines for matched PO lines'); - TempPurchaseReceiptLine.SetRange("Document No.", PurchaseReceiptLine1."Document No."); - TempPurchaseReceiptLine.SetRange("Line No.", PurchaseReceiptLine1."Line No."); - Assert.IsFalse(TempPurchaseReceiptLine.IsEmpty(), 'First receipt line should be included'); - TempPurchaseReceiptLine.SetRange("Line No.", PurchaseReceiptLine2."Line No."); - Assert.IsFalse(TempPurchaseReceiptLine.IsEmpty(), 'Second receipt line should be included'); - end; - - [Test] - procedure LoadAvailableReceiptLinesExcludesZeroQuantityLines() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - PurchaseHeader: Record "Purchase Header"; - PurchaseLine1, PurchaseLine2 : Record "Purchase Line"; - PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; - PurchaseReceiptLine1, PurchaseReceiptLine2 : Record "Purch. Rcpt. Line"; - TempPurchaseReceiptLine: Record "Purch. Rcpt. Line" temporary; - Item: Record Item; - begin - Initialize(); - ClearPurchaseDocumentsForVendor(); - // [SCENARIO] Loading available receipt lines excludes receipt lines with zero quantity - // [GIVEN] An E-Document line matched to PO lines with receipt lines having zero and non-zero quantities - LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); - - // Create E-Document Purchase Header and Line - EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; - EDocumentPurchaseHeader.Modify(); - EDocumentPurchaseLine := LibraryEDocument.InsertPurchaseDraftLine(EDocument); - - // Create Purchase Order with lines - LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); - LibraryEDocument.GetGenericItem(Item); - LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader, PurchaseLine1.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader, PurchaseLine2.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - - // Match PO lines to E-Document line - MatchEDocumentLineToTwoPOLines(EDocumentPurchaseLine, PurchaseLine1, PurchaseLine2); - - // Create receipt header and receipt lines - one with zero quantity, one with non-zero quantity - CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); - CreateMockReceiptLine(PurchaseReceiptLine1, PurchaseReceiptHeader, Item."No.", 0, PurchaseLine1); // Zero quantity - CreateMockReceiptLine(PurchaseReceiptLine2, PurchaseReceiptHeader, Item."No.", LibraryRandom.RandDec(5, 2), PurchaseLine2); // Non-zero quantity - - // [WHEN] LoadAvailableReceiptLinesForEDocumentLine is called - EDocPOMatching.LoadAvailableReceiptLinesForEDocumentLine(EDocumentPurchaseLine, TempPurchaseReceiptLine); - - // [THEN] Only receipt lines with non-zero quantities should be loaded - Assert.AreEqual(1, TempPurchaseReceiptLine.Count(), 'Expected 1 receipt line (excluding zero quantity line)'); - TempPurchaseReceiptLine.FindFirst(); - Assert.AreEqual(PurchaseReceiptLine2.SystemId, TempPurchaseReceiptLine.SystemId, 'Only non-zero quantity receipt line should be included'); - Assert.IsTrue(TempPurchaseReceiptLine.Quantity > 0, 'Loaded receipt line should have non-zero quantity'); - end; - [Test] procedure LoadReceiptsMatchedToEDocLineWithNoReceiptMatches() var @@ -519,60 +330,6 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" Assert.IsTrue(TempPurchaseReceiptHeader.IsEmpty(), 'Expected no receipt headers when E-Document line has no receipt line matches'); end; - [Test] - procedure LoadReceiptsMatchedToEDocLineReturnsUniqueReceiptHeaders() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - PurchaseHeader: Record "Purchase Header"; - PurchaseLine1, PurchaseLine2 : Record "Purchase Line"; - PurchaseReceiptHeader1, PurchaseReceiptHeader2 : Record "Purch. Rcpt. Header"; - PurchaseReceiptLine1, PurchaseReceiptLine2, PurchaseReceiptLine3 : Record "Purch. Rcpt. Line"; - TempPurchaseReceiptHeader: Record "Purch. Rcpt. Header" temporary; - Item: Record Item; - begin - Initialize(); - // [SCENARIO] Loading receipts matched to an E-Document line returns unique receipt headers - // [GIVEN] An E-Document line with receipt line matches from different receipt documents - LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); - - // Create E-Document Purchase Header and Line - EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; - EDocumentPurchaseHeader.Modify(); - EDocumentPurchaseLine := LibraryEDocument.InsertPurchaseDraftLine(EDocument); - - // Create Purchase Order with lines - LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); - LibraryEDocument.GetGenericItem(Item); - LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader, PurchaseLine1.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader, PurchaseLine2.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - - // Match E-Document line to PO lines - MatchEDocumentLineToTwoPOLines(EDocumentPurchaseLine, PurchaseLine1, PurchaseLine2); - - // Create receipt headers and lines - CreateMockReceiptHeader(PurchaseReceiptHeader1, Vendor."No."); - CreateMockReceiptHeader(PurchaseReceiptHeader2, Vendor."No."); - CreateMockReceiptLine(PurchaseReceiptLine1, PurchaseReceiptHeader1, Item."No.", LibraryRandom.RandDec(5, 2), PurchaseLine1); - CreateMockReceiptLine(PurchaseReceiptLine2, PurchaseReceiptHeader1, Item."No.", LibraryRandom.RandDec(5, 2), PurchaseLine1); - CreateMockReceiptLine(PurchaseReceiptLine3, PurchaseReceiptHeader2, Item."No.", LibraryRandom.RandDec(5, 2), PurchaseLine2); - - // Match receipt lines to E-Document line - MatchEDocumentLineToThreeReceiptLines(EDocumentPurchaseLine, PurchaseReceiptLine1, PurchaseReceiptLine2, PurchaseReceiptLine3); - - // [WHEN] LoadReceiptsMatchedToEDocumentLine is called - EDocPOMatching.LoadReceiptsMatchedToEDocumentLine(EDocumentPurchaseLine, TempPurchaseReceiptHeader); - - // [THEN] All unique receipt headers should be loaded into the temporary record - Assert.AreEqual(2, TempPurchaseReceiptHeader.Count(), 'Expected 2 unique receipt headers to be loaded'); - TempPurchaseReceiptHeader.SetRange("No.", PurchaseReceiptHeader1."No."); - Assert.IsFalse(TempPurchaseReceiptHeader.IsEmpty(), 'First receipt header should be included'); - TempPurchaseReceiptHeader.SetRange("No.", PurchaseReceiptHeader2."No."); - Assert.IsFalse(TempPurchaseReceiptHeader.IsEmpty(), 'Second receipt header should be included'); - end; - [Test] procedure CalculatePOMatchWarningsGeneratesMissingInformationWarning() var @@ -1668,49 +1425,6 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" asserterror EDocPOMatching.MatchPOLinesToEDocumentLine(TempPurchaseLine, EDocumentPurchaseLine); end; - [Test] - procedure MatchPOLinesWithDifferentTypesToEDocumentLineRaisesError() - var - EDocument: Record "E-Document"; - EDocumentPurchaseHeader: Record "E-Document Purchase Header"; - EDocumentPurchaseLine: Record "E-Document Purchase Line"; - PurchaseHeader: Record "Purchase Header"; - PurchaseLine1, PurchaseLine2 : Record "Purchase Line"; - VATPostingSetup: Record "VAT Posting Setup"; - VATProductPostingGroup: Record "VAT Product Posting Group"; - TempPurchaseLine: Record "Purchase Line" temporary; - Item: Record Item; - GLAccountNo: Code[20]; - begin - Initialize(); - // [SCENARIO] Matching PO lines with different types or numbers to E-Document line raises error - // [GIVEN] PO lines with different types or item numbers - LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); - EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); - EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; - EDocumentPurchaseHeader.Modify(); - EDocumentPurchaseLine := LibraryEDocument.InsertPurchaseDraftLine(EDocument); - - // Create Purchase Order with lines of different types - LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); - LibraryEDocument.GetGenericItem(Item); - LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader, PurchaseLine1.Type::Item, Item."No.", LibraryRandom.RandDec(10, 2)); - - LibraryERM.CreateVATProductPostingGroup(VATProductPostingGroup); - LibraryERM.CreateVATPostingSetup(VATPostingSetup, PurchaseHeader."VAT Bus. Posting Group", VATProductPostingGroup.Code); - GLAccountNo := LibraryERM.CreateGLAccountWithVATPostingSetup(VATPostingSetup, Enum::"General Posting Type"::" "); - LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader, PurchaseLine2.Type::"G/L Account", GLAccountNo, LibraryRandom.RandDec(10, 2)); - - TempPurchaseLine := PurchaseLine1; - TempPurchaseLine.Insert(); - TempPurchaseLine := PurchaseLine2; - TempPurchaseLine.Insert(); - - // [WHEN] MatchPOLinesToEDocumentLine is called - // [THEN] An error should be raised - asserterror EDocPOMatching.MatchPOLinesToEDocumentLine(TempPurchaseLine, EDocumentPurchaseLine); - end; - [Test] procedure MatchPOLinesAlreadyMatchedToOtherEDocumentLinesRaisesError() var @@ -2309,6 +2023,427 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" Assert.IsTrue(TempPOMatchWarnings.IsEmpty(), 'Expected no NotYetReceived warning for non-specified vendor'); end; + [Test] + procedure SuggestReceiptsForMatchedOrderLinesDoesNotSuggestWhenEDocLineAlreadyHasReceiptMatch() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + PurchaseReceiptLine: Record "Purch. Rcpt. Line"; + Item: Record Item; + begin + Initialize(); + // [SCENARIO] SuggestReceiptsForMatchedOrderLines suggests no receipts when E-Document line already has a receipt match + // [GIVEN] An E-Document line matched to a receipt line and an additional receipt line that could be suggested + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine, 10); + LibraryInventory.CreateItem(Item); + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, Item."No.", 10); + + // Match E-Document line to PO line + MatchEDocumentLineToPOLine(EDocumentPurchaseLine, PurchaseLine); + + // Create receipt with 2 lines that could be suggested, match the last one to an E-Document line + CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine, PurchaseReceiptHeader, Item."No.", 10, PurchaseLine); + CreateMockReceiptLine(PurchaseReceiptLine, PurchaseReceiptHeader, Item."No.", 10, PurchaseLine); + MatchEDocumentLineToReceiptLine(EDocumentPurchaseLine, PurchaseReceiptLine); + + // [WHEN] SuggestReceiptsForMatchedOrderLines is called + EDocPOMatching.SuggestReceiptsForMatchedOrderLines(EDocumentPurchaseHeader); + + // [THEN] No additional receipt lines are matched (still just the one) + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine, EDocumentPurchaseLine), 'Expected the original receipt match to remain'); + Assert.AreEqual(1, CountReceiptMatchesForEDocumentLine(EDocumentPurchaseLine), 'Expected exactly one receipt match'); + end; + + [Test] + procedure SuggestReceiptsForMatchedOrderLinesSuggestsReceiptWhenPOLineHasSingleReceiptCoveringFullQuantity() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + PurchaseReceiptLine: Record "Purch. Rcpt. Line"; + Item: Record Item; + begin + Initialize(); + // [SCENARIO] SuggestReceiptsForMatchedOrderLines suggests receipt when PO line has a single receipt that covers full quantity + // [GIVEN] An E-Document line matched to a purchase order line with quantity 10 + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine, 10); + + // [GIVEN] The purchase order line has one receipt line with quantity 10 + LibraryInventory.CreateItem(Item); + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, Item."No.", 10); + EDocumentPurchaseLine."[BC] Unit of Measure" := PurchaseLine."Unit of Measure Code"; + EDocumentPurchaseLine.Modify(); + MatchEDocumentLineToPOLine(EDocumentPurchaseLine, PurchaseLine); + + CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine, PurchaseReceiptHeader, Item."No.", 10, PurchaseLine); + + // [WHEN] SuggestReceiptsForMatchedOrderLines is called + EDocPOMatching.SuggestReceiptsForMatchedOrderLines(EDocumentPurchaseHeader); + + // [THEN] The receipt line is matched to the E-Document line + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine, EDocumentPurchaseLine), 'Expected receipt line to be matched to E-Document line'); + end; + + [Test] + procedure SuggestReceiptsForMatchedOrderLinesSuggestsNoReceiptWhenAllReceiptsHaveInsufficientQuantity() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + PurchaseReceiptLine1, PurchaseReceiptLine2 : Record "Purch. Rcpt. Line"; + Item: Record Item; + begin + Initialize(); + // [SCENARIO] SuggestReceiptsForMatchedOrderLines suggests no receipt when all receipts have insufficient quantity + // [GIVEN] An E-Document line matched to a purchase order line with quantity 10 + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine, 10); + + // [GIVEN] The purchase order line has two receipt lines with quantities 5 and 7 + LibraryInventory.CreateItem(Item); + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Order, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, Item."No.", 10); + MatchEDocumentLineToPOLine(EDocumentPurchaseLine, PurchaseLine); + + CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine1, PurchaseReceiptHeader, Item."No.", 5, PurchaseLine); + CreateMockReceiptLine(PurchaseReceiptLine2, PurchaseReceiptHeader, Item."No.", 7, PurchaseLine); + + // [WHEN] SuggestReceiptsForMatchedOrderLines is called + EDocPOMatching.SuggestReceiptsForMatchedOrderLines(EDocumentPurchaseHeader); + + // [THEN] No receipt lines are matched to the E-Document line + Assert.IsFalse(EDocPOMatching.IsEDocumentLineMatchedToAnyReceiptLine(EDocumentPurchaseLine), 'Expected no receipt lines to be matched'); + end; + + [Test] + procedure SuggestReceiptsForMatchedOrderLinesProcessesMultipleEDocumentLinesIndependently() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine1, EDocumentPurchaseLine2 : Record "E-Document Purchase Line"; + PurchaseHeader1, PurchaseHeader2 : Record "Purchase Header"; + PurchaseLine1, PurchaseLine2 : Record "Purchase Line"; + PurchaseReceiptHeader1, PurchaseReceiptHeader2 : Record "Purch. Rcpt. Header"; + PurchaseReceiptLine1, PurchaseReceiptLine2 : Record "Purch. Rcpt. Line"; + Item1, Item2 : Record Item; + begin + Initialize(); + // [SCENARIO] SuggestReceiptsForMatchedOrderLines processes multiple E-Document lines independently + // [GIVEN] An E-Document with two lines matched to different purchase order lines + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine1, 10); + // Second E-Document line + EDocumentPurchaseLine2 := LibraryEDocument.InsertPurchaseDraftLine(EDocument); + EDocumentPurchaseLine2.Quantity := 15; + EDocumentPurchaseLine2.Modify(); + + // [GIVEN] Each purchase order line has its own receipt that covers the full quantity + LibraryInventory.CreateItem(Item1); + LibraryPurchase.CreatePurchHeader(PurchaseHeader1, PurchaseHeader1."Document Type"::Order, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader1, PurchaseLine1.Type::Item, Item1."No.", 10); + EDocumentPurchaseLine1."[BC] Unit of Measure" := PurchaseLine1."Unit of Measure Code"; + EDocumentPurchaseLine1.Modify(); + MatchEDocumentLineToPOLine(EDocumentPurchaseLine1, PurchaseLine1); + CreateMockReceiptHeader(PurchaseReceiptHeader1, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine1, PurchaseReceiptHeader1, Item1."No.", 10, PurchaseLine1); + + LibraryInventory.CreateItem(Item2); + LibraryPurchase.CreatePurchHeader(PurchaseHeader2, PurchaseHeader2."Document Type"::Order, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader2, PurchaseLine2.Type::Item, Item2."No.", 15); + EDocumentPurchaseLine2."[BC] Unit of Measure" := PurchaseLine2."Unit of Measure Code"; + EDocumentPurchaseLine2.Modify(); + MatchEDocumentLineToPOLine(EDocumentPurchaseLine2, PurchaseLine2); + CreateMockReceiptHeader(PurchaseReceiptHeader2, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine2, PurchaseReceiptHeader2, Item2."No.", 15, PurchaseLine2); + + // [WHEN] SuggestReceiptsForMatchedOrderLines is called + EDocPOMatching.SuggestReceiptsForMatchedOrderLines(EDocumentPurchaseHeader); + + // [THEN] Each E-Document line is matched to its corresponding receipt line + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine1, EDocumentPurchaseLine1), 'Expected first receipt line to be matched to first E-Document line'); + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine2, EDocumentPurchaseLine2), 'Expected second receipt line to be matched to second E-Document line'); + end; + + [Test] + procedure TransferPOMatchesFromEDocumentToInvoiceTransfersReceiptMatchAndRemovesEDocumentMatches() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + PurchaseOrderHeader: Record "Purchase Header"; + PurchaseOrderLine: Record "Purchase Line"; + PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + PurchaseReceiptLine: Record "Purch. Rcpt. Line"; + Item: Record Item; + begin + Initialize(); + // [SCENARIO] TransferPOMatchesFromEDocumentToInvoice transfers receipt match to linked invoice line and removes E-Document matches + // [GIVEN] An E-Document line matched to a receipt line + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine, 10); + LibraryInventory.CreateItem(Item); + LibraryPurchase.CreatePurchHeader(PurchaseOrderHeader, PurchaseOrderHeader."Document Type"::Order, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine, PurchaseOrderHeader, PurchaseOrderLine.Type::Item, Item."No.", 10); + MatchEDocumentLineToPOLine(EDocumentPurchaseLine, PurchaseOrderLine); + + CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine, PurchaseReceiptHeader, Item."No.", 10, PurchaseOrderLine); + MatchEDocumentLineToReceiptLine(EDocumentPurchaseLine, PurchaseReceiptLine); + + // [AND] The E-Document line is linked to a purchase invoice line + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, Item."No.", 10); + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine, PurchaseLine); + + // [WHEN] TransferPOMatchesFromEDocumentToInvoice is called + EDocPOMatching.TransferPOMatchesFromEDocumentToInvoice(EDocument, PurchaseHeader); + + // [THEN] The invoice line's Receipt No. and Receipt Line No. are set + PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No."); + Assert.AreEqual(PurchaseReceiptLine."Document No.", PurchaseLine."Receipt No.", 'Expected invoice line Receipt No. to match receipt'); + Assert.AreEqual(PurchaseReceiptLine."Line No.", PurchaseLine."Receipt Line No.", 'Expected invoice line Receipt Line No. to match receipt line'); + + // [THEN] The E-Document line no longer has any PO or receipt matches + Assert.IsFalse(EDocPOMatching.IsEDocumentLineMatchedToAnyPOLine(EDocumentPurchaseLine), 'Expected E-Document line to have no PO matches'); + Assert.IsFalse(EDocPOMatching.IsEDocumentLineMatchedToAnyReceiptLine(EDocumentPurchaseLine), 'Expected E-Document line to have no receipt matches'); + end; + + [Test] + procedure TransferPOMatchesFromEDocumentToInvoiceProcessesMultipleLinesIndependently() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine1, EDocumentPurchaseLine2, EDocumentPurchaseLine3 : Record "E-Document Purchase Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine1, PurchaseLine2, PurchaseLine3 : Record "Purchase Line"; + PurchaseOrderHeader: Record "Purchase Header"; + PurchaseOrderLine1, PurchaseOrderLine2, PurchaseOrderLine3 : Record "Purchase Line"; + PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + PurchaseReceiptLine1, PurchaseReceiptLine2, PurchaseReceiptLine3 : Record "Purch. Rcpt. Line"; + Item1, Item2, Item3 : Record Item; + begin + Initialize(); + // [SCENARIO] TransferPOMatchesFromEDocumentToInvoice processes multiple lines independently + // [GIVEN] An E-Document with three lines matched to different receipt lines + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine1, 10); + EDocumentPurchaseLine2 := LibraryEDocument.InsertPurchaseDraftLine(EDocument); + EDocumentPurchaseLine2.Quantity := 15; + EDocumentPurchaseLine2.Modify(); + EDocumentPurchaseLine3 := LibraryEDocument.InsertPurchaseDraftLine(EDocument); + EDocumentPurchaseLine3.Quantity := 20; + EDocumentPurchaseLine3.Modify(); + + // Create purchase order with three lines + LibraryPurchase.CreatePurchHeader(PurchaseOrderHeader, PurchaseOrderHeader."Document Type"::Order, Vendor."No."); + LibraryInventory.CreateItem(Item1); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine1, PurchaseOrderHeader, PurchaseOrderLine1.Type::Item, Item1."No.", 10); + LibraryInventory.CreateItem(Item2); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine2, PurchaseOrderHeader, PurchaseOrderLine2.Type::Item, Item2."No.", 15); + LibraryInventory.CreateItem(Item3); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine3, PurchaseOrderHeader, PurchaseOrderLine3.Type::Item, Item3."No.", 20); + + // Create receipt lines + CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine1, PurchaseReceiptHeader, Item1."No.", 10, PurchaseOrderLine1); + CreateMockReceiptLine(PurchaseReceiptLine2, PurchaseReceiptHeader, Item2."No.", 15, PurchaseOrderLine2); + CreateMockReceiptLine(PurchaseReceiptLine3, PurchaseReceiptHeader, Item3."No.", 20, PurchaseOrderLine3); + + // Match E-Document lines to PO lines and receipt lines + MatchEDocumentLineToPOLine(EDocumentPurchaseLine1, PurchaseOrderLine1); + MatchEDocumentLineToReceiptLine(EDocumentPurchaseLine1, PurchaseReceiptLine1); + + MatchEDocumentLineToPOLine(EDocumentPurchaseLine2, PurchaseOrderLine2); + MatchEDocumentLineToReceiptLine(EDocumentPurchaseLine2, PurchaseReceiptLine2); + + MatchEDocumentLineToPOLine(EDocumentPurchaseLine3, PurchaseOrderLine3); + MatchEDocumentLineToReceiptLine(EDocumentPurchaseLine3, PurchaseReceiptLine3); + + // [GIVEN] A purchase invoice with three corresponding lines + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader, PurchaseLine1.Type::Item, Item1."No.", 10); + LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader, PurchaseLine2.Type::Item, Item2."No.", 15); + LibraryPurchase.CreatePurchaseLine(PurchaseLine3, PurchaseHeader, PurchaseLine3.Type::Item, Item3."No.", 20); + + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine1, PurchaseLine1); + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine2, PurchaseLine2); + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine3, PurchaseLine3); + + // [WHEN] TransferPOMatchesFromEDocumentToInvoice is called + EDocPOMatching.TransferPOMatchesFromEDocumentToInvoice(EDocument, PurchaseHeader); + + // [THEN] Each invoice line has the correct Receipt No. and Receipt Line No. + PurchaseLine1.Get(PurchaseLine1."Document Type", PurchaseLine1."Document No.", PurchaseLine1."Line No."); + Assert.AreEqual(PurchaseReceiptLine1."Document No.", PurchaseLine1."Receipt No.", 'Expected first invoice line Receipt No. to match receipt'); + Assert.AreEqual(PurchaseReceiptLine1."Line No.", PurchaseLine1."Receipt Line No.", 'Expected first invoice line Receipt Line No. to match receipt line'); + + PurchaseLine2.Get(PurchaseLine2."Document Type", PurchaseLine2."Document No.", PurchaseLine2."Line No."); + Assert.AreEqual(PurchaseReceiptLine2."Document No.", PurchaseLine2."Receipt No.", 'Expected second invoice line Receipt No. to match receipt'); + Assert.AreEqual(PurchaseReceiptLine2."Line No.", PurchaseLine2."Receipt Line No.", 'Expected second invoice line Receipt Line No. to match receipt line'); + + PurchaseLine3.Get(PurchaseLine3."Document Type", PurchaseLine3."Document No.", PurchaseLine3."Line No."); + Assert.AreEqual(PurchaseReceiptLine3."Document No.", PurchaseLine3."Receipt No.", 'Expected third invoice line Receipt No. to match receipt'); + Assert.AreEqual(PurchaseReceiptLine3."Line No.", PurchaseLine3."Receipt Line No.", 'Expected third invoice line Receipt Line No. to match receipt line'); + end; + + [Test] + procedure TransferPOMatchesFromInvoiceToEDocumentCreatesMatchesAndClearsInvoiceReceiptInfo() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine: Record "E-Document Purchase Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine: Record "Purchase Line"; + PurchaseOrderHeader: Record "Purchase Header"; + PurchaseOrderLine: Record "Purchase Line"; + PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + PurchaseReceiptLine: Record "Purch. Rcpt. Line"; + Item: Record Item; + begin + Initialize(); + // [SCENARIO] TransferPOMatchesFromInvoiceToEDocument creates matches from invoice line receipt info and clears invoice receipt info + // [GIVEN] A purchase invoice line with Receipt No. and Receipt Line No. + LibraryInventory.CreateItem(Item); + LibraryPurchase.CreatePurchHeader(PurchaseOrderHeader, PurchaseOrderHeader."Document Type"::Order, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine, PurchaseOrderHeader, PurchaseOrderLine.Type::Item, Item."No.", 10); + + CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine, PurchaseReceiptHeader, Item."No.", 10, PurchaseOrderLine); + + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine, PurchaseHeader, PurchaseLine.Type::Item, Item."No.", 10); + PurchaseLine."Receipt No." := PurchaseReceiptLine."Document No."; + PurchaseLine."Receipt Line No." := PurchaseReceiptLine."Line No."; + PurchaseLine.Modify(); + + // [GIVEN] The invoice line is linked to an E-Document line + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine, 10); + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine, PurchaseLine); + + // [WHEN] TransferPOMatchesFromInvoiceToEDocument is called + EDocPOMatching.TransferPOMatchesFromInvoiceToEDocument(PurchaseHeader, EDocument); + + // [THEN] The E-Document line is matched to the purchase order line + Assert.IsTrue(EDocPOMatching.IsPOLineMatchedToEDocumentLine(PurchaseOrderLine, EDocumentPurchaseLine), 'Expected E-Document line to be matched to PO line'); + + // [THEN] The E-Document line is matched to the receipt line + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine, EDocumentPurchaseLine), 'Expected E-Document line to be matched to receipt line'); + + // [THEN] The invoice line's Receipt No. is empty and Receipt Line No. is 0 + PurchaseLine.Get(PurchaseLine."Document Type", PurchaseLine."Document No.", PurchaseLine."Line No."); + Assert.AreEqual('', PurchaseLine."Receipt No.", 'Expected invoice line Receipt No. to be cleared'); + Assert.AreEqual(0, PurchaseLine."Receipt Line No.", 'Expected invoice line Receipt Line No. to be cleared'); + end; + + [Test] + procedure TransferPOMatchesFromInvoiceToEDocumentProcessesMultipleLinesIndependently() + var + EDocument: Record "E-Document"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + EDocumentPurchaseLine1, EDocumentPurchaseLine2, EDocumentPurchaseLine3 : Record "E-Document Purchase Line"; + PurchaseHeader: Record "Purchase Header"; + PurchaseLine1, PurchaseLine2, PurchaseLine3 : Record "Purchase Line"; + PurchaseOrderHeader: Record "Purchase Header"; + PurchaseOrderLine1, PurchaseOrderLine2, PurchaseOrderLine3 : Record "Purchase Line"; + PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; + PurchaseReceiptLine1, PurchaseReceiptLine2, PurchaseReceiptLine3 : Record "Purch. Rcpt. Line"; + Item1, Item2, Item3 : Record Item; + begin + Initialize(); + // [SCENARIO] TransferPOMatchesFromInvoiceToEDocument processes multiple lines independently + // [GIVEN] A purchase invoice with three lines, each with different receipt information + LibraryPurchase.CreatePurchHeader(PurchaseOrderHeader, PurchaseOrderHeader."Document Type"::Order, Vendor."No."); + LibraryInventory.CreateItem(Item1); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine1, PurchaseOrderHeader, PurchaseOrderLine1.Type::Item, Item1."No.", 10); + LibraryInventory.CreateItem(Item2); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine2, PurchaseOrderHeader, PurchaseOrderLine2.Type::Item, Item2."No.", 15); + LibraryInventory.CreateItem(Item3); + LibraryPurchase.CreatePurchaseLine(PurchaseOrderLine3, PurchaseOrderHeader, PurchaseOrderLine3.Type::Item, Item3."No.", 20); + + CreateMockReceiptHeader(PurchaseReceiptHeader, Vendor."No."); + CreateMockReceiptLine(PurchaseReceiptLine1, PurchaseReceiptHeader, Item1."No.", 10, PurchaseOrderLine1); + CreateMockReceiptLine(PurchaseReceiptLine2, PurchaseReceiptHeader, Item2."No.", 15, PurchaseOrderLine2); + CreateMockReceiptLine(PurchaseReceiptLine3, PurchaseReceiptHeader, Item3."No.", 20, PurchaseOrderLine3); + + LibraryPurchase.CreatePurchHeader(PurchaseHeader, PurchaseHeader."Document Type"::Invoice, Vendor."No."); + LibraryPurchase.CreatePurchaseLine(PurchaseLine1, PurchaseHeader, PurchaseLine1.Type::Item, Item1."No.", 10); + PurchaseLine1."Receipt No." := PurchaseReceiptLine1."Document No."; + PurchaseLine1."Receipt Line No." := PurchaseReceiptLine1."Line No."; + PurchaseLine1.Modify(); + + LibraryPurchase.CreatePurchaseLine(PurchaseLine2, PurchaseHeader, PurchaseLine2.Type::Item, Item2."No.", 15); + PurchaseLine2."Receipt No." := PurchaseReceiptLine2."Document No."; + PurchaseLine2."Receipt Line No." := PurchaseReceiptLine2."Line No."; + PurchaseLine2.Modify(); + + LibraryPurchase.CreatePurchaseLine(PurchaseLine3, PurchaseHeader, PurchaseLine3.Type::Item, Item3."No.", 20); + PurchaseLine3."Receipt No." := PurchaseReceiptLine3."Document No."; + PurchaseLine3."Receipt Line No." := PurchaseReceiptLine3."Line No."; + PurchaseLine3.Modify(); + + // [GIVEN] An E-Document with three corresponding lines + CreateMockEDocumentDraftWithLine(EDocument, EDocumentPurchaseHeader, EDocumentPurchaseLine1, 10); + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine1, PurchaseLine1); + + EDocumentPurchaseLine2 := LibraryEDocument.InsertPurchaseDraftLine(EDocument); + EDocumentPurchaseLine2.Quantity := 15; + EDocumentPurchaseLine2.Modify(); + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine2, PurchaseLine2); + + EDocumentPurchaseLine3 := LibraryEDocument.InsertPurchaseDraftLine(EDocument); + EDocumentPurchaseLine3.Quantity := 20; + EDocumentPurchaseLine3.Modify(); + LinkEDocumentLineToPurchaseLine(EDocument, EDocumentPurchaseLine3, PurchaseLine3); + + // [WHEN] TransferPOMatchesFromInvoiceToEDocument is called + EDocPOMatching.TransferPOMatchesFromInvoiceToEDocument(PurchaseHeader, EDocument); + + // [THEN] Each E-Document line is matched to its corresponding PO line and receipt line + Assert.IsTrue(EDocPOMatching.IsPOLineMatchedToEDocumentLine(PurchaseOrderLine1, EDocumentPurchaseLine1), 'Expected first E-Document line to be matched to first PO line'); + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine1, EDocumentPurchaseLine1), 'Expected first E-Document line to be matched to first receipt line'); + + Assert.IsTrue(EDocPOMatching.IsPOLineMatchedToEDocumentLine(PurchaseOrderLine2, EDocumentPurchaseLine2), 'Expected second E-Document line to be matched to second PO line'); + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine2, EDocumentPurchaseLine2), 'Expected second E-Document line to be matched to second receipt line'); + + Assert.IsTrue(EDocPOMatching.IsPOLineMatchedToEDocumentLine(PurchaseOrderLine3, EDocumentPurchaseLine3), 'Expected third E-Document line to be matched to third PO line'); + Assert.IsTrue(EDocPOMatching.IsReceiptLineMatchedToEDocumentLine(PurchaseReceiptLine3, EDocumentPurchaseLine3), 'Expected third E-Document line to be matched to third receipt line'); + + // [THEN] All invoice lines have their receipt information cleared + PurchaseLine1.Get(PurchaseLine1."Document Type", PurchaseLine1."Document No.", PurchaseLine1."Line No."); + Assert.AreEqual('', PurchaseLine1."Receipt No.", 'Expected first invoice line Receipt No. to be cleared'); + Assert.AreEqual(0, PurchaseLine1."Receipt Line No.", 'Expected first invoice line Receipt Line No. to be cleared'); + + PurchaseLine2.Get(PurchaseLine2."Document Type", PurchaseLine2."Document No.", PurchaseLine2."Line No."); + Assert.AreEqual('', PurchaseLine2."Receipt No.", 'Expected second invoice line Receipt No. to be cleared'); + Assert.AreEqual(0, PurchaseLine2."Receipt Line No.", 'Expected second invoice line Receipt Line No. to be cleared'); + + PurchaseLine3.Get(PurchaseLine3."Document Type", PurchaseLine3."Document No.", PurchaseLine3."Line No."); + Assert.AreEqual('', PurchaseLine3."Receipt No.", 'Expected third invoice line Receipt No. to be cleared'); + Assert.AreEqual(0, PurchaseLine3."Receipt Line No.", 'Expected third invoice line Receipt Line No. to be cleared'); + end; + + local procedure SetInvoiceNoSeriesInSetup() + var + PurchasesPayablesSetup: Record "Purchases & Payables Setup"; + begin + PurchasesPayablesSetup.Get(); + PurchasesPayablesSetup.Validate("Invoice Nos.", LibraryERM.CreateNoSeriesCode()); + PurchasesPayablesSetup.Modify(); + end; + local procedure SetupPOMatchingConfiguration(Configuration: Enum "E-Doc. PO M. Configuration"; VendorNo: Code[20]; IncludeVendorInList: Boolean) var EDocPOMatchingSetup: Record "E-Doc. PO Matching Setup"; @@ -2346,6 +2481,11 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" local procedure Initialize() begin LibraryLowerPermission.SetOutsideO365Scope(); + LibraryPurchase.SetOrderNoSeriesInSetup(); + LibraryPurchase.SetPostedNoSeriesInSetup(); + SetInvoiceNoSeriesInSetup(); + ClearPurchaseDocumentsForVendor(); + if IsInitialized then exit; LibraryEDocument.SetupStandardVAT(); @@ -2362,30 +2502,6 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" EDocPOMatching.MatchPOLinesToEDocumentLine(TempPurchaseLine, EDocumentLine); end; - local procedure MatchEDocumentLineToTwoPOLines(EDocumentLine: Record "E-Document Purchase Line"; PurchaseLine1: Record "Purchase Line"; PurchaseLine2: Record "Purchase Line") - var - TempPurchaseLine: Record "Purchase Line" temporary; - begin - TempPurchaseLine := PurchaseLine1; - TempPurchaseLine.Insert(); - TempPurchaseLine := PurchaseLine2; - TempPurchaseLine.Insert(); - EDocPOMatching.MatchPOLinesToEDocumentLine(TempPurchaseLine, EDocumentLine); - end; - - local procedure MatchEDocumentLineToThreePOLines(EDocumentLine: Record "E-Document Purchase Line"; PurchaseLine1: Record "Purchase Line"; PurchaseLine2: Record "Purchase Line"; PurchaseLine3: Record "Purchase Line") - var - TempPurchaseLine: Record "Purchase Line" temporary; - begin - TempPurchaseLine := PurchaseLine1; - TempPurchaseLine.Insert(); - TempPurchaseLine := PurchaseLine2; - TempPurchaseLine.Insert(); - TempPurchaseLine := PurchaseLine3; - TempPurchaseLine.Insert(); - EDocPOMatching.MatchPOLinesToEDocumentLine(TempPurchaseLine, EDocumentLine); - end; - local procedure MatchEDocumentLineToReceiptLine(EDocumentLine: Record "E-Document Purchase Line"; ReceiptLine: Record "Purch. Rcpt. Line") var TempReceiptLine: Record "Purch. Rcpt. Line" temporary; @@ -2395,19 +2511,6 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" EDocPOMatching.MatchReceiptLinesToEDocumentLine(TempReceiptLine, EDocumentLine); end; - local procedure MatchEDocumentLineToThreeReceiptLines(EDocumentLine: Record "E-Document Purchase Line"; ReceiptLine1: Record "Purch. Rcpt. Line"; ReceiptLine2: Record "Purch. Rcpt. Line"; ReceiptLine3: Record "Purch. Rcpt. Line") - var - TempReceiptLine: Record "Purch. Rcpt. Line" temporary; - begin - TempReceiptLine := ReceiptLine1; - TempReceiptLine.Insert(); - TempReceiptLine := ReceiptLine2; - TempReceiptLine.Insert(); - TempReceiptLine := ReceiptLine3; - TempReceiptLine.Insert(); - EDocPOMatching.MatchReceiptLinesToEDocumentLine(TempReceiptLine, EDocumentLine); - end; - local procedure CreateMockReceiptHeader(var PurchaseReceiptHeader: Record "Purch. Rcpt. Header"; VendorNo: Code[20]) begin PurchaseReceiptHeader.Init(); @@ -2442,4 +2545,38 @@ codeunit 133508 "E-Doc. PO Matching Unit Tests" PurchaseReceiptLine.Insert(); end; + local procedure LinkEDocumentLineToPurchaseLine(EDocument: Record "E-Document"; EDocumentPurchaseLine: Record "E-Document Purchase Line"; PurchaseLine: Record "Purchase Line") + var + EDocRecordLink: Record "E-Doc. Record Link"; + begin + EDocRecordLink."Source Table No." := Database::"E-Document Purchase Line"; + EDocRecordLink."Source SystemId" := EDocumentPurchaseLine.SystemId; + EDocRecordLink."Target Table No." := Database::"Purchase Line"; + EDocRecordLink."Target SystemId" := PurchaseLine.SystemId; + EDocRecordLink."E-Document Entry No." := EDocument."Entry No"; + EDocRecordLink.Insert(); + end; + + local procedure CountReceiptMatchesForEDocumentLine(EDocumentPurchaseLine: Record "E-Document Purchase Line"): Integer + var + EDocPurchaseLinePOMatch: Record "E-Doc. Purchase Line PO Match"; + NullGuid: Guid; + begin + EDocPurchaseLinePOMatch.SetRange("E-Doc. Purchase Line SystemId", EDocumentPurchaseLine.SystemId); + EDocPurchaseLinePOMatch.SetFilter("Receipt Line SystemId", '<>%1', NullGuid); + exit(EDocPurchaseLinePOMatch.Count()); + end; + + local procedure CreateMockEDocumentDraftWithLine(var EDocument: Record "E-Document"; var EDocumentPurchaseHeader: Record "E-Document Purchase Header"; var EDocumentPurchaseLine: Record "E-Document Purchase Line"; Quantity: Decimal) + begin + LibraryEDocument.CreateInboundEDocument(EDocument, EDocumentService); + EDocumentPurchaseHeader := LibraryEDocument.MockPurchaseDraftPrepared(EDocument); + EDocumentPurchaseHeader."[BC] Vendor No." := Vendor."No."; + EDocumentPurchaseHeader.Modify(); + EDocumentPurchaseLine := LibraryEDocument.InsertPurchaseDraftLine(EDocument); + EDocumentPurchaseLine.Quantity := Quantity; + EDocumentPurchaseLine.Modify(); + end; + } +