Codeunit 1303 Correct Posted Sales Invoice

App
Base Application
Namespace
Microsoft.Sales.History
Versions
17-28

Procedures, 11Events, 40Obsolete, 3

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al1689 lines, Copyright (c) Microsoft Corporation. MIT

// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Sales.History;

using Microsoft.EServices.EDocument;
using Microsoft.Finance.Currency;
using Microsoft.Finance.Dimension;
using Microsoft.Finance.GeneralLedger.Account;
using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.VAT.Setup;
using Microsoft.Foundation.NoSeries;
using Microsoft.Inventory;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Ledger;
using Microsoft.Inventory.Location;
using Microsoft.Inventory.Posting;
using Microsoft.Inventory.Setup;
using Microsoft.Projects.Project.Job;
using Microsoft.Projects.Project.Planning;
using Microsoft.Sales.Customer;
using Microsoft.Sales.Document;
using Microsoft.Sales.Posting;
using Microsoft.Sales.Receivables;
using Microsoft.Sales.Setup;
using Microsoft.Utilities;
using Microsoft.Warehouse.Request;
using System.Environment.Configuration;

/// <summary>
/// Handles the correction of posted sales invoices by creating a corrective sales credit memo.
/// </summary>
codeunit 1303 "Correct Posted Sales Invoice"
{
    Permissions = TableData "Sales Invoice Header" = rm,
                  TableData "Sales Cr.Memo Header" = rm;
    TableNo = "Sales Invoice Header";

    trigger OnRun()
    var
        SalesHeader: Record "Sales Header";
        NoSeries: Codeunit "No. Series";
        IsHandled: Boolean;
    begin
        UnapplyCostApplication(Rec."No.");

        OnBeforeCreateCorrectiveSalesCrMemo(Rec);
        CreateCopyDocument(Rec, SalesHeader, SalesHeader."Document Type"::"Credit Memo", false);
        OnAfterCreateCorrectiveSalesCrMemo(Rec, SalesHeader, CancellingOnly);

        if SalesInvoiceLinesContainJob(Rec."No.") then
            CreateAndProcessJobPlanningLines(SalesHeader);

        SuppressCommit := not NoSeries.IsNoSeriesInDateOrder(SalesHeader."Posting No. Series");

        IsHandled := false;
        OnRunOnBeforePostCorrectiveSalesCrMemo(Rec, SalesHeader, IsHandled, SuppressCommit);
        if not IsHandled then begin
            if not SuppressCommit then
                Commit();
            CODEUNIT.Run(CODEUNIT::"Sales-Post", SalesHeader);
            SetTrackInfoForCancellation(Rec);
            UpdateSalesOrderLinesFromCancelledInvoice(Rec."No.");
            ResetIncomingDocumentForCancelledInvoice(Rec."No.");
        end;
        OnOnRunOnAfterUpdateSalesOrderLinesFromCancelledInvoice(Rec, SalesHeader);

        Commit();
    end;

    var
        SalesReceivablesSetup: Record "Sales & Receivables Setup";
        CancellingOnly: Boolean;
        SuppressCommit: Boolean;

        PostedInvoiceIsPaidCorrectErr: Label 'You cannot correct this posted sales invoice because it is fully or partially paid.\\To reverse a paid sales invoice, you must manually create a sales credit memo.';
        PostedInvoiceIsPaidCancelErr: Label 'You cannot cancel this posted sales invoice because it is fully or partially paid.\\To reverse a paid sales invoice, you must manually create a sales credit memo.';
        AlreadyCorrectedErr: Label 'You cannot correct this posted sales invoice because it has been canceled.';
        AlreadyCancelledErr: Label 'You cannot cancel this posted sales invoice because it has already been canceled.';
        CorrCorrectiveDocErr: Label 'You cannot correct this posted sales invoice because it represents a correction of a credit memo.';
        CancelCorrectiveDocErr: Label 'You cannot cancel this posted sales invoice because it represents a correction of a credit memo.';
        CustomerIsBlockedCorrectErr: Label 'You cannot correct this posted sales invoice because customer %1 is blocked.', Comment = '%1 = Customer name';
        CustomerIsBlockedCancelErr: Label 'You cannot cancel this posted sales invoice because customer %1 is blocked.', Comment = '%1 = Customer name';
        ItemIsBlockedCorrectErr: Label 'You cannot correct this posted sales invoice because item %1 %2 is blocked.', Comment = '%1 = Item No. %2 = Item Description';
        ItemIsBlockedCancelErr: Label 'You cannot cancel this posted sales invoice because item %1 %2 is blocked.', Comment = '%1 = Item No. %2 = Item Description';
        ItemVariantIsBlockedCorrectErr: Label 'You cannot correct this posted sales invoice because item variant %1 for item %2 %3 is blocked.', Comment = '%1 - Item Variant Code, %2 = Item No. %3 = Item Description';
        ItemVariantIsBlockedCancelErr: Label 'You cannot cancel this posted sales invoice because item variant %1 for item %2 %3 is blocked.', Comment = '%1 - Item Variant Code, %2 = Item No. %3 = Item Description';
        AccountIsBlockedCorrectErr: Label 'You cannot correct this posted sales invoice because %1 %2 is blocked.', Comment = '%1 = Table Caption %2 = Account number.';
        AccountIsBlockedCancelErr: Label 'You cannot cancel this posted sales invoice because %1 %2 is blocked.', Comment = '%1 = Table Caption %2 = Account number.';
        NoFreeInvoiceNoSeriesCorrectErr: Label 'You cannot correct this posted sales invoice because no unused invoice numbers are available. \\You must extend the range of the number series for sales invoices.';
        NoFreeInvoiceNoSeriesCancelErr: Label 'You cannot cancel this posted sales invoice because no unused invoice numbers are available. \\You must extend the range of the number series for sales invoices.';
        NoFreeCMSeriesCorrectErr: Label 'You cannot correct this posted sales invoice because no unused credit memo numbers are available. \\You must extend the range of the number series for credit memos.';
        NoFreeCMSeriesCancelErr: Label 'You cannot cancel this posted sales invoice because no unused credit memo numbers are available. \\You must extend the range of the number series for credit memos.';
        NoFreePostCMSeriesCorrectErr: Label 'You cannot correct this posted sales invoice because no unused posted credit memo numbers are available. \\You must extend the range of the number series for posted credit memos.';
        NoFreePostCMSeriesCancelErr: Label 'You cannot cancel this posted sales invoice because no unused posted credit memo numbers are available. \\You must extend the range of the number series for posted credit memos.';
        SalesLineFromOrderCorrectErr: Label 'You cannot correct this posted sales invoice because item %1 %2 is used on a sales order.', Comment = '%1 = Item no. %2 = Item description';
        ShippedQtyReturnedCorrectErr: Label 'You cannot correct this posted sales invoice because item %1 %2 has already been fully or partially returned.', Comment = '%1 = Item no. %2 = Item description.';
        ShippedQtyReturnedCancelErr: Label 'You cannot cancel this posted sales invoice because item %1 %2 has already been fully or partially returned.', Comment = '%1 = Item no. %2 = Item description.';
        UsedInJobCorrectErr: Label 'You cannot correct this posted sales invoice because item %1 %2 is used in a project.', Comment = '%1 = Item no. %2 = Item description.';
        UsedInJobCancelErr: Label 'You cannot cancel this posted sales invoice because item %1 %2 is used in a project.', Comment = '%1 = Item no. %2 = Item description.';
        PostingNotAllowedCorrectErr: Label 'You cannot correct this posted sales invoice because it was posted in a posting period that is closed.';
        PostingNotAllowedCancelErr: Label 'You cannot cancel this posted sales invoice because it was posted in a posting period that is closed.';
        LineTypeNotAllowedCorrectErr: Label 'You cannot correct this posted sales invoice because the sales invoice line for %1 %2 is of type %3, which is not allowed on a simplified sales invoice.', Comment = '%1 = Item no. %2 = Item description %3 = Item type.';
        LineTypeNotAllowedCancelErr: Label 'You cannot cancel this posted sales invoice because the sales invoice line for %1 %2 is of type %3, which is not allowed on a simplified sales invoice.', Comment = '%1 = Item no. %2 = Item description %3 = Item type.';
        InvalidDimCodeCorrectErr: Label 'You cannot correct this posted sales invoice because the dimension rule setup for account ''%1'' %2 prevents %3 %4 from being canceled.', Comment = '%1 = Table caption %2 = Account number %3 = Item no. %4 = Item description.';
        InvalidDimCodeCancelErr: Label 'You cannot cancel this posted sales invoice because the dimension rule setup for account ''%1'' %2 prevents %3 %4 from being canceled.', Comment = '%1 = Table caption %2 = Account number %3 = Item no. %4 = Item description.';
        InvalidDimCombinationCorrectErr: Label 'You cannot correct this posted sales invoice because the dimension combination for item %1 %2 is not allowed.', Comment = '%1 = Item no. %2 = Item description.';
        InvalidDimCombinationCancelErr: Label 'You cannot cancel this posted sales invoice because the dimension combination for item %1 %2 is not allowed.', Comment = '%1 = Item no. %2 = Item description.';
        InvalidDimCombHeaderCorrectErr: Label 'You cannot correct this posted sales invoice because the combination of dimensions on the invoice is blocked.';
        InvalidDimCombHeaderCancelErr: Label 'You cannot cancel this posted sales invoice because the combination of dimensions on the invoice is blocked.';
        ExternalDocCorrectErr: Label 'You cannot correct this posted sales invoice because the external document number is required on the invoice.';
        ExternalDocCancelErr: Label 'You cannot cancel this posted sales invoice because the external document number is required on the invoice.';
        InventoryPostClosedCorrectErr: Label 'You cannot correct this posted sales invoice because the posting inventory period is already closed.';
        InventoryPostClosedCancelErr: Label 'You cannot cancel this posted sales invoice because the posting inventory period is already closed.';
        FixedAssetNotPossibleToCreateCreditMemoErr: Label 'You cannot cancel this posted sales invoice because it contains lines of type Fixed Asset.\\Use the Cancel Entries function in the FA Ledger Entries window instead.';
#pragma warning disable AA0470
        PostingCreditMemoFailedOpenPostedCMQst: Label 'Canceling the invoice failed because of the following error: \\%1\\A credit memo is posted. Do you want to open the posted credit memo?';
        PostingCreditMemoFailedOpenCMQst: Label 'Canceling the invoice failed because of the following error: \\%1\\A credit memo is created but not posted. Do you want to open the credit memo?';
        CreatingCreditMemoFailedNothingCreatedErr: Label 'Canceling the invoice failed because of the following error: \\%1.';
#pragma warning restore AA0470
        WrongDocumentTypeForCopyDocumentErr: Label 'You cannot correct or cancel this type of document.';
        CheckPrepaymentErr: Label 'You cannot correct or cancel a posted sales prepayment invoice.\\Open the related sales order and choose the Post Prepayment Credit Memo.';
        InvoicePartiallyPaidMsg: Label 'Invoice %1 is partially paid or credited. The corrective credit memo may not be fully closed by the invoice.', Comment = '%1 - invoice no.';
        InvoiceClosedMsg: Label 'Invoice %1 is closed. The corrective credit memo will not be applied to the invoice.', Comment = '%1 - invoice no.';
        SkipLbl: Label 'Skip';
        CreateCreditMemoLbl: Label 'Create credit memo anyway';
        ShowEntriesLbl: Label 'Show applied entries';
        WMSLocationCancelCorrectErr: Label 'You cannot cancel or correct this posted sales invoice because Warehouse Receive is required for Line No. = %1.', Comment = '%1 - line number';
        DropShipmentDocumentExistsErr: Label 'You cannot use the cancel or correct functionality because the invoice line is associated with purchase order %1 due to Drop Shipment.', Comment = '%1 - Purchase Order No.';
        CreateCreditMemoQst: Label 'The invoice was posted from an order. A Sales Credit memo will be created which you complete and post manually. The quantities will be corrected in the existing Sales Order.\ \Do you want to continue?';

    /// <summary>
    /// Cancels the posted sales invoice by creating and posting a corrective credit memo.
    /// </summary>
    /// <param name="SalesInvoiceHeader">Specifies the posted sales invoice to cancel.</param>
    /// <returns>True if the invoice was successfully canceled, otherwise false.</returns>
    procedure CancelPostedInvoice(var SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean
    begin
        CancellingOnly := true;
        exit(CreateCreditMemo(SalesInvoiceHeader));
    end;

    local procedure CreateCreditMemo(var SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean
    var
        SalesHeader: Record "Sales Header";
        SalesCrMemoHeader: Record "Sales Cr.Memo Header";
        PageManagement: Codeunit "Page Management";
        IsHandled: Boolean;
    begin
        TestCorrectInvoiceIsAllowed(SalesInvoiceHeader, CancellingOnly);
        if not CODEUNIT.Run(CODEUNIT::"Correct Posted Sales Invoice", SalesInvoiceHeader) then begin
            SalesCrMemoHeader.SetRange("Applies-to Doc. No.", SalesInvoiceHeader."No.");
            if SalesCrMemoHeader.FindFirst() then begin
                if Confirm(StrSubstNo(PostingCreditMemoFailedOpenPostedCMQst, GetLastErrorText)) then begin
                    IsHandled := false;
                    OnCreateCreditMemoOnBeforePostedPageRun(SalesCrMemoHeader, IsHandled);
                    if not IsHandled then
                        PageManagement.PageRun(SalesCrMemoHeader);
                end;
            end else begin
                SalesHeader.SetRange("Applies-to Doc. No.", SalesInvoiceHeader."No.");
                if SalesHeader.FindFirst() then begin
                    if Confirm(StrSubstNo(PostingCreditMemoFailedOpenCMQst, GetLastErrorText)) then begin
                        IsHandled := false;
                        OnCreateCreditMemoOnBeforePageRun(SalesHeader, IsHandled);
                        if not IsHandled then
                            PAGE.Run(PAGE::"Sales Credit Memo", SalesHeader);
                    end;
                end else
                    Error(CreatingCreditMemoFailedNothingCreatedErr, GetLastErrorText);
            end;
            exit(false);
        end;
        exit(true);
    end;

    local procedure CreateCopyDocument(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"; DocumentType: Enum "Sales Document Type"; SkipCopyFromDescription: Boolean)
    var
        CopyDocMgt: Codeunit "Copy Document Mgt.";
    begin
        Clear(SalesHeader);
        SalesHeader."No." := '';
        SalesHeader."Document Type" := DocumentType;
        SalesHeader.SetAllowSelectNoSeries();
        OnBeforeSalesHeaderInsert(SalesHeader, SalesInvoiceHeader, CancellingOnly);
        SalesHeader.Insert(true);

        case DocumentType of
            SalesHeader."Document Type"::"Credit Memo":
                CopyDocMgt.SetPropertiesForCorrectiveCreditMemo(true);
            SalesHeader."Document Type"::Invoice:
                CopyDocMgt.SetPropertiesForInvoiceCorrection(SkipCopyFromDescription);
            else
                Error(WrongDocumentTypeForCopyDocumentErr);
        end;

        CopyDocMgt.CopySalesDocForInvoiceCancelling(SalesInvoiceHeader."No.", SalesHeader);
        OnAfterCreateCopyDocument(SalesHeader, SalesInvoiceHeader);
    end;

    /// <summary>
    /// Creates a credit memo document as a copy of the posted sales invoice without posting it.
    /// </summary>
    /// <param name="SalesInvoiceHeader">Specifies the posted sales invoice to copy from.</param>
    /// <param name="SalesHeader">Returns the created sales credit memo header.</param>
    /// <returns>True if the credit memo was successfully created, otherwise false.</returns>
    procedure CreateCreditMemoCopyDocument(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"): Boolean
    var
        SalesHdr: Record "Sales Header";
        ConfirmQuestion: Text;
    begin
        OnBeforeCreateCreditMemoCopyDocument(SalesInvoiceHeader);
        TestNoFixedAssetInSalesInvoice(SalesInvoiceHeader);
        TestNotSalesPrepaymentlInvoice(SalesInvoiceHeader);
        TestIfDropShipmentDocument(SalesInvoiceHeader);
        if not SalesInvoiceHeader.IsFullyOpen() then begin
            ShowInvoiceAppliedNotification(SalesInvoiceHeader);
            exit(false);
        end;
        SalesHdr.SetRange("Document Type", SalesHdr."Document Type"::Order);
        SalesHdr.SetRange("No.", SalesInvoiceHeader."Order No.");
        if not SalesHdr.IsEmpty then begin
            ConfirmQuestion := CreateCreditMemoQst;
            OnCreateCreditMemoCopyDocumentOnBeforeConfirm(SalesInvoiceHeader, SalesHdr, ConfirmQuestion);
            if not Confirm(ConfirmQuestion) then
                exit(false);
        end;

        CreateCopyDocument(SalesInvoiceHeader, SalesHeader, SalesHeader."Document Type"::"Credit Memo", false);

        if SalesInvoiceLinesContainJob(SalesInvoiceHeader."No.") then
            CreateAndProcessJobPlanningLines(SalesHeader);

        exit(true);
    end;

    /// <summary>
    /// Creates a corrective credit memo from a notification action triggered on a posted sales invoice.
    /// </summary>
    /// <param name="InvoiceNotification">Specifies the notification containing the invoice number to correct.</param>
    procedure CreateCorrectiveCreditMemo(var InvoiceNotification: Notification)
    var
        SalesHeader: Record "Sales Header";
        SalesInvoiceHeader: Record "Sales Invoice Header";
        IsHandled: Boolean;
    begin
        SalesInvoiceHeader.Get(InvoiceNotification.GetData(SalesInvoiceHeader.FieldName("No.")));
        InvoiceNotification.Recall();

        CreateCopyDocument(SalesInvoiceHeader, SalesHeader, SalesHeader."Document Type"::"Credit Memo", false);

        if SalesInvoiceLinesContainJob(SalesInvoiceHeader."No.") then
            CreateAndProcessJobPlanningLines(SalesHeader);

        IsHandled := false;
        OnCreateCorrectiveCreditMemoOnBeforePageRun(SalesHeader, IsHandled);
        if not IsHandled then
            PAGE.Run(PAGE::"Sales Credit Memo", SalesHeader);
    end;

    /// <summary>
    /// Shows the applied customer ledger entries for the posted sales invoice referenced in the notification.
    /// </summary>
    /// <param name="InvoiceNotification">Specifies the notification containing the invoice number.</param>
    procedure ShowAppliedEntries(var InvoiceNotification: Notification)
    var
        CustLedgerEntry: Record "Cust. Ledger Entry";
        SalesInvoiceHeader: Record "Sales Invoice Header";
    begin
        SalesInvoiceHeader.Get(InvoiceNotification.GetData(SalesInvoiceHeader.FieldName("No.")));
        CustLedgerEntry.Get(SalesInvoiceHeader."Cust. Ledger Entry No.");
        PAGE.RunModal(PAGE::"Applied Customer Entries", CustLedgerEntry);
    end;

    /// <summary>
    /// Dismisses the corrective credit memo notification without taking any action.
    /// </summary>
    /// <param name="InvoiceNotification">Specifies the notification to dismiss.</param>
    procedure SkipCorrectiveCreditMemo(var InvoiceNotification: Notification)
    begin
        InvoiceNotification.Recall();
    end;

    local procedure CreateAndProcessJobPlanningLines(SalesHeader: Record "Sales Header")
    var
        SalesLine: Record "Sales Line";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeCreateAndProcessJobPlanningLines(SalesHeader, IsHandled);
        if IsHandled then
            exit;

        SalesLine.SetRange("Document No.", SalesHeader."No.");
        SalesLine.SetRange("Document Type", SalesHeader."Document Type");
        SalesLine.SetFilter("Job Contract Entry No.", '<>0');
        if SalesLine.FindSet() then
            repeat
                SalesLine."Job Contract Entry No." := CreateJobPlanningLine(SalesHeader, SalesLine);
                SalesLine.Modify();
            until SalesLine.Next() = 0;
    end;

    local procedure CreateJobPlanningLine(SalesHeader: Record "Sales Header"; SalesLine: Record "Sales Line"): Integer
    var
        FromJobPlanningLine: Record "Job Planning Line";
        ToJobPlanningLine: Record "Job Planning Line";
        JobPlanningLineInvoice: Record "Job Planning Line Invoice";
    begin
        FromJobPlanningLine.SetCurrentKey("Job Contract Entry No.");
        FromJobPlanningLine.SetRange("Job Contract Entry No.", SalesLine."Job Contract Entry No.");
        FromJobPlanningLine.FindFirst();

        ToJobPlanningLine.InitFromJobPlanningLine(FromJobPlanningLine, -SalesLine.Quantity);
        OnCreateJobPlanningLineOnAfterInitFromJobPlanningLine(ToJobPlanningLine, FromJobPlanningLine, SalesLine);
        JobPlanningLineInvoice.InitFromJobPlanningLine(ToJobPlanningLine);
        JobPlanningLineInvoice.InitFromSales(SalesHeader, SalesHeader."Posting Date", SalesLine."Line No.");
        JobPlanningLineInvoice.Insert();

        ToJobPlanningLine.UpdateQtyToTransfer();
        ToJobPlanningLine.Insert();

        exit(ToJobPlanningLine."Job Contract Entry No.");
    end;

    /// <summary>
    /// Cancels the posted sales invoice and creates a new sales invoice as a copy for correction.
    /// </summary>
    /// <param name="SalesInvoiceHeader">Specifies the posted sales invoice to cancel.</param>
    /// <param name="SalesHeader">Returns the newly created sales invoice header for correction.</param>
    procedure CancelPostedInvoiceCreateNewInvoice(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header")
    begin
        CancellingOnly := false;

        if CreateCreditMemo(SalesInvoiceHeader) then begin
            CreateCopyDocument(SalesInvoiceHeader, SalesHeader, SalesHeader."Document Type"::Invoice, true);
            OnAfterCreateCorrSalesInvoice(SalesHeader, SalesInvoiceHeader);
            Commit();
        end;
    end;

    /// <summary>
    /// Tests whether the posted sales invoice can be corrected or canceled by checking payment status, customer blocks, and other conditions.
    /// </summary>
    /// <param name="SalesInvoiceHeader">Specifies the posted sales invoice to validate.</param>
    /// <param name="Cancelling">Specifies whether the operation is a cancellation or correction.</param>
    procedure TestCorrectInvoiceIsAllowed(var SalesInvoiceHeader: Record "Sales Invoice Header"; Cancelling: Boolean)
    begin
        CancellingOnly := Cancelling;

        TestIfPostingIsAllowed(SalesInvoiceHeader);
        TestIfInvoiceIsCorrectedOnce(SalesInvoiceHeader);
        TestIfInvoiceIsNotCorrectiveDoc(SalesInvoiceHeader);
        TestIfInvoiceIsPaid(SalesInvoiceHeader);
        TestIfCustomerIsBlocked(SalesInvoiceHeader, SalesInvoiceHeader."Sell-to Customer No.");
        TestIfCustomerIsBlocked(SalesInvoiceHeader, SalesInvoiceHeader."Bill-to Customer No.");
        TestIfJobPostingIsAllowed(SalesInvoiceHeader."No.");
        TestCustomerDimension(SalesInvoiceHeader, SalesInvoiceHeader."Bill-to Customer No.");
        TestDimensionOnHeader(SalesInvoiceHeader);
        TestSalesLines(SalesInvoiceHeader);
        TestIfAnyFreeNumberSeries(SalesInvoiceHeader);
        TestExternalDocument(SalesInvoiceHeader);
        TestInventoryPostingClosed(SalesInvoiceHeader);
        TestNotSalesPrepaymentlInvoice(SalesInvoiceHeader);
        TestIfDropShipmentDocument(SalesInvoiceHeader);

        OnAfterTestCorrectInvoiceIsAllowed(SalesInvoiceHeader, Cancelling);
    end;

    local procedure ShowInvoiceAppliedNotification(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        NotificationLifecycleMgt: Codeunit "Notification Lifecycle Mgt.";
        InvoiceNotification: Notification;
        NotificationText: Text;
    begin
        InvoiceNotification.Id := CreateGuid();
        InvoiceNotification.Scope(NOTIFICATIONSCOPE::LocalScope);
        InvoiceNotification.SetData(SalesInvoiceHeader.FieldName("No."), SalesInvoiceHeader."No.");
        SalesInvoiceHeader.CalcFields(Closed);
        if SalesInvoiceHeader.Closed then
            NotificationText := StrSubstNo(InvoiceClosedMsg, SalesInvoiceHeader."No.")
        else
            NotificationText := StrSubstNo(InvoicePartiallyPaidMsg, SalesInvoiceHeader."No.");
        InvoiceNotification.Message(NotificationText);
        InvoiceNotification.AddAction(ShowEntriesLbl, CODEUNIT::"Correct Posted Sales Invoice", 'ShowAppliedEntries');
        InvoiceNotification.AddAction(SkipLbl, CODEUNIT::"Correct Posted Sales Invoice", 'SkipCorrectiveCreditMemo');
        InvoiceNotification.AddAction(CreateCreditMemoLbl, CODEUNIT::"Correct Posted Sales Invoice", 'CreateCorrectiveCreditMemo');
        NotificationLifecycleMgt.SendNotification(InvoiceNotification, SalesInvoiceHeader.RecordId);
    end;

    local procedure SetTrackInfoForCancellation(var SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        SalesCrMemoHeader: Record "Sales Cr.Memo Header";
        CancelledDocument: Record "Cancelled Document";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeSetTrackInfoForCancellation(SalesInvoiceHeader, IsHandled);
        if IsHandled then
            exit;

        SalesCrMemoHeader.SetRange("Applies-to Doc. No.", SalesInvoiceHeader."No.");
        if SalesCrMemoHeader.FindLast() then
            CancelledDocument.InsertSalesInvToCrMemoCancelledDocument(SalesInvoiceHeader."No.", SalesCrMemoHeader."No.");
    end;

    local procedure SalesInvoiceLinesContainJob(InvoiceNo: Code[20]): Boolean
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
    begin
        SalesInvoiceLine.SetRange("Document No.", InvoiceNo);
        SalesInvoiceLine.SetFilter("Job No.", '<>%1', '');
        exit(not SalesInvoiceLine.IsEmpty);
    end;

    local procedure TestDimensionOnHeader(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        DimensionManagement: Codeunit DimensionManagement;
    begin
        if not DimensionManagement.CheckDimIDComb(SalesInvoiceHeader."Dimension Set ID") then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::DimCombHeaderErr, SalesInvoiceHeader);
    end;

    local procedure TestIfCustomerIsBlocked(SalesInvoiceHeader: Record "Sales Invoice Header"; CustNo: Code[20])
    var
        Customer: Record Customer;
    begin
        Customer.Get(CustNo);
        if Customer.Blocked in [Customer.Blocked::Invoice, Customer.Blocked::All] then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::CustomerBlocked, SalesInvoiceHeader);
    end;

    local procedure TestCustomerDimension(SalesInvoiceHeader: Record "Sales Invoice Header"; CustNo: Code[20])
    var
        Customer: Record Customer;
        DimensionManagement: Codeunit DimensionManagement;
        TableID: array[10] of Integer;
        No: array[10] of Code[20];
    begin
        Customer.Get(CustNo);
        TableID[1] := DATABASE::Customer;
        No[1] := Customer."No.";
        if not DimensionManagement.CheckDimValuePosting(TableID, No, SalesInvoiceHeader."Dimension Set ID") then
            ErrorHelperAccount(Enum::"Correct Sales Inv. Error Type"::DimErr, Customer."No.", Customer.TableCaption(), Customer."No.", Customer.Name);
    end;

    local procedure TestSalesLines(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
        Item: Record Item;
        ItemVariant: Record "Item Variant";
        DimensionManagement: Codeunit DimensionManagement;
        ShippedQtyNoReturned: Decimal;
        RevUnitCostLCY: Decimal;
        TableID: array[10] of Integer;
        No: array[10] of Code[20];
    begin
        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
        TestNoFixedAssetInSalesLines(SalesInvoiceLine);
        if SalesInvoiceLine.Find('-') then
            repeat
                if not IsCommentLine(SalesInvoiceLine) then begin
                    TestSalesLineType(SalesInvoiceLine);

                    if SalesInvoiceLine.Type = SalesInvoiceLine.Type::Item then begin
                        if (SalesInvoiceLine.Quantity > 0) and (SalesInvoiceLine."Job No." = '') and
                           WasNotCancelled(SalesInvoiceHeader."No.")
                        then begin
                            SalesInvoiceLine.CalcShippedSaleNotReturned(ShippedQtyNoReturned, RevUnitCostLCY, false);
                            OnTestSalesLinesOnAfterCalcShippedQtyNoReturned(SalesInvoiceLine, ShippedQtyNoReturned);
                            if SalesInvoiceLine.Quantity <> ShippedQtyNoReturned then
                                ErrorHelperLine(Enum::"Correct Sales Inv. Error Type"::ItemIsReturned, SalesInvoiceLine);
                        end;

                        Item.Get(SalesInvoiceLine."No.");

                        if Item.Blocked then
                            ErrorHelperLine(Enum::"Correct Sales Inv. Error Type"::ItemBlocked, SalesInvoiceLine);
                        if SalesInvoiceLine."Variant Code" <> '' then begin
                            ItemVariant.SetLoadFields(Blocked);
                            if ItemVariant.Get(SalesInvoiceLine."No.", SalesInvoiceLine."Variant Code") and ItemVariant.Blocked then
                                ErrorHelperLine("Correct Sales Inv. Error Type"::ItemVariantBlocked, SalesInvoiceLine);
                        end;

                        if SalesInvoiceLine.Quantity <> 0 then begin
                            TableID[1] := DATABASE::Item;
                            No[1] := SalesInvoiceLine."No.";
                            if not DimensionManagement.CheckDimValuePosting(TableID, No, SalesInvoiceLine."Dimension Set ID") then
                                ErrorHelperAccount(Enum::"Correct Sales Inv. Error Type"::DimErr, No[1], Item.TableCaption(), Item."No.", Item.Description);
                        end;

                        if Item.Type = Item.Type::Inventory then
                            TestInventoryPostingSetup(SalesInvoiceLine);
                    end;

                    TestGenPostingSetup(SalesInvoiceLine);
                    TestCustomerPostingGroup(SalesInvoiceHeader);
                    TestVATPostingSetup(SalesInvoiceLine);
                    TestWMSLocation(SalesInvoiceLine);

                    if not DimensionManagement.CheckDimIDComb(SalesInvoiceLine."Dimension Set ID") then
                        ErrorHelperLine(Enum::"Correct Sales Inv. Error Type"::DimCombErr, SalesInvoiceLine);
                end;
            until SalesInvoiceLine.Next() = 0;
    end;

    local procedure TestGLAccount(AccountNo: Code[20]; SalesInvoiceLine: Record "Sales Invoice Line")
    var
        GLAccount: Record "G/L Account";
        Item: Record Item;
        DimensionManagement: Codeunit DimensionManagement;
        TableID: array[10] of Integer;
        No: array[10] of Code[20];
    begin
        GLAccount.Get(AccountNo);
        if GLAccount.Blocked then
            ErrorHelperAccount(Enum::"Correct Sales Inv. Error Type"::AccountBlocked, AccountNo, GLAccount.TableCaption(), '', '');
        TableID[1] := DATABASE::"G/L Account";
        No[1] := AccountNo;

        if SalesInvoiceLine.Type = SalesInvoiceLine.Type::Item then begin
            Item.Get(SalesInvoiceLine."No.");
            if not DimensionManagement.CheckDimValuePosting(TableID, No, SalesInvoiceLine."Dimension Set ID") then
                ErrorHelperAccount(Enum::"Correct Sales Inv. Error Type"::DimErr, AccountNo, GLAccount.TableCaption(), Item."No.", Item.Description);
        end;
    end;

    local procedure TestGLAccount(AccountNo: Code[20]; SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        GLAccount: Record "G/L Account";
        CustomerPostingGroup: Record "Customer Posting Group";
        DimensionManagement: Codeunit DimensionManagement;
        TableID: array[10] of Integer;
        No: array[10] of Code[20];
    begin
        GLAccount.Get(AccountNo);
        if GLAccount.Blocked then
            ErrorHelperAccount(Enum::"Correct Sales Inv. Error Type"::AccountBlocked, AccountNo, GLAccount.TableCaption(), '', '');
        TableID[1] := DATABASE::"G/L Account";
        No[1] := AccountNo;

        if not DimensionManagement.CheckDimValuePosting(TableID, No, SalesInvoiceHeader."Dimension Set ID") then
            ErrorHelperAccount(
                Enum::"Correct Sales Inv. Error Type"::DimErr, AccountNo, GLAccount.TableCaption(),
                SalesInvoiceHeader."Customer Posting Group", CustomerPostingGroup.TableCaption());
    end;

    local procedure TestIfInvoiceIsPaid(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTestIfInvoiceIsPaid(SalesInvoiceHeader, IsHandled);
        if IsHandled then
            exit;

        SalesInvoiceHeader.CalcFields("Amount Including VAT");
        SalesInvoiceHeader.CalcFields("Remaining Amount");
        if SalesInvoiceHeader."Amount Including VAT" <> SalesInvoiceHeader."Remaining Amount" then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::IsPaid, SalesInvoiceHeader);
    end;

    local procedure TestIfInvoiceIsCorrectedOnce(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        CancelledDocument: Record "Cancelled Document";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTestIfInvoiceIsCorrectedOnce(SalesInvoiceHeader, IsHandled);
        if IsHandled then
            exit;

        if CancelledDocument.FindSalesCancelledInvoice(SalesInvoiceHeader."No.") then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::IsCorrected, SalesInvoiceHeader);
    end;

    local procedure TestIfInvoiceIsNotCorrectiveDoc(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        CancelledDocument: Record "Cancelled Document";
    begin
        if CancelledDocument.FindSalesCorrectiveInvoice(SalesInvoiceHeader."No.") then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::IsCorrective, SalesInvoiceHeader);
    end;

    local procedure TestIfPostingIsAllowed(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        GenJnlCheckLine: Codeunit "Gen. Jnl.-Check Line";
    begin
        if GenJnlCheckLine.DateNotAllowed(SalesInvoiceHeader."Posting Date") then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::PostingNotAllowed, SalesInvoiceHeader);
    end;

    local procedure TestIfAnyFreeNumberSeries(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        GenJournalTemplate: Record "Gen. Journal Template";
        GeneralLedgerSetup: Record "General Ledger Setup";
        SalesReceivablesSetup: Record "Sales & Receivables Setup";
        PostingDate: Date;
        PostingNoSeries: Code[20];
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTestIfAnyFreeNumberSeries(SalesInvoiceHeader, IsHandled);
        if IsHandled then
            exit;

        PostingDate := WorkDate();
        SalesReceivablesSetup.Get();

        if not TryPeekNextNo(SalesReceivablesSetup."Credit Memo Nos.", PostingDate) then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::SerieNumCM, SalesInvoiceHeader);

        GeneralLedgerSetup.Get();
        if GeneralLedgerSetup."Journal Templ. Name Mandatory" then begin
            GenJournalTemplate.Get(SalesReceivablesSetup."S. Cr. Memo Template Name");
            PostingNoSeries := GenJournalTemplate."Posting No. Series";
        end else
            PostingNoSeries := SalesReceivablesSetup."Posted Credit Memo Nos.";
        if not TryPeekNextNo(PostingNoSeries, PostingDate) then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::SerieNumPostCM, SalesInvoiceHeader);

        if (not CancellingOnly) and (not TryPeekNextNo(SalesReceivablesSetup."Invoice Nos.", PostingDate)) then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::SerieNumInv, SalesInvoiceHeader);
    end;

    [TryFunction]
    local procedure TryPeekNextNo(NoSeriesCode: Code[20]; UsageDate: Date)
    var
        NoSeries: Codeunit "No. Series";
    begin
        if NoSeries.PeekNextNo(NoSeriesCode, UsageDate) = '' then
            Error('');
    end;

    local procedure TestIfJobPostingIsAllowed(SalesInvoiceNo: Code[20])
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
        Job: Record Job;
    begin
        SalesInvoiceLine.SetFilter("Document No.", SalesInvoiceNo);
        SalesInvoiceLine.SetFilter("Job No.", '<>%1', '');
        if SalesInvoiceLine.FindSet() then
            repeat
                Job.Get(SalesInvoiceLine."Job No.");
                Job.TestBlocked();
            until SalesInvoiceLine.Next() = 0;
    end;

    local procedure TestExternalDocument(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        SalesReceivablesSetup: Record "Sales & Receivables Setup";
    begin
        SalesReceivablesSetup.Get();
        if (SalesInvoiceHeader."External Document No." = '') and SalesReceivablesSetup."Ext. Doc. No. Mandatory" then
            ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::ExtDocErr, SalesInvoiceHeader);
    end;

    local procedure TestInventoryPostingClosed(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        InventoryPeriod: Record "Inventory Period";
        SalesInvoiceLine: Record "Sales Invoice Line";
        DocumentHasLineWithRestrictedType: Boolean;
    begin
        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
        SalesInvoiceLine.SetFilter(Quantity, '<>%1', 0);
        SalesInvoiceLine.SetFilter(Type, '%1|%2', SalesInvoiceLine.Type::Item, SalesInvoiceLine.Type::"Charge (Item)");
        DocumentHasLineWithRestrictedType := not SalesInvoiceLine.IsEmpty();

        if DocumentHasLineWithRestrictedType then begin
            InventoryPeriod.SetRange(Closed, true);
            InventoryPeriod.SetFilter("Ending Date", '>=%1', SalesInvoiceHeader."Posting Date");
            if not InventoryPeriod.IsEmpty() then
                ErrorHelperHeader(Enum::"Correct Sales Inv. Error Type"::InventoryPostClosed, SalesInvoiceHeader);
        end;
    end;

    local procedure TestSalesLineType(SalesInvoiceLine: Record "Sales Invoice Line")
    var
        IsHandled: Boolean;
    begin
        if SalesInvoiceLine.IsCancellationSupported() then
            exit;

        if (SalesInvoiceLine."Job No." <> '') and (SalesInvoiceLine.Type = SalesInvoiceLine.Type::Resource) then
            exit;

        IsHandled := false;
        OnAfterTestSalesLineType(SalesInvoiceLine, IsHandled);
        if not IsHandled then
            ErrorHelperLine(Enum::"Correct Sales Inv. Error Type"::WrongItemType, SalesInvoiceLine);
    end;

    local procedure TestGenPostingSetup(SalesInvoiceLine: Record "Sales Invoice Line")
    var
        GenPostingSetup: Record "General Posting Setup";
        Item: Record Item;
        IsHandled: Boolean;
    begin
        if SalesInvoiceLine."VAT Calculation Type" = SalesInvoiceLine."VAT Calculation Type"::"Sales Tax" then
            exit;

        GenPostingSetup.Get(SalesInvoiceLine."Gen. Bus. Posting Group", SalesInvoiceLine."Gen. Prod. Posting Group");
        if SalesInvoiceLine.Type <> SalesInvoiceLine.Type::"G/L Account" then begin
            GenPostingSetup.TestField("Sales Account");
            TestGLAccount(GenPostingSetup."Sales Account", SalesInvoiceLine);
            GenPostingSetup.TestField("Sales Credit Memo Account");
            TestGLAccount(GenPostingSetup."Sales Credit Memo Account", SalesInvoiceLine);
        end;
        if HasLineDiscountSetup(SalesInvoiceLine) then
            if GenPostingSetup."Sales Line Disc. Account" <> '' then
                TestGLAccount(GenPostingSetup."Sales Line Disc. Account", SalesInvoiceLine);

        IsHandled := false;
        OnTestGenPostingSetupOnBeforeTestTypeItem(SalesInvoiceLine, IsHandled);
        if not IsHandled then
            if SalesInvoiceLine.Type = SalesInvoiceLine.Type::Item then begin
                Item.Get(SalesInvoiceLine."No.");
                if Item.IsInventoriableType() then
                    TestGLAccount(GenPostingSetup.GetCOGSAccount(), SalesInvoiceLine);
            end;
    end;

    local procedure TestCustomerPostingGroup(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        CustomerPostingGroup: Record "Customer Posting Group";
    begin
        CustomerPostingGroup.Get(SalesInvoiceHeader."Customer Posting Group");
        CustomerPostingGroup.TestField("Receivables Account");
        TestGLAccount(CustomerPostingGroup."Receivables Account", SalesInvoiceHeader);
    end;

    local procedure TestVATPostingSetup(SalesInvoiceLine: Record "Sales Invoice Line")
    var
        VATPostingSetup: Record "VAT Posting Setup";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTestVATPostingSetup(SalesInvoiceLine, IsHandled);
        if IsHandled then
            exit;

        VATPostingSetup.Get(SalesInvoiceLine."VAT Bus. Posting Group", SalesInvoiceLine."VAT Prod. Posting Group");
        if VATPostingSetup."VAT Calculation Type" <> VATPostingSetup."VAT Calculation Type"::"Sales Tax" then begin
            VATPostingSetup.TestField("Sales VAT Account");
            TestGLAccount(VATPostingSetup."Sales VAT Account", SalesInvoiceLine);
        end;
    end;

    local procedure TestInventoryPostingSetup(SalesInvoiceLine: Record "Sales Invoice Line")
    var
        InventoryPostingSetup: Record "Inventory Posting Setup";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTestInventoryPostingSetup(SalesInvoiceLine, IsHandled);
        if IsHandled then
            exit;

        InventoryPostingSetup.Get(SalesInvoiceLine."Location Code", SalesInvoiceLine."Posting Group");
        InventoryPostingSetup.TestField("Inventory Account");
        TestGLAccount(InventoryPostingSetup."Inventory Account", SalesInvoiceLine);
    end;

    local procedure TestNoFixedAssetInSalesInvoice(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
    begin
        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
        TestNoFixedAssetInSalesLines(SalesInvoiceLine);
    end;

    local procedure TestNoFixedAssetInSalesLines(var SalesInvoiceLine: Record "Sales Invoice Line")
    var
        SalesInvLine: Record "Sales Invoice Line";
    begin
        SalesInvLine.Copy(SalesInvoiceLine);
        SalesInvLine.SetRange(Type, SalesInvLine.Type::"Fixed Asset");
        if not SalesInvLine.IsEmpty() then
            Error(FixedAssetNotPossibleToCreateCreditMemoErr);
    end;

    local procedure TestNotSalesPrepaymentlInvoice(SalesInvoiceHeader: Record "Sales Invoice Header")
    begin
        if SalesInvoiceHeader."Prepayment Invoice" then
            Error(CheckPrepaymentErr);
    end;

    local procedure IsCommentLine(SalesInvoiceLine: Record "Sales Invoice Line") Result: Boolean
    begin
        Result := (SalesInvoiceLine.Type = SalesInvoiceLine.Type::" ") or (SalesInvoiceLine."No." = '');
        OnAfterIsCommentLine(SalesInvoiceLine, Result);
    end;

    local procedure WasNotCancelled(InvNo: Code[20]): Boolean
    var
        SalesCrMemoHeader: Record "Sales Cr.Memo Header";
        IsHandled, Result : Boolean;
    begin
        IsHandled := false;
        Result := false;
        OnBeforeWasNotCancelled(InvNo, Result, IsHandled);
        if IsHandled then
            exit(Result);

        SalesCrMemoHeader.SetRange("Applies-to Doc. Type", SalesCrMemoHeader."Applies-to Doc. Type"::Invoice);
        SalesCrMemoHeader.SetRange("Applies-to Doc. No.", InvNo);
        exit(SalesCrMemoHeader.IsEmpty);
    end;

    local procedure UnapplyCostApplication(InvNo: Code[20])
    var
        TempItemLedgEntry: Record "Item Ledger Entry" temporary;
        TempItemApplicationEntry: Record "Item Application Entry" temporary;
        ItemJnlPostLine: Codeunit "Item Jnl.-Post Line";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeUnapplyCostApplication(InvNo, IsHandled);
        if IsHandled then
            exit;

        FindItemLedgEntries(TempItemLedgEntry, InvNo);
        if FindAppliedInbndEntries(TempItemApplicationEntry, TempItemLedgEntry) then begin
            repeat
                ItemJnlPostLine.UnApply(TempItemApplicationEntry);
            until TempItemApplicationEntry.Next() = 0;
            ItemJnlPostLine.RedoApplications();
        end;
    end;

    /// <summary>
    /// Finds all item ledger entries associated with the specified posted sales invoice.
    /// </summary>
    /// <param name="ItemLedgEntry">Returns the item ledger entries found for the invoice.</param>
    /// <param name="InvNo">Specifies the posted sales invoice number to search for.</param>
    procedure FindItemLedgEntries(var ItemLedgEntry: Record "Item Ledger Entry"; InvNo: Code[20])
    var
        SalesInvLine: Record "Sales Invoice Line";
    begin
        SalesInvLine.SetRange("Document No.", InvNo);
        SalesInvLine.SetRange(Type, SalesInvLine.Type::Item);
        if SalesInvLine.FindSet() then
            repeat
                SalesInvLine.GetItemLedgEntries(ItemLedgEntry, false);
            until SalesInvLine.Next() = 0;
    end;

    local procedure FindAppliedInbndEntries(var TempItemApplicationEntry: Record "Item Application Entry" temporary; var ItemLedgEntry: Record "Item Ledger Entry"): Boolean
    var
        ItemApplicationEntry: Record "Item Application Entry";
    begin
        TempItemApplicationEntry.Reset();
        TempItemApplicationEntry.DeleteAll();
        if ItemLedgEntry.FindSet() then
            repeat
                if ItemApplicationEntry.AppliedInbndEntryExists(ItemLedgEntry."Entry No.", true) then
                    repeat
                        TempItemApplicationEntry := ItemApplicationEntry;
                        if not TempItemApplicationEntry.Find() then
                            TempItemApplicationEntry.Insert();
                    until ItemApplicationEntry.Next() = 0;
            until ItemLedgEntry.Next() = 0;
        exit(TempItemApplicationEntry.FindSet());
    end;

    /// <summary>
    /// Raises an error message based on the header error type encountered during invoice correction validation.
    /// </summary>
    /// <param name="HeaderErrorType">Specifies the type of error that occurred.</param>
    /// <param name="SalesInvoiceHeader">Specifies the posted sales invoice that caused the error.</param>
    procedure ErrorHelperHeader(HeaderErrorType: Enum "Correct Sales Inv. Error Type"; SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        Customer: Record Customer;
    begin
        OnBeforeErrorHelperHeader(HeaderErrorType, SalesInvoiceHeader, CancellingOnly);

        if CancellingOnly then
            case HeaderErrorType of
                Enum::"Correct Sales Inv. Error Type"::IsPaid:
                    Error(PostedInvoiceIsPaidCancelErr);
                Enum::"Correct Sales Inv. Error Type"::CustomerBlocked:
                    begin
                        Customer.Get(SalesInvoiceHeader."Bill-to Customer No.");
                        Error(CustomerIsBlockedCancelErr, Customer.Name);
                    end;
                Enum::"Correct Sales Inv. Error Type"::IsCorrected:
                    Error(AlreadyCancelledErr);
                Enum::"Correct Sales Inv. Error Type"::IsCorrective:
                    Error(CancelCorrectiveDocErr);
                Enum::"Correct Sales Inv. Error Type"::SerieNumInv:
                    Error(NoFreeInvoiceNoSeriesCancelErr);
                Enum::"Correct Sales Inv. Error Type"::SerieNumCM:
                    Error(NoFreeCMSeriesCancelErr);
                Enum::"Correct Sales Inv. Error Type"::SerieNumPostCM:
                    Error(NoFreePostCMSeriesCancelErr);
                Enum::"Correct Sales Inv. Error Type"::PostingNotAllowed:
                    Error(PostingNotAllowedCancelErr);
                Enum::"Correct Sales Inv. Error Type"::ExtDocErr:
                    Error(ExternalDocCancelErr);
                Enum::"Correct Sales Inv. Error Type"::InventoryPostClosed:
                    Error(InventoryPostClosedCancelErr);
                Enum::"Correct Sales Inv. Error Type"::DimCombHeaderErr:
                    Error(InvalidDimCombHeaderCancelErr);
            end
        else
            case HeaderErrorType of
                Enum::"Correct Sales Inv. Error Type"::IsPaid:
                    Error(PostedInvoiceIsPaidCorrectErr);
                Enum::"Correct Sales Inv. Error Type"::CustomerBlocked:
                    begin
                        Customer.Get(SalesInvoiceHeader."Bill-to Customer No.");
                        Error(CustomerIsBlockedCorrectErr, Customer.Name);
                    end;
                Enum::"Correct Sales Inv. Error Type"::IsCorrected:
                    Error(AlreadyCorrectedErr);
                Enum::"Correct Sales Inv. Error Type"::IsCorrective:
                    Error(CorrCorrectiveDocErr);
                Enum::"Correct Sales Inv. Error Type"::SerieNumInv:
                    Error(NoFreeInvoiceNoSeriesCorrectErr);
                Enum::"Correct Sales Inv. Error Type"::SerieNumPostCM:
                    Error(NoFreePostCMSeriesCorrectErr);
                Enum::"Correct Sales Inv. Error Type"::SerieNumCM:
                    Error(NoFreeCMSeriesCorrectErr);
                Enum::"Correct Sales Inv. Error Type"::PostingNotAllowed:
                    Error(PostingNotAllowedCorrectErr);
                Enum::"Correct Sales Inv. Error Type"::ExtDocErr:
                    Error(ExternalDocCorrectErr);
                Enum::"Correct Sales Inv. Error Type"::InventoryPostClosed:
                    Error(InventoryPostClosedCorrectErr);
                Enum::"Correct Sales Inv. Error Type"::DimCombHeaderErr:
                    Error(InvalidDimCombHeaderCorrectErr);
            end;
    end;

    local procedure ErrorHelperLine(LineErrorType: Enum "Correct Sales Inv. Error Type"; SalesInvoiceLine: Record "Sales Invoice Line")
    var
        Item: Record Item;
    begin
        if CancellingOnly then
            case LineErrorType of
                Enum::"Correct Sales Inv. Error Type"::ItemBlocked:
                    begin
                        Item.Get(SalesInvoiceLine."No.");
                        Error(ItemIsBlockedCancelErr, Item."No.", Item.Description);
                    end;
                "Correct Sales Inv. Error Type"::ItemVariantBlocked:
                    begin
                        Item.SetLoadFields(Description);
                        Item.Get(SalesInvoiceLine."No.");
                        Error(ItemVariantIsBlockedCancelErr, SalesInvoiceLine."Variant Code", Item."No.", Item.Description);
                    end;
                Enum::"Correct Sales Inv. Error Type"::ItemIsReturned:
                    begin
                        Item.Get(SalesInvoiceLine."No.");
                        Error(ShippedQtyReturnedCancelErr, Item."No.", Item.Description);
                    end;
                Enum::"Correct Sales Inv. Error Type"::WrongItemType:
                    Error(LineTypeNotAllowedCancelErr, SalesInvoiceLine."No.", SalesInvoiceLine.Description, SalesInvoiceLine.Type);
                Enum::"Correct Sales Inv. Error Type"::LineFromJob:
                    Error(UsedInJobCancelErr, SalesInvoiceLine."No.", SalesInvoiceLine.Description);
                Enum::"Correct Sales Inv. Error Type"::DimCombErr:
                    Error(InvalidDimCombinationCancelErr, SalesInvoiceLine."No.", SalesInvoiceLine.Description);
            end
        else
            case LineErrorType of
                Enum::"Correct Sales Inv. Error Type"::ItemBlocked:
                    begin
                        Item.Get(SalesInvoiceLine."No.");
                        Error(ItemIsBlockedCorrectErr, Item."No.", Item.Description);
                    end;
                "Correct Sales Inv. Error Type"::ItemVariantBlocked:
                    begin
                        Item.SetLoadFields(Description);
                        Item.Get(SalesInvoiceLine."No.");
                        Error(ItemVariantIsBlockedCorrectErr, SalesInvoiceLine."Variant Code", Item."No.", Item.Description);
                    end;
                Enum::"Correct Sales Inv. Error Type"::ItemIsReturned:
                    begin
                        Item.Get(SalesInvoiceLine."No.");
                        Error(ShippedQtyReturnedCorrectErr, Item."No.", Item.Description);
                    end;
                Enum::"Correct Sales Inv. Error Type"::LineFromOrder:
                    Error(SalesLineFromOrderCorrectErr, SalesInvoiceLine."No.", SalesInvoiceLine.Description);
                Enum::"Correct Sales Inv. Error Type"::WrongItemType:
                    Error(LineTypeNotAllowedCorrectErr, SalesInvoiceLine."No.", SalesInvoiceLine.Description, SalesInvoiceLine.Type);
                Enum::"Correct Sales Inv. Error Type"::LineFromJob:
                    Error(UsedInJobCorrectErr, SalesInvoiceLine."No.", SalesInvoiceLine.Description);
                Enum::"Correct Sales Inv. Error Type"::DimCombErr:
                    Error(InvalidDimCombinationCorrectErr, SalesInvoiceLine."No.", SalesInvoiceLine.Description);
            end;
    end;

    local procedure ErrorHelperAccount(AccountErrorType: Enum "Correct Sales Inv. Error Type"; AccountNo: Code[20]; AccountCaption: Text; No: Code[20]; Name: Text)
    begin
        if CancellingOnly then
            case AccountErrorType of
                Enum::"Correct Sales Inv. Error Type"::AccountBlocked:
                    Error(AccountIsBlockedCancelErr, AccountCaption, AccountNo);
                Enum::"Correct Sales Inv. Error Type"::DimErr:
                    Error(InvalidDimCodeCancelErr, AccountCaption, AccountNo, No, Name);
            end
        else
            case AccountErrorType of
                Enum::"Correct Sales Inv. Error Type"::AccountBlocked:
                    Error(AccountIsBlockedCorrectErr, AccountCaption, AccountNo);
                Enum::"Correct Sales Inv. Error Type"::DimErr:
                    Error(InvalidDimCodeCorrectErr, AccountCaption, AccountNo, No, Name);
            end;
    end;

    local procedure HasLineDiscountSetup(SalesInvoiceLine: Record "Sales Invoice Line") Result: Boolean
    begin
        SalesReceivablesSetup.GetRecordOnce();
        Result := SalesReceivablesSetup."Discount Posting" in [SalesReceivablesSetup."Discount Posting"::"Line Discounts", SalesReceivablesSetup."Discount Posting"::"All Discounts"];
        if Result then
            Result := SalesInvoiceLine."Line Discount %" <> 0;
        OnHasLineDiscountSetup(SalesReceivablesSetup, Result);
    end;

    /// <summary>
    /// Updates the related sales order lines when a credit memo exists for the specified credit memo number.
    /// </summary>
    /// <param name="SalesCreditMemoNo">Specifies the posted sales credit memo number.</param>
    internal procedure UpdateSalesOrderLineIfExist(SalesCreditMemoNo: Code[20])
    var
        SalesCrMemoLine: Record "Sales Cr.Memo Line";
        SalesInvoiceLine: Record "Sales Invoice Line";
        TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary;
    begin
        SalesCrMemoLine.SetLoadFields("Document No.", "No.", "Appl.-from Item Entry", Quantity, "Variant Code");
        SalesCrMemoLine.SetRange("Document No.", SalesCreditMemoNo);
        SalesCrMemoLine.SetFilter("No.", '<>%1', '');
        SalesCrMemoLine.SetFilter(Quantity, '<>%1', 0);
        if SalesCrMemoLine.FindSet() then
            repeat
                Clear(SalesInvoiceLine);
                SalesCrMemoLine.GetSalesInvoiceLine(SalesInvoiceLine, TempUsedSalesInvoiceLine);
                if SalesInvoiceLine."Line No." <> 0 then begin
                    UpdateSalesOrderLinesFromCreditMemo(SalesInvoiceLine, SalesCrMemoLine);
                    TempUsedSalesInvoiceLine := SalesInvoiceLine;
                    if TempUsedSalesInvoiceLine.Insert() then;
                end;
            until SalesCrMemoLine.Next() = 0;
    end;

    local procedure UpdateSalesOrderLinesFromCreditMemo(SalesInvoiceLine: Record "Sales Invoice Line"; SalesCrMemoLine: Record "Sales Cr.Memo Line")
    var
        TempItemLedgerEntry: Record "Item Ledger Entry" temporary;
        SalesLine: Record "Sales Line";
        UndoPostingManagement: Codeunit "Undo Posting Management";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeUpdateSalesOrderLinesFromCreditMemo(SalesInvoiceLine, SalesCrMemoLine, IsHandled);
        if IsHandled then
            exit;

        if not SalesLine.Get(SalesLine."Document Type"::Order, SalesInvoiceLine."Order No.", SalesInvoiceLine."Order Line No.") then
            exit;

        if SalesLine.Type = SalesLine.Type::Item then
            SalesInvoiceLine.GetItemLedgEntries(TempItemLedgerEntry, false);

        UpdateSalesOrderLineInvoicedQuantity(SalesLine, SalesCrMemoLine.Quantity, SalesCrMemoLine."Quantity (Base)");
        UpdateSalesOrderLinePrepmtAmount(SalesInvoiceLine);

        if SalesLine.Type = SalesLine.Type::Item then begin
            if SalesLine."Qty. to Ship" = 0 then
                UpdateWhseRequest(Database::"Sales Line", SalesLine."Document Type".AsInteger(), SalesLine."Document No.", SalesLine."Location Code");

            TempItemLedgerEntry.SetFilter("Item Tracking", '<>%1', TempItemLedgerEntry."Item Tracking"::None.AsInteger());
            UndoPostingManagement.RevertPostedItemTracking(TempItemLedgerEntry, SalesInvoiceLine."Shipment Date", true);
        end;
    end;

    local procedure ResetIncomingDocumentForCancelledInvoice(SalesInvoiceHeaderNo: Code[20])
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
        SalesHeader: Record "Sales Header";
        IncomingDocument: Record "Incoming Document";
        IncomingDocumentAttachment: Record "Incoming Document Attachment";
        DummyRecordID: RecordID;
    begin
        SalesInvoiceLine.SetLoadFields("Order No.");
        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeaderNo);
        SalesInvoiceLine.SetFilter("Order No.", '<>%1', '');
        if not SalesInvoiceLine.FindFirst() then
            exit;

        if not SalesHeader.Get(SalesHeader."Document Type"::Order, SalesInvoiceLine."Order No.") then
            exit;

        if SalesHeader."Incoming Document Entry No." = 0 then
            exit;

        if not IncomingDocument.Get(SalesHeader."Incoming Document Entry No.") then
            exit;

        if not IncomingDocument.Posted then
            exit;

        IncomingDocument.Posted := false;
        IncomingDocument.Processed := false;
        IncomingDocument.Status := IncomingDocument.Status::Released;
        IncomingDocument."Posted Date-Time" := 0DT;
        IncomingDocument."Related Record ID" := DummyRecordID;
        IncomingDocument."Document No." := '';
        IncomingDocument."Document Type" := IncomingDocument."Document Type"::" ";
        IncomingDocument."Posting Date" := 0D;
        IncomingDocument.Modify(true);

        IncomingDocumentAttachment.SetRange("Incoming Document Entry No.", IncomingDocument."Entry No.");
        IncomingDocumentAttachment.ModifyAll("Document No.", '');
        IncomingDocumentAttachment.ModifyAll("Posting Date", 0D);
    end;

    local procedure UpdateSalesOrderLinesFromCancelledInvoice(SalesInvoiceHeaderNo: Code[20])
    var
        TempItemLedgerEntry: Record "Item Ledger Entry" temporary;
        SalesLine: Record "Sales Line";
        SalesInvoiceLine: Record "Sales Invoice Line";
        UndoPostingManagement: Codeunit "Undo Posting Management";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeUpdateSalesOrderLinesFromCancelledInvoice(SalesInvoiceHeaderNo, IsHandled);
        if IsHandled then
            exit;

        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeaderNo);
        if SalesInvoiceLine.FindSet() then
            repeat
                TempItemLedgerEntry.Reset();
                TempItemLedgerEntry.DeleteAll();
                SalesInvoiceLine.GetItemLedgEntries(TempItemLedgerEntry, false);
                if SalesLine.Get(SalesLine."Document Type"::Order, SalesInvoiceLine."Order No.", SalesInvoiceLine."Order Line No.") then begin
                    UpdateSalesOrderLineInvoicedQuantity(SalesLine, SalesInvoiceLine.Quantity, SalesInvoiceLine."Quantity (Base)");
                    UpdateSalesOrderLinePrepmtAmount(SalesInvoiceLine);
                    if SalesLine."Qty. to Ship" = 0 then
                        UpdateWhseRequest(Database::"Sales Line", SalesLine."Document Type".AsInteger(), SalesLine."Document No.", SalesLine."Location Code");
                    TempItemLedgerEntry.SetFilter("Item Tracking", '<>%1', TempItemLedgerEntry."Item Tracking"::None.AsInteger());
                    UndoPostingManagement.RevertPostedItemTracking(TempItemLedgerEntry, SalesInvoiceLine."Shipment Date", true);
                end;
            until SalesInvoiceLine.Next() = 0;
    end;

    local procedure UpdateWhseRequest(SourceType: Integer; SourceSubType: Integer; SourceNo: Code[20]; LocationCode: Code[10])
    var
        WarehouseRequest: Record "Warehouse Request";
    begin
        WarehouseRequest.SetCurrentKey("Source Type", "Source Subtype", "Source No.");
        WarehouseRequest.SetSourceFilter(SourceType, SourceSubType, SourceNo);
        WarehouseRequest.SetRange("Location Code", LocationCode);
        if WarehouseRequest.FindFirst() and WarehouseRequest."Completely Handled" then begin
            WarehouseRequest."Completely Handled" := false;
            WarehouseRequest.Modify();
        end;
    end;

    local procedure UpdateSalesOrderLineInvoicedQuantity(var SalesLine: Record "Sales Line"; CancelledQuantity: Decimal; CancelledQtyBase: Decimal)
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeUpdateSalesOrderLineInvoicedQuantity(SalesLine, CancelledQuantity, CancelledQtyBase, IsHandled);
        if IsHandled then
            exit;

        SalesLine."Quantity Invoiced" -= CancelledQuantity;
        SalesLine."Qty. Invoiced (Base)" -= CancelledQtyBase;
        SalesLine."Quantity Shipped" -= CancelledQuantity;
        SalesLine."Qty. Shipped (Base)" -= CancelledQtyBase;
        SalesLine.InitOutstanding();
        SalesLine.InitQtyToShip();
        SalesLine.UpdateWithWarehouseShip();
        SalesLine.Modify();

        OnAfterUpdateSalesOrderLineInvoicedQuantity(SalesLine, CancelledQuantity, CancelledQtyBase);
    end;

    local procedure TestWMSLocation(SalesInvoiceLine: Record "Sales Invoice Line")
    var
        Item: Record Item;
        Location: Record Location;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTestWMSLocation(SalesInvoiceLine, IsHandled);
        if IsHandled then
            exit;

        if SalesInvoiceLine.Type <> SalesInvoiceLine.Type::Item then
            exit;
        if SalesInvoiceLine.Quantity = 0 then
            exit;
        if not Item.Get(SalesInvoiceLine."No.") then
            exit;
        if not Item.IsInventoriableType() then
            exit;
        if not Location.Get(SalesInvoiceLine."Location Code") then
            exit;

        if Location."Directed Put-away and Pick" then
            Error(WMSLocationCancelCorrectErr, SalesInvoiceLine."Line No.");
    end;

    local procedure UpdateSalesOrderLinePrepmtAmount(SalesInvoiceLine: Record "Sales Invoice Line")
    var
        CurrExchRate: Record "Currency Exchange Rate";
        SalesHeader: Record "Sales Header";
        SalesLine: Record "Sales Line";
        Currency: Record Currency;
        IsHandled: Boolean;
    begin
        if not SalesLine.Get(
            SalesLine."Document Type"::Order,
            SalesInvoiceLine."Order No.",
            SalesInvoiceLine."Order Line No.")
        then
            exit;

        if (SalesLine."Prepayment Amount" = 0) or SalesInvoiceLine."Prepayment Line" then
            exit;

        SalesHeader.Get(SalesLine."Document Type"::Order, SalesLine."Document No.");
        Currency.Initialize(SalesHeader."Currency Code", true);

        IsHandled := false;
        OnUpdateSalesOrderLinePrepmtAmountOnBeforeUpdatePrepmtAmounts(SalesInvoiceLine, SalesLine, SalesHeader, Currency, IsHandled);
        if IsHandled then
            exit;

        if SalesHeader."Currency Code" <> '' then
            SalesLine.Validate(
                "Prepmt. Amount Inv. (LCY)",
                SalesLine."Prepmt. Amount Inv. (LCY)" +
                    Round(
                        CurrExchRate.ExchangeAmtFCYToLCY(
                            SalesHeader."Posting Date",
                            SalesHeader."Currency Code",
                            Round(
                                SalesInvoiceLine.Quantity * (SalesLine."Prepayment Amount" / SalesLine.Quantity),
                                Currency."Amount Rounding Precision"),
                            SalesHeader."Currency Factor"),
                        Currency."Amount Rounding Precision"))
        else
            SalesLine.Validate(
                "Prepmt. Amount Inv. (LCY)",
                SalesLine."Prepmt. Amount Inv. (LCY)" +
                    Round(
                        SalesInvoiceLine.Quantity * (SalesLine."Prepayment Amount" / SalesLine.Quantity),
                        Currency."Amount Rounding Precision"));

        if SalesHeader."Currency Code" <> '' then
            SalesLine.Validate(
                "Prepmt. VAT Amount Inv. (LCY)",
                SalesLine."Prepmt. VAT Amount Inv. (LCY)" +
                    Round(
                        CurrExchRate.ExchangeAmtFCYToLCY(
                            SalesHeader."Posting Date",
                            SalesHeader."Currency Code",
                            Round(
                                SalesInvoiceLine.Quantity * ((SalesLine."Prepmt. Amt. Incl. VAT" - SalesLine."Prepmt. VAT Base Amt.") / SalesLine.Quantity),
                                Currency."Amount Rounding Precision"),
                            SalesHeader."Currency Factor"),
                        Currency."Amount Rounding Precision"))
        else
            SalesLine.Validate(
                "Prepmt. VAT Amount Inv. (LCY)",
                SalesLine."Prepmt. VAT Amount Inv. (LCY)" +
                    Round(
                        SalesInvoiceLine.Quantity * ((SalesLine."Prepmt. Amt. Incl. VAT" - SalesLine."Prepmt. VAT Base Amt.") / SalesLine.Quantity),
                        Currency."Amount Rounding Precision"));

        SalesLine.Validate(
            "Prepmt Amt Deducted",
            SalesLine."Prepmt Amt Deducted" -
                Round(
                    SalesInvoiceLine.Quantity * (SalesLine."Prepmt. Line Amount" / SalesLine.Quantity),
                    Currency."Amount Rounding Precision"));

        SalesLine.Validate(
            "Prepmt Amt to Deduct",
            SalesLine."Prepmt Amt to Deduct" +
                Round(
                    SalesInvoiceLine.Quantity * (SalesLine."Prepmt. Line Amount" / SalesLine.Quantity),
                    Currency."Amount Rounding Precision"));

        SalesLine.Modify(true);
    end;

    local procedure TestIfDropShipmentDocument(SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
        TempSalesShipmentLine: Record "Sales Shipment Line" temporary;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTestIfDropShipmentDocument(SalesInvoiceHeader, IsHandled);
        if IsHandled then
            exit;

        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");
        SalesInvoiceLine.SetRange("Drop Shipment", true);
        if SalesInvoiceLine.FindFirst() then begin
            SalesInvoiceLine.GetSalesShptLines(TempSalesShipmentLine);
            Error(DropShipmentDocumentExistsErr, TempSalesShipmentLine."Purchase Order No.");
        end;
    end;

    /// <summary>
    /// Raised after creating a copy document from a posted sales invoice.
    /// </summary>
    /// <param name="SalesHeader">The newly created sales header.</param>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being copied from.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterCreateCopyDocument(var SalesHeader: Record "Sales Header"; var SalesInvoiceHeader: Record "Sales Invoice Header")
    begin
    end;

    /// <summary>
    /// Raised before testing whether to drop shipment document exists for the posted sales invoice.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being validated.</param>
    /// <param name="IsHandled">Set to true to skip default drop shipment document validation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTestIfDropShipmentDocument(var SalesInvoiceHeader: Record "Sales Invoice Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after determining whether a sales invoice line is a comment line.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being checked.</param>
    /// <param name="Result">The result indicating whether it is a comment line.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterIsCommentLine(SalesInvoiceLine: Record "Sales Invoice Line"; var Result: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after testing whether the invoice correction or cancellation is allowed.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being validated.</param>
    /// <param name="Cancelling">Indicates whether this is a cancellation operation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterTestCorrectInvoiceIsAllowed(var SalesInvoiceHeader: Record "Sales Invoice Header"; Cancelling: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after testing whether the sales invoice line type is allowed for correction.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being validated.</param>
    /// <param name="IsHandled">Set to true to skip the default line type error.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterTestSalesLineType(SalesInvoiceLine: Record "Sales Invoice Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after creating a corrective sales invoice from a posted invoice.
    /// </summary>
    /// <param name="SalesHeader">The newly created corrective sales invoice header.</param>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being corrected.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterCreateCorrSalesInvoice(var SalesHeader: Record "Sales Header"; var SalesInvoiceHeader: Record "Sales Invoice Header")
    begin
    end;

    /// <summary>
    /// Raised after creating a corrective sales credit memo from a posted invoice.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being corrected.</param>
    /// <param name="SalesHeader">The newly created corrective sales credit memo header.</param>
    /// <param name="CancellingOnly">Indicates whether only cancellation is being performed.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterCreateCorrectiveSalesCrMemo(SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"; var CancellingOnly: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after updating the invoiced quantity on a sales order line during cancellation.
    /// </summary>
    /// <param name="SalesLine">The updated sales order line.</param>
    /// <param name="CancelledQuantity">The cancelled quantity.</param>
    /// <param name="CancelledQtyBase">The cancelled quantity in base unit of measure.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterUpdateSalesOrderLineInvoicedQuantity(var SalesLine: Record "Sales Line"; CancelledQuantity: Decimal; CancelledQtyBase: Decimal)
    begin
    end;

    /// <summary>
    /// Raised before creating a corrective sales credit memo from a posted invoice.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice to correct.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCreateCorrectiveSalesCrMemo(var SalesInvoiceHeader: Record "Sales Invoice Header")
    begin
    end;

    /// <summary>
    /// Raised before creating a credit memo copy document from a posted invoice.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice to copy from.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCreateCreditMemoCopyDocument(var SalesInvoiceHeader: Record "Sales Invoice Header")
    begin
    end;

    /// <summary>
    /// Raised before confirming the creation of a corrective credit memo for an invoice posted from a sales order.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being corrected.</param>
    /// <param name="SalesHeader">The originating sales order the invoice was posted from.</param>
    /// <param name="ConfirmQuestion">The confirmation text to be shown. Subscribers can modify it.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateCreditMemoCopyDocumentOnBeforeConfirm(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"; var ConfirmQuestion: Text)
    begin
    end;

    /// <summary>
    /// Raised before inserting the sales header during invoice correction.
    /// </summary>
    /// <param name="SalesHeader">The sales header to be inserted.</param>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being corrected.</param>
    /// <param name="CancellingOnly">Indicates whether only cancellation is being performed.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeSalesHeaderInsert(var SalesHeader: Record "Sales Header"; var SalesInvoiceHeader: Record "Sales Invoice Header"; CancellingOnly: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before setting tracking information for invoice cancellation.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being cancelled.</param>
    /// <param name="IsHandled">Set to true to skip default tracking setup.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeSetTrackInfoForCancellation(var SalesInvoiceHeader: Record "Sales Invoice Header"; var IsHandled: Boolean)
    begin
    end;

#pragma warning disable AS0018
#pragma warning restore AS0018

    /// <summary>
    /// Raised before testing whether there are free number series for the correction.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being corrected.</param>
    /// <param name="IsHandled">Set to true to skip default number series validation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTestIfAnyFreeNumberSeries(var SalesInvoiceHeader: Record "Sales Invoice Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised to determine whether line discount setup is configured.
    /// </summary>
    /// <param name="SalesReceivablesSetup">The sales and receivables setup record.</param>
    /// <param name="Result">The result indicating whether line discount setup exists.</param>
    [IntegrationEvent(false, false)]
    local procedure OnHasLineDiscountSetup(SalesReceivablesSetup: Record "Sales & Receivables Setup"; var Result: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing inventory posting setup for the sales invoice line.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being validated.</param>
    /// <param name="IsHandled">Set to true to skip default inventory posting setup validation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTestInventoryPostingSetup(SalesInvoiceLine: Record "Sales Invoice Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before updating the invoiced quantity on a sales order line during cancellation.
    /// </summary>
    /// <param name="SalesLine">The sales order line to be updated.</param>
    /// <param name="CancelledQuantity">The cancelled quantity.</param>
    /// <param name="CancelledQtyBase">The cancelled quantity in base unit of measure.</param>
    /// <param name="IsHandled">Set to true to skip default quantity update.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeUpdateSalesOrderLineInvoicedQuantity(var SalesLine: Record "Sales Line"; CancelledQuantity: Decimal; CancelledQtyBase: Decimal; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before updating prepayment amounts on a sales order line during correction.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being processed.</param>
    /// <param name="SalesLine">The sales order line that would be updated.</param>
    /// <param name="SalesHeader">The related sales order header.</param>
    /// <param name="Currency">The initialized currency for the sales order.</param>
    /// <param name="IsHandled">Set to true to skip the default prepayment amount update.</param>
    [IntegrationEvent(false, false)]
    local procedure OnUpdateSalesOrderLinePrepmtAmountOnBeforeUpdatePrepmtAmounts(SalesInvoiceLine: Record "Sales Invoice Line"; var SalesLine: Record "Sales Line"; SalesHeader: Record "Sales Header"; Currency: Record Currency; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before opening the credit memo page during invoice correction.
    /// </summary>
    /// <param name="SalesHeader">The sales credit memo header to display.</param>
    /// <param name="IsHandled">Set to true to skip opening the default page.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateCreditMemoOnBeforePageRun(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before opening the posted credit memo page during invoice correction.
    /// </summary>
    /// <param name="SalesCrMemoHeader">The posted sales credit memo header to display.</param>
    /// <param name="IsHandled">Set to true to skip opening the default page.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateCreditMemoOnBeforePostedPageRun(var SalesCrMemoHeader: Record "Sales Cr.Memo Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before unapplying cost applications for the invoice.
    /// </summary>
    /// <param name="InvNo">The invoice number to unapply cost applications for.</param>
    /// <param name="IsHandled">Set to true to skip default cost unapplication.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeUnapplyCostApplication(InvNo: Code[20]; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after calculating the shipped quantity not returned during sales line validation.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being validated.</param>
    /// <param name="ShippedQtyNoReturned">The calculated shipped quantity not returned.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTestSalesLinesOnAfterCalcShippedQtyNoReturned(SalesInvoiceLine: Record "Sales Invoice Line"; var ShippedQtyNoReturned: Decimal)
    begin
    end;

    /// <summary>
    /// Raised after updating sales order lines from a cancelled invoice during the OnRun trigger.
    /// </summary>
    /// <param name="Rec">The posted sales invoice header being cancelled.</param>
    /// <param name="SalesHeader">The credit memo header created for cancellation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnOnRunOnAfterUpdateSalesOrderLinesFromCancelledInvoice(var Rec: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header")
    begin
    end;

    /// <summary>
    /// Raised before testing whether the invoice has been paid.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being validated.</param>
    /// <param name="IsHandled">Set to true to skip default payment validation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTestIfInvoiceIsPaid(var SalesInvoiceHeader: Record "Sales Invoice Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before opening the corrective credit memo page from a notification action.
    /// </summary>
    /// <param name="SalesHeader">The corrective sales credit memo header to display.</param>
    /// <param name="IsHandled">Set to true to skip opening the default page.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateCorrectiveCreditMemoOnBeforePageRun(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing VAT posting setup for the sales invoice line.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being validated.</param>
    /// <param name="IsHandled">Set to true to skip default VAT posting setup validation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTestVATPostingSetup(SalesInvoiceLine: Record "Sales Invoice Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before raising a header-level error during invoice correction validation.
    /// </summary>
    /// <param name="HeaderErrorType">The type of header error being raised.</param>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being validated.</param>
    /// <param name="CancellingOnly">Indicates whether only cancellation is being performed.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeErrorHelperHeader(HeaderErrorType: Enum "Correct Sales Inv. Error Type"; SalesInvoiceHeader: Record "Sales Invoice Header"; CancellingOnly: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing whether the invoice has already been corrected.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being validated.</param>
    /// <param name="IsHandled">Set to true to skip default correction validation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTestIfInvoiceIsCorrectedOnce(var SalesInvoiceHeader: Record "Sales Invoice Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before checking whether the invoice was not cancelled.
    /// </summary>
    /// <param name="InvNo">The invoice number to check.</param>
    /// <param name="Result">The result indicating whether the invoice was not cancelled.</param>
    /// <param name="IsHandled">Set to true to skip default cancellation check.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeWasNotCancelled(InvNo: Code[20]; var Result: Boolean; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before posting the corrective sales credit memo during the OnRun trigger.
    /// </summary>
    /// <param name="SalesInvoiceHeader">The posted sales invoice being corrected.</param>
    /// <param name="SalesHeader">The corrective credit memo header to be posted.</param>
    /// <param name="IsHandled">Set to true to skip default posting.</param>
    /// <param name="SuppressCommit">Indicates whether to suppress the commit operation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnRunOnBeforePostCorrectiveSalesCrMemo(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"; var IsHandled: Boolean; var SuppressCommit: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing warehouse management system location for the sales invoice line.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being validated.</param>
    /// <param name="IsHandled">Set to true to skip default WMS location validation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTestWMSLocation(var SalesInvoiceLine: Record "Sales Invoice Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before creating and processing project planning lines during invoice correction.
    /// </summary>
    /// <param name="SalesHeader">The sales header for which project planning lines are being created.</param>
    /// <param name="IsHandled">Set to true to skip default project planning line processing.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCreateAndProcessJobPlanningLines(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing general posting setup for item type lines.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being validated.</param>
    /// <param name="IsHandled">Set to true to skip default item type posting setup test.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTestGenPostingSetupOnBeforeTestTypeItem(SalesInvoiceLine: Record "Sales Invoice Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before updating sales order lines from a cancelled invoice.
    /// </summary>
    /// <param name="SalesInvoiceHeaderNo">The cancelled invoice number.</param>
    /// <param name="IsHandled">Set to true to skip default order line update.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeUpdateSalesOrderLinesFromCancelledInvoice(SalesInvoiceHeaderNo: Code[20]; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before updating sales order lines from a credit memo.
    /// </summary>
    /// <param name="SalesInvoiceLine">The sales invoice line being processed.</param>
    /// <param name="SalesCrMemoLine">The sales credit memo line that triggered the update.</param>
    /// <param name="IsHandled">Set to true to skip default order line update.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeUpdateSalesOrderLinesFromCreditMemo(var SalesInvoiceLine: Record "Sales Invoice Line"; var SalesCrMemoLine: Record "Sales Cr.Memo Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after initializing a project planning line from another project planning line during correction.
    /// </summary>
    /// <param name="ToJobPlanningLine">The newly created project planning line.</param>
    /// <param name="FromJobPlanningLine">The source project planning line.</param>
    /// <param name="SalesLine">The sales line being processed.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateJobPlanningLineOnAfterInitFromJobPlanningLine(var ToJobPlanningLine: Record "Job Planning Line"; FromJobPlanningLine: Record "Job Planning Line"; SalesLine: Record "Sales Line")
    begin
    end;
}