Codeunit 5477 Sales Invoice Aggregator

App
Base Application
Namespace
Microsoft.Integration.Entity
Versions
17-28

Procedures, 21Events, 2

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Integration/Entity/SalesInvoiceAggregator.Codeunit.al1251 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.Integration.Entity;

using Microsoft.API.Upgrade;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.SalesTax;
using Microsoft.Finance.VAT.Setup;
using Microsoft.Integration.Graph;
using Microsoft.Inventory.Item;
using Microsoft.Sales.Document;
using Microsoft.Sales.History;
using Microsoft.Sales.Posting;
using Microsoft.Sales.Receivables;
using Microsoft.Upgrade;
using Microsoft.Utilities;
using System.Reflection;
using System.Upgrade;

codeunit 5477 "Sales Invoice Aggregator"
{
    Permissions = tabledata "Sales Invoice Header" = rimd,
                  tabledata "Sales Invoice Entity Aggregate" = rimd;

    trigger OnRun()
    begin
    end;

    var
        GraphMgtGeneralTools: Codeunit "Graph Mgt - General Tools";
        SkipUpdateDiscounts: Boolean;

        DocumentIDNotSpecifiedForLinesErr: Label 'You must specify a document id to get the lines.';
        DocumentDoesNotExistErr: Label 'No document with the specified ID exists.';
        MultipleDocumentsFoundForIdErr: Label 'Multiple documents have been found for the specified criteria.';
        CannotModifyPostedInvioceErr: Label 'The invoice has been posted and can no longer be modified.';
        CannotInsertALineThatAlreadyExistsErr: Label 'You cannot insert a line because a line already exists.';
        CannotModifyALineThatDoesntExistErr: Label 'You cannot modify a line that does not exist.';
        CannotInsertPostedInvoiceErr: Label 'Invoices created through the API must be in Draft state.';
        CanOnlySetUOMForTypeItemErr: Label 'Unit of Measure can be set only for lines with type Item.';
        InvoiceIdIsNotSpecifiedErr: Label 'Invoice ID is not specified.';
        EntityIsNotFoundErr: Label 'Sales Invoice Entity is not found.';
        OrphanedRecordsFoundMsg: Label 'Found orphaned records.', Locked = true;
        AggregatorCategoryLbl: Label 'Sales Invoice Aggregator', Locked = true;

    [EventSubscriber(ObjectType::Table, Database::"Sales Header", 'OnAfterInsertEvent', '', false, false)]
    local procedure OnAfterInsertSalesHeader(var Rec: Record "Sales Header"; RunTrigger: Boolean)
    begin
        if not CheckValidRecord(Rec) or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        InsertOrModifyFromSalesHeader(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Header", 'OnAfterModifyEvent', '', false, false)]
    local procedure OnAfterModifySalesHeader(var Rec: Record "Sales Header"; var xRec: Record "Sales Header"; RunTrigger: Boolean)
    begin
        if not CheckValidRecord(Rec) or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if IsBackgroundPosting(Rec) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        InsertOrModifyFromSalesHeader(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Header", 'OnAfterDeleteEvent', '', false, false)]
    local procedure OnAfterDeleteSalesHeader(var Rec: Record "Sales Header"; RunTrigger: Boolean)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
    begin
        if not CheckValidRecord(Rec) or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        TransferRecordIDs(Rec);

        if not SalesInvoiceEntityAggregate.Get(Rec."No.", false) then
            exit;

        SalesInvoiceEntityAggregate.Delete();
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales - Calc Discount By Type", 'OnAfterResetRecalculateInvoiceDisc', '', false, false)]
    local procedure OnAfterResetRecalculateInvoiceDisc(var SalesHeader: Record "Sales Header")
    begin
        if not CheckValidRecord(SalesHeader) or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(SalesHeader.SystemId) then
            exit;

        InsertOrModifyFromSalesHeader(SalesHeader);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Line", 'OnAfterInsertEvent', '', false, false)]
    local procedure OnAfterInsertSalesLine(var Rec: Record "Sales Line"; RunTrigger: Boolean)
    begin
        if not CheckValidLineRecord(Rec) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        ModifyTotalsSalesLine(Rec, true);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Line", 'OnAfterModifyEvent', '', false, false)]
    local procedure OnAfterModifySalesLine(var Rec: Record "Sales Line"; var xRec: Record "Sales Line"; RunTrigger: Boolean)
    begin
        if not CheckValidLineRecord(Rec) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        ModifyTotalsSalesLine(Rec, Rec."Recalculate Invoice Disc.");
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Line", 'OnAfterDeleteEvent', '', false, false)]
    local procedure OnAfterDeleteSalesLine(var Rec: Record "Sales Line"; RunTrigger: Boolean)
    var
        SalesLine: Record "Sales Line";
    begin
        if not CheckValidLineRecord(Rec) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        SalesLine.SetRange("Document No.", Rec."Document No.");
        SalesLine.SetRange("Document Type", Rec."Document Type");
        SalesLine.SetRange("Recalculate Invoice Disc.", true);

        if SalesLine.FindFirst() then begin
            ModifyTotalsSalesLine(SalesLine, true);
            exit;
        end;

        SalesLine.SetRange("Recalculate Invoice Disc.");

        if not SalesLine.FindFirst() then
            BlankTotals(Rec."Document No.", false);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Invoice Header", 'OnAfterInsertEvent', '', false, false)]
    local procedure OnAfterInsertSalesInvoiceHeader(var Rec: Record "Sales Invoice Header"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        InsertOrModifyFromSalesInvoiceHeader(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Invoice Header", 'OnAfterModifyEvent', '', false, false)]
    local procedure OnAfterModifySalesInvoiceHeader(var Rec: Record "Sales Invoice Header"; var xRec: Record "Sales Invoice Header"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        InsertOrModifyFromSalesInvoiceHeader(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Invoice Header", 'OnAfterRenameEvent', '', false, false)]
    local procedure OnAfterRenameSalesInvoiceHeader(var Rec: Record "Sales Invoice Header"; var xRec: Record "Sales Invoice Header"; RunTrigger: Boolean)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        if not SalesInvoiceEntityAggregate.Get(xRec."No.", true) then
            exit;

        SalesInvoiceEntityAggregate.SetIsRenameAllowed(true);
        SalesInvoiceEntityAggregate.Rename(Rec."No.", true);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Sales Invoice Header", 'OnAfterDeleteEvent', '', false, false)]
    local procedure OnAfterDeleteSalesInvoiceHeader(var Rec: Record "Sales Invoice Header"; RunTrigger: Boolean)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(Rec.SystemId) then
            exit;

        if not SalesInvoiceEntityAggregate.Get(Rec."No.", true) then
            exit;

        SalesInvoiceEntityAggregate.Delete();
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Calc. Discount", 'OnAfterCalcSalesDiscount', '', false, false)]
    local procedure OnAfterCalculateSalesDiscountOnSalesHeader(var SalesHeader: Record "Sales Header")
    begin
        if not CheckValidRecord(SalesHeader) or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(SalesHeader.SystemId) then
            exit;

        InsertOrModifyFromSalesHeader(SalesHeader);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cust. Ledger Entry", 'OnAfterInsertEvent', '', false, false)]
    local procedure OnAfterInsertCustomerLedgerEntry(var Rec: Record "Cust. Ledger Entry"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCustLedgerEntry(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cust. Ledger Entry", 'OnAfterModifyEvent', '', false, false)]
    local procedure OnAfterModifyCustomerLedgerEntry(var Rec: Record "Cust. Ledger Entry"; var xRec: Record "Cust. Ledger Entry"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCustLedgerEntry(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cust. Ledger Entry", 'OnAfterRenameEvent', '', false, false)]
    local procedure OnAfterRenameCustomerLedgerEntry(var Rec: Record "Cust. Ledger Entry"; var xRec: Record "Cust. Ledger Entry"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCustLedgerEntry(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cust. Ledger Entry", 'OnAfterDeleteEvent', '', false, false)]
    local procedure OnAfterDeleteCustomerLedgerEntry(var Rec: Record "Cust. Ledger Entry"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCustLedgerEntry(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cancelled Document", 'OnAfterInsertEvent', '', false, false)]
    local procedure OnAfterInsertCancelledDocument(var Rec: Record "Cancelled Document"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCancelledDocument(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cancelled Document", 'OnAfterModifyEvent', '', false, false)]
    local procedure OnAfterModifyCancelledDocument(var Rec: Record "Cancelled Document"; var xRec: Record "Cancelled Document"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCancelledDocument(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cancelled Document", 'OnAfterRenameEvent', '', false, false)]
    local procedure OnAfterRenameCancelledDocument(var Rec: Record "Cancelled Document"; var xRec: Record "Cancelled Document"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCancelledDocument(xRec);
        SetStatusOptionFromCancelledDocument(Rec);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Cancelled Document", 'OnAfterDeleteEvent', '', false, false)]
    local procedure OnAfterDeleteCancelledDocument(var Rec: Record "Cancelled Document"; RunTrigger: Boolean)
    begin
        if Rec.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        SetStatusOptionFromCancelledDocument(Rec);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnBeforeSalesInvHeaderInsert', '', false, false)]
    local procedure OnBeforeSalesInvHeaderInsert(var SalesInvHeader: Record "Sales Invoice Header"; SalesHeader: Record "Sales Header"; CommitIsSuppressed: Boolean)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        ExistingSalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        IsRenameAllowed: Boolean;
    begin
        if SalesInvHeader.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if CheckUpdatesDisabled(SalesInvHeader.SystemId) then
            exit;

        if IsNullGuid(SalesHeader.SystemId) then begin
            Session.LogMessage('00006TK', InvoiceIdIsNotSpecifiedErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AggregatorCategoryLbl);
            exit;
        end;

        if SalesInvHeader."Pre-Assigned No." <> SalesHeader."No." then
            exit;

        if not SalesInvoiceEntityAggregate.Get(SalesHeader."No.", false) then begin
            Session.LogMessage('00006TL', EntityIsNotFoundErr, Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AggregatorCategoryLbl);
            exit;
        end;

        if SalesInvoiceEntityAggregate.Id <> SalesHeader.SystemId then
            exit;

        if ExistingSalesInvoiceEntityAggregate.Get(SalesInvHeader."No.", true) then begin
            Session.LogMessage('0000DPV', OrphanedRecordsFoundMsg, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AggregatorCategoryLbl);
            ExistingSalesInvoiceEntityAggregate.Delete();
        end;

        IsRenameAllowed := SalesInvoiceEntityAggregate.GetIsRenameAllowed();
        SalesInvoiceEntityAggregate.SetIsRenameAllowed(true);
        SalesInvoiceEntityAggregate.Rename(SalesInvHeader."No.", true);
        SalesInvoiceEntityAggregate.SetIsRenameAllowed(IsRenameAllowed);
        SalesInvHeader."Draft Invoice SystemId" := SalesHeader.SystemId;
    end;

    procedure PropagateOnInsert(var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var TempFieldBuffer: Record "Field Buffer" temporary)
    var
        SalesHeader: Record "Sales Header";
        TypeHelper: Codeunit "Type Helper";
        TargetRecordRef: RecordRef;
        DocTypeFieldRef: FieldRef;
    begin
        if SalesInvoiceEntityAggregate.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if SalesInvoiceEntityAggregate.Posted then
            Error(CannotInsertPostedInvoiceErr);

        TargetRecordRef.Open(DATABASE::"Sales Header");

        DocTypeFieldRef := TargetRecordRef.Field(SalesHeader.FieldNo("Document Type"));
        DocTypeFieldRef.Value(SalesHeader."Document Type"::Invoice);

        TypeHelper.TransferFieldsWithValidate(TempFieldBuffer, SalesInvoiceEntityAggregate, TargetRecordRef);

        // SetTable does not transfer globals, which will affect the logic in OnInsert trigger. We have to insert here and modify latter.
        TargetRecordRef.Insert(true);

        SalesHeader.Get(TargetRecordRef.RecordId);
        SalesHeader.CopySellToAddressToBillToAddress();
        SalesHeader.SetDefaultPaymentServices();
        SetShortcutDimensions(SalesHeader, SalesInvoiceEntityAggregate, TempFieldBuffer);
        SalesHeader.Modify(true);

        SalesInvoiceEntityAggregate."No." := SalesHeader."No.";
        SalesInvoiceEntityAggregate.Find();
    end;

    procedure PropagateOnModify(var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var TempFieldBuffer: Record "Field Buffer" temporary)
    begin
        if SalesInvoiceEntityAggregate.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if SalesInvoiceEntityAggregate.Posted then
            PropagateOnModifyToSalesInvoiceHeader(SalesInvoiceEntityAggregate, TempFieldBuffer)
        else
            PropagateOnModifyToSalesHeader(SalesInvoiceEntityAggregate, TempFieldBuffer);
    end;

    local procedure PropagateOnModifyToSalesInvoiceHeader(var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var TempFieldBuffer: Record "Field Buffer" temporary)
    var
        SalesInvocieHeader: Record "Sales Invoice Header";
        TypeHelper: Codeunit "Type Helper";
        TargetRecordRef: RecordRef;
    begin
        if not ChangeOnlyContainsWhitelistedFields(TempFieldBuffer) then
            Error(CannotModifyPostedInvioceErr);
        if SalesInvocieHeader.Get(SalesInvoiceEntityAggregate."No.") then begin
            TargetRecordRef.GetTable(SalesInvocieHeader);
            TypeHelper.TransferFieldsWithValidate(TempFieldBuffer, SalesInvoiceEntityAggregate, TargetRecordRef);
            TargetRecordRef.SetTable(SalesInvocieHeader);
            SalesInvocieHeader.Modify(true);
            CODEUNIT.Run(CODEUNIT::"Sales Inv. Header - Edit", SalesInvocieHeader);
        end
    end;

    local procedure PropagateOnModifyToSalesHeader(var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var TempFieldBuffer: Record "Field Buffer" temporary)
    var
        SalesHeader: Record "Sales Header";
        TypeHelper: Codeunit "Type Helper";
        TargetRecordRef: RecordRef;
        Exists: Boolean;
    begin
        Exists := SalesHeader.Get(SalesHeader."Document Type"::Invoice, SalesInvoiceEntityAggregate."No.");
        if Exists then
            TargetRecordRef.GetTable(SalesHeader)
        else
            TargetRecordRef.Open(DATABASE::"Sales Header");

        TypeHelper.TransferFieldsWithValidate(TempFieldBuffer, SalesInvoiceEntityAggregate, TargetRecordRef);

        TargetRecordRef.SetTable(SalesHeader);
        SalesHeader.CopySellToAddressToBillToAddress();

        if Exists then
            SalesHeader.Modify(true)
        else begin
            SalesHeader.SetDefaultPaymentServices();
            SalesHeader.Insert(true);
        end;
    end;

    procedure ChangeOnlyContainsWhitelistedFields(var TempFieldBuffer: Record "Field Buffer"): Boolean
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        WhiteListedFieldsFound, NonWhiteListedFieldsFound : Boolean;
    begin
        WhiteListedFieldsFound := false;
        NonWhiteListedFieldsFound := false;
        if TempFieldBuffer.findset() then
            repeat
                if TempFieldBuffer."Field ID" in [SalesInvoiceEntityAggregate.FieldNo("Due Date"), SalesInvoiceEntityAggregate.FieldNo("Promised Pay Date"), SalesInvoiceEntityAggregate.FieldNo("Dispute Status"), SalesInvoiceEntityAggregate.FieldNo("Dispute Status Id")] then
                    WhiteListedFieldsFound := true
                else
                    NonWhiteListedFieldsFound := true;
            until (TempFieldBuffer.next() = 0) or NonWhiteListedFieldsFound;
        if NonWhiteListedFieldsFound then
            exit(false);
        if WhiteListedFieldsFound then
            exit(true);
        exit(false);
    end;

    procedure PropagateOnDelete(var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesInvoiceHeader: Record "Sales Invoice Header";
        SalesHeader: Record "Sales Header";
        GraphMgtGeneralTools: Codeunit "Graph Mgt - General Tools";
    begin
        if SalesInvoiceEntityAggregate.IsTemporary or (not GraphMgtGeneralTools.IsApiEnabled()) then
            exit;

        if SalesInvoiceEntityAggregate.Posted then begin
            if not SalesInvoiceHeader.Get(SalesInvoiceEntityAggregate."No.") then begin
                GraphMgtGeneralTools.CleanAggregateWithoutParent(SalesInvoiceEntityAggregate);
                exit;
            end;

            if SalesInvoiceHeader."No. Printed" = 0 then
                SalesInvoiceHeader."No. Printed" := 1;
            SalesInvoiceHeader.Delete(true);
        end else begin
            if not SalesHeader.Get(SalesHeader."Document Type"::Invoice, SalesInvoiceEntityAggregate."No.") then begin
                GraphMgtGeneralTools.CleanAggregateWithoutParent(SalesInvoiceEntityAggregate);
                exit;
            end;

            SalesHeader.Delete(true);
        end;
    end;

    procedure UpdateAggregateTableRecords()
    var
        SalesHeader: Record "Sales Header";
        SalesInvoiceHeader: Record "Sales Invoice Header";
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        APIDataUpgrade: Codeunit "API Data Upgrade";
        RecordCount: Integer;
    begin
        SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Invoice);
        if SalesHeader.FindSet() then
            repeat
                InsertOrModifyFromSalesHeader(SalesHeader);
                APIDataUpgrade.CountRecordsAndCommit(RecordCount);
            until SalesHeader.Next() = 0;

        if SalesInvoiceHeader.FindSet() then
            repeat
                InsertOrModifyFromSalesInvoiceHeader(SalesInvoiceHeader);
                APIDataUpgrade.CountRecordsAndCommit(RecordCount);
            until SalesInvoiceHeader.Next() = 0;

        SalesInvoiceEntityAggregate.SetRange(Posted, false);
        if SalesInvoiceEntityAggregate.FindSet(true) then
            repeat
                if not SalesHeader.Get(SalesHeader."Document Type"::Invoice, SalesInvoiceEntityAggregate."No.") then begin
                    SalesInvoiceEntityAggregate.Delete(true);
                    APIDataUpgrade.CountRecordsAndCommit(RecordCount);
                end;
            until SalesInvoiceEntityAggregate.Next() = 0;

        SalesInvoiceEntityAggregate.SetRange(Posted, true);
        if SalesInvoiceEntityAggregate.FindSet(true) then
            repeat
                if not SalesInvoiceHeader.Get(SalesInvoiceEntityAggregate."No.") then begin
                    SalesInvoiceEntityAggregate.Delete(true);
                    APIDataUpgrade.CountRecordsAndCommit(RecordCount);
                end;
            until SalesInvoiceEntityAggregate.Next() = 0;
    end;

    procedure GetSalesInvoiceHeaderId(var SalesInvoiceHeader: Record "Sales Invoice Header"): Guid
    begin
        if (not IsNullGuid(SalesInvoiceHeader."Draft Invoice SystemId")) then
            exit(SalesInvoiceHeader."Draft Invoice SystemId");

        exit(SalesInvoiceHeader.SystemId);
    end;

    procedure GetSalesInvoiceHeaderFromId(Id: text; var SalesInvoiceHeader: Record "Sales Invoice Header"): Boolean
    begin
        SalesInvoiceHeader.SetFilter("Draft Invoice SystemId", Id);
        if SalesInvoiceHeader.FINDFIRST() then
            exit(true);

        SalesInvoiceHeader.SetRange("Draft Invoice SystemId");

        exit(SalesInvoiceHeader.GetBySystemId(Id));
    end;

    local procedure InsertOrModifyFromSalesHeader(var SalesHeader: Record "Sales Header")
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        RecordExists: Boolean;
    begin
        SalesInvoiceEntityAggregate.LockTable();
        RecordExists := SalesInvoiceEntityAggregate.Get(SalesHeader."No.", false);

        SalesInvoiceEntityAggregate.TransferFields(SalesHeader, true);
        SalesInvoiceEntityAggregate.Id := SalesHeader.SystemId;
        SalesInvoiceEntityAggregate.Posted := false;

        case SalesHeader.Status of
            SalesHeader.Status::"Pending Approval":
                SalesInvoiceEntityAggregate.Status := SalesInvoiceEntityAggregate.Status::"In Review";
            SalesHeader.Status::Released, SalesHeader.Status::"Pending Prepayment":
                SalesInvoiceEntityAggregate.Status := SalesInvoiceEntityAggregate.Status::Open;
            else
                SalesInvoiceEntityAggregate.Status := SalesInvoiceEntityAggregate.Status::Draft;
        end;

        AssignTotalsFromSalesHeader(SalesHeader, SalesInvoiceEntityAggregate);
        SalesInvoiceEntityAggregate.UpdateReferencedRecordIds();

        if RecordExists then
            SalesInvoiceEntityAggregate.Modify(true)
        else
            SalesInvoiceEntityAggregate.Insert(true);
    end;

    local procedure InsertOrModifyFromSalesInvoiceHeader(var SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        RecordExists: Boolean;
    begin
        SalesInvoiceEntityAggregate.LockTable();
        RecordExists := SalesInvoiceEntityAggregate.Get(SalesInvoiceHeader."No.", true);
        SalesInvoiceEntityAggregate.TransferFields(SalesInvoiceHeader, true);
        SalesInvoiceEntityAggregate.Id := GetSalesInvoiceHeaderId(SalesInvoiceHeader);

        SalesInvoiceEntityAggregate.Posted := true;
        SetStatusOptionFromSalesInvoiceHeader(SalesInvoiceHeader, SalesInvoiceEntityAggregate);
        AssignTotalsFromSalesInvoiceHeader(SalesInvoiceHeader, SalesInvoiceEntityAggregate);
        SalesInvoiceEntityAggregate.UpdateReferencedRecordIds();

        if RecordExists then
            SalesInvoiceEntityAggregate.Modify(true)
        else
            SalesInvoiceEntityAggregate.Insert(true);
    end;

    local procedure SetStatusOptionFromSalesInvoiceHeader(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeSetStatusOptionFromSalesInvoiceHeader(SalesInvoiceHeader, SalesInvoiceEntityAggregate, IsHandled);
        if IsHandled then
            exit;

        SalesInvoiceHeader.CalcFields(Cancelled, Closed, Corrective);
        if SalesInvoiceHeader.Cancelled then begin
            SalesInvoiceEntityAggregate.Status := SalesInvoiceEntityAggregate.Status::Canceled;
            exit;
        end;

        if SalesInvoiceHeader.Corrective then begin
            SalesInvoiceEntityAggregate.Status := SalesInvoiceEntityAggregate.Status::Corrective;
            exit;
        end;

        if SalesInvoiceHeader.Closed then begin
            SalesInvoiceEntityAggregate.Status := SalesInvoiceEntityAggregate.Status::Paid;
            exit;
        end;

        SalesInvoiceEntityAggregate.Status := SalesInvoiceEntityAggregate.Status::Open;
    end;

    local procedure SetStatusOptionFromCustLedgerEntry(var CustLedgerEntry: Record "Cust. Ledger Entry")
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        DummyGuid: Guid;
    begin
        if not GraphMgtGeneralTools.IsApiEnabled() then
            exit;

        if CheckUpdatesDisabled(DummyGuid) then
            exit;

        SalesInvoiceEntityAggregate.SetLoadFields("Cust. Ledger Entry No.", Posted, "No.", Status);
        SalesInvoiceEntityAggregate.SetRange("Cust. Ledger Entry No.", CustLedgerEntry."Entry No.");
        SalesInvoiceEntityAggregate.SetRange(Posted, true);

        if not SalesInvoiceEntityAggregate.FindSet(true) then
            exit;

        repeat
            UpdateStatusIfChanged(SalesInvoiceEntityAggregate);
        until SalesInvoiceEntityAggregate.Next() = 0;
    end;

    local procedure SetStatusOptionFromCancelledDocument(var CancelledDocument: Record "Cancelled Document")
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        DummyGuid: Guid;
    begin
        if not GraphMgtGeneralTools.IsApiEnabled() then
            exit;

        if CheckUpdatesDisabled(DummyGuid) then
            exit;

        case CancelledDocument."Source ID" of
            DATABASE::"Sales Invoice Header":
                if not SalesInvoiceEntityAggregate.Get(CancelledDocument."Cancelled Doc. No.", true) then
                    exit;
            DATABASE::"Sales Cr.Memo Header":
                if not SalesInvoiceEntityAggregate.Get(CancelledDocument."Cancelled By Doc. No.", true) then
                    exit;
            else
                exit;
        end;

        UpdateStatusIfChanged(SalesInvoiceEntityAggregate);
    end;

    procedure SetTaxGroupIdAndCode(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; TaxGroupCode: Code[20]; VATProductPostingGroupCode: Code[20]; VATIdentifier: Code[20])
    var
        TaxGroup: Record "Tax Group";
        VATProductPostingGroup: Record "VAT Product Posting Group";
        GeneralLedgerSetup: Record "General Ledger Setup";
    begin
        if GeneralLedgerSetup.UseVat() then begin
            SalesInvoiceLineAggregate."Tax Code" := VATIdentifier;
            if VATProductPostingGroup.Get(VATProductPostingGroupCode) then
                SalesInvoiceLineAggregate."Tax Id" := VATProductPostingGroup.SystemId;
        end else begin
            SalesInvoiceLineAggregate."Tax Code" := TaxGroupCode;
            if TaxGroup.Get(TaxGroupCode) then
                SalesInvoiceLineAggregate."Tax Id" := TaxGroup.SystemId;
        end;
    end;

    procedure SetItemVariantId(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; ItemNo: Code[20]; VariantCode: Code[20])
    var
        ItemVariant: Record "Item Variant";
    begin
        if ItemVariant.Get(ItemNo, VariantCode) then
            SalesInvoiceLineAggregate."Variant Id" := ItemVariant.SystemId;
    end;

    procedure UpdateUnitOfMeasure(var Item: Record Item; JSONUnitOfMeasureTxt: Text)
    var
        TempFieldSet: Record "Field" temporary;
        GraphCollectionMgtItem: Codeunit "Graph Collection Mgt - Item";
        ItemModified: Boolean;
    begin
        GraphCollectionMgtItem.UpdateOrCreateItemUnitOfMeasureFromSalesDocument(JSONUnitOfMeasureTxt, Item, TempFieldSet, ItemModified);

        if ItemModified then
            Item.Modify(true);
    end;

    local procedure UpdateStatusIfChanged(var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesInvoiceHeader: Record "Sales Invoice Header";
        CurrentStatus: Enum "Invoice Entity Aggregate Status";
    begin
        if not SalesInvoiceHeader.Get(SalesInvoiceEntityAggregate."No.") then
            exit;

        CurrentStatus := SalesInvoiceEntityAggregate.Status;

        SetStatusOptionFromSalesInvoiceHeader(SalesInvoiceHeader, SalesInvoiceEntityAggregate);
        if CurrentStatus <> SalesInvoiceEntityAggregate.Status then
            SalesInvoiceEntityAggregate.Modify(true);
    end;

    local procedure AssignTotalsFromSalesHeader(var SalesHeader: Record "Sales Header"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesLine: Record "Sales Line";
    begin
        SalesLine.SetRange("Document No.", SalesHeader."No.");
        SalesLine.SetRange("Document Type", SalesHeader."Document Type");

        if not SalesLine.FindFirst() then begin
            BlankTotals(SalesLine."Document No.", false);
            exit;
        end;

        AssignTotalsFromSalesLine(SalesLine, SalesInvoiceEntityAggregate, SalesHeader);
    end;

    local procedure AssignTotalsFromSalesInvoiceHeader(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
    begin
        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceHeader."No.");

        if not SalesInvoiceLine.FindFirst() then begin
            BlankTotals(SalesInvoiceLine."Document No.", true);
            exit;
        end;

        AssignTotalsFromSalesInvoiceLine(SalesInvoiceLine, SalesInvoiceEntityAggregate);
    end;

    local procedure AssignTotalsFromSalesLine(var SalesLine: Record "Sales Line"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var SalesHeader: Record "Sales Header")
    var
        TotalSalesLine: Record "Sales Line";
        DocumentTotals: Codeunit "Document Totals";
        VATAmount: Decimal;
    begin
        if SalesLine."VAT Calculation Type" = SalesLine."VAT Calculation Type"::"Sales Tax" then begin
            SalesInvoiceEntityAggregate."Discount Applied Before Tax" := true;
            SalesInvoiceEntityAggregate."Prices Including VAT" := false;
        end else
            SalesInvoiceEntityAggregate."Discount Applied Before Tax" := not SalesHeader."Prices Including VAT";

        DocumentTotals.CalculateSalesTotals(TotalSalesLine, VATAmount, SalesLine);

        SalesInvoiceEntityAggregate."Invoice Discount Amount" := TotalSalesLine."Inv. Discount Amount";
        SalesInvoiceEntityAggregate.Amount := TotalSalesLine.Amount;
        SalesInvoiceEntityAggregate."Total Tax Amount" := VATAmount;
        SalesInvoiceEntityAggregate."Subtotal Amount" := TotalSalesLine."Line Amount";
        SalesInvoiceEntityAggregate."Amount Including VAT" := TotalSalesLine."Amount Including VAT";
    end;

    local procedure AssignTotalsFromSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesInvoiceHeader: Record "Sales Invoice Header";
        TotalSalesInvoiceHeader: Record "Sales Invoice Header";
        TotalSalesInvoiceLine: Record "Sales Invoice Line";
        DocumentTotals: Codeunit "Document Totals";
        VATAmount: Decimal;
    begin
        if SalesInvoiceLine."VAT Calculation Type" = SalesInvoiceLine."VAT Calculation Type"::"Sales Tax" then
            SalesInvoiceEntityAggregate."Discount Applied Before Tax" := true
        else begin
            SalesInvoiceHeader.Get(SalesInvoiceLine."Document No.");
            SalesInvoiceEntityAggregate."Discount Applied Before Tax" := not SalesInvoiceHeader."Prices Including VAT";
        end;

        DocumentTotals.CalculatePostedSalesInvoiceTotals(TotalSalesInvoiceHeader, VATAmount, SalesInvoiceLine);

        SalesInvoiceEntityAggregate."Invoice Discount Amount" := TotalSalesInvoiceHeader."Invoice Discount Amount";
        SalesInvoiceEntityAggregate.Amount := TotalSalesInvoiceHeader.Amount;
        SalesInvoiceEntityAggregate."Total Tax Amount" := VATAmount;

        TotalSalesInvoiceLine.SetRange("Document No.", SalesInvoiceLine."Document No.");
        TotalSalesInvoiceLine.CalcSums("Line Amount");
        SalesInvoiceEntityAggregate."Subtotal Amount" := TotalSalesInvoiceLine."Line Amount";
        SalesInvoiceEntityAggregate."Amount Including VAT" := TotalSalesInvoiceHeader."Amount Including VAT";
    end;

    local procedure BlankTotals(DocumentNo: Code[20]; Posted: Boolean)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
    begin
        if not SalesInvoiceEntityAggregate.Get(DocumentNo, Posted) then
            exit;

        if CheckUpdatesDisabled(SalesInvoiceEntityAggregate.Id) then
            exit;

        SalesInvoiceEntityAggregate."Invoice Discount Amount" := 0;
        SalesInvoiceEntityAggregate."Total Tax Amount" := 0;
        SalesInvoiceEntityAggregate."Subtotal Amount" := 0;
        SalesInvoiceEntityAggregate.Amount := 0;
        SalesInvoiceEntityAggregate."Amount Including VAT" := 0;
        SalesInvoiceEntityAggregate.Modify();
    end;

    local procedure CheckValidRecord(var SalesHeader: Record "Sales Header"): Boolean

    begin
        if SalesHeader.IsTemporary then
            exit(false);

        if SalesHeader."Document Type" <> SalesHeader."Document Type"::Invoice then
            exit(false);

        exit(true);
    end;

    local procedure CheckUpdatesDisabled(RecSystemId: Guid): Boolean
    var
        DisableAggregateTableUpgrade: Codeunit "Disable Aggregate Table Update";
        UpdatesDisabled: Boolean;
    begin
        DisableAggregateTableUpgrade.OnGetAggregateTablesUpdateEnabled(UpdatesDisabled, Database::"Sales Invoice Entity Aggregate", RecSystemId);

        if UpdatesDisabled then
            exit(true);

        exit(false);
    end;

    local procedure ModifyTotalsSalesLine(var SalesLine: Record "Sales Line"; RecalculateInvoiceDisc: Boolean)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        SalesHeader: Record "Sales Header";
    begin
        if (not RecalculateInvoiceDisc) and (SalesLine.Type = SalesLine.Type::Item) then
            exit;

        if not SalesInvoiceEntityAggregate.Get(SalesLine."Document No.", false) then
            exit;

        if not SalesHeader.Get(SalesLine."Document Type", SalesLine."Document No.") then
            exit;

        if CheckUpdatesDisabled(SalesHeader.SystemId) then
            exit;

        AssignTotalsFromSalesLine(SalesLine, SalesInvoiceEntityAggregate, SalesHeader);
        SalesInvoiceEntityAggregate.Modify(true);
    end;

    local procedure TransferSalesInvoiceLineAggregateToSalesLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesLine: Record "Sales Line"; var TempFieldBuffer: Record "Field Buffer" temporary)
    var
        TypeHelper: Codeunit "Type Helper";
        SalesLineRecordRef: RecordRef;
    begin
        SalesLine."Document Type" := SalesLine."Document Type"::Invoice;

        SalesLineRecordRef.GetTable(SalesLine);

        TypeHelper.TransferFieldsWithValidate(TempFieldBuffer, SalesInvoiceLineAggregate, SalesLineRecordRef);
        SalesLineRecordRef.SetTable(SalesLine);
    end;

    local procedure TransferRecordIDs(var SalesHeader: Record "Sales Header")
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        SalesInvoiceHeader: Record "Sales Invoice Header";
        IsRenameAllowed: Boolean;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTransferRecordIDs(SalesHeader, IsHandled);
        if IsHandled then
            exit;

        if IsNullGuid(SalesHeader.SystemId) then
            exit;

        SalesInvoiceHeader.SetRange("Pre-Assigned No.", SalesHeader."No.");
        if not SalesInvoiceHeader.FindFirst() then
            exit;

        if SalesInvoiceHeader."Draft Invoice SystemId" = SalesHeader.SystemId then
            exit;

        if SalesInvoiceEntityAggregate.Get(SalesInvoiceHeader."No.", true) then
            SalesInvoiceEntityAggregate.Delete(true);

        if SalesInvoiceEntityAggregate.Get(SalesHeader."No.", false) then begin
            IsRenameAllowed := SalesInvoiceEntityAggregate.GetIsRenameAllowed();
            SalesInvoiceEntityAggregate.SetIsRenameAllowed(true);
            SalesInvoiceEntityAggregate.Rename(SalesInvoiceHeader."No.", true);
            SalesInvoiceEntityAggregate.SetIsRenameAllowed(IsRenameAllowed);
        end;

        SalesInvoiceHeader."Draft Invoice SystemId" := SalesHeader.SystemId;
        SalesInvoiceHeader.Modify(true);
    end;

    procedure RedistributeInvoiceDiscounts(var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesLine: Record "Sales Line";
    begin
        if SalesInvoiceEntityAggregate.Posted then
            exit;

        SalesLine.SetRange("Document Type", SalesLine."Document Type"::Invoice);
        SalesLine.SetRange("Document No.", SalesInvoiceEntityAggregate."No.");
        SalesLine.SetRange("Recalculate Invoice Disc.", true);
        if SalesLine.FindFirst() then
            CODEUNIT.Run(CODEUNIT::"Sales - Calc Discount By Type", SalesLine);

        SalesInvoiceEntityAggregate.Get(SalesInvoiceEntityAggregate."No.", SalesInvoiceEntityAggregate.Posted);
    end;

    procedure LoadLines(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; DocumentIdFilter: Text)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
    begin
        if DocumentIdFilter = '' then
            Error(DocumentIDNotSpecifiedForLinesErr);

        SalesInvoiceEntityAggregate.SetFilter(Id, DocumentIdFilter);
        if not SalesInvoiceEntityAggregate.FindFirst() then
            exit;

        if SalesInvoiceEntityAggregate.Posted then
            LoadSalesInvoiceLines(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate)
        else
            LoadSalesLines(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate);
    end;

    local procedure LoadSalesInvoiceLines(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesInvoiceLine: Record "Sales Invoice Line";
        SalesInvoiceAggregator: Codeunit "Sales Invoice Aggregator";
    begin
        SalesInvoiceLine.SetRange("Document No.", SalesInvoiceEntityAggregate."No.");

        if SalesInvoiceLine.FindSet(false) then
            repeat
                Clear(SalesInvoiceLineAggregate);
                SalesInvoiceLineAggregate.TransferFields(SalesInvoiceLine, true);
                SalesInvoiceLineAggregate."Document Id" := SalesInvoiceEntityAggregate.Id;
                SalesInvoiceLineAggregate.Id := GetIdFromDocumentIdAndSequence(SalesInvoiceEntityAggregate.Id, SalesInvoiceLine."Line No.");
                SalesInvoiceLineAggregate.SystemId := SalesInvoiceLine.SystemId;
                SetTaxGroupIdAndCode(
                  SalesInvoiceLineAggregate,
                  SalesInvoiceLine."Tax Group Code",
                  SalesInvoiceLine."VAT Prod. Posting Group",
                  SalesInvoiceLine."VAT Identifier");
                SalesInvoiceLineAggregate."VAT %" := SalesInvoiceLine."VAT %";
                SalesInvoiceLineAggregate."Tax Amount" := SalesInvoiceLine."Amount Including VAT" - SalesInvoiceLine."VAT Base Amount";
                SalesInvoiceLineAggregate."Currency Code" := SalesInvoiceLine.GetCurrencyCode();
                SalesInvoiceLineAggregate."Prices Including Tax" := SalesInvoiceEntityAggregate."Prices Including VAT";
                SalesInvoiceLineAggregate.SetDiscountValue();
                SalesInvoiceLineAggregate.UpdateReferencedRecordIds();
                UpdateLineAmountsFromSalesInvoiceLine(SalesInvoiceLineAggregate, SalesInvoiceLine);
                SalesInvoiceAggregator.SetItemVariantId(SalesInvoiceLineAggregate, SalesInvoiceLine."No.", SalesInvoiceLine."Variant Code");
                SalesInvoiceLineAggregate.Insert(true);
            until SalesInvoiceLine.Next() = 0;
    end;

    local procedure LoadSalesLines(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SalesLine: Record "Sales Line";
    begin
        SalesLine.SetRange("Document Type", SalesLine."Document Type"::Invoice);
        SalesLine.SetRange("Document No.", SalesInvoiceEntityAggregate."No.");

        if SalesLine.FindSet(false) then
            repeat
                TransferFromSalesLine(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate, SalesLine);
                SalesInvoiceLineAggregate.Insert(true);
            until SalesLine.Next() = 0;
    end;

    local procedure TransferFromSalesLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var SalesLine: Record "Sales Line")
    begin
        TransferFromSalesLineToAggregateLine(
          SalesInvoiceLineAggregate, SalesLine, SalesInvoiceEntityAggregate.Id, SalesInvoiceEntityAggregate."Prices Including VAT");
    end;

    procedure TransferFromSalesLineToAggregateLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesLine: Record "Sales Line"; DocumentId: Guid; PricesIncludingVAT: Boolean)
    var
        SalesInvoiceAggregator: Codeunit "Sales Invoice Aggregator";
    begin
        Clear(SalesInvoiceLineAggregate);
        SalesInvoiceLineAggregate.TransferFields(SalesLine, true);
        SalesInvoiceLineAggregate.Id := GetIdFromDocumentIdAndSequence(DocumentId, SalesLine."Line No.");
        SalesInvoiceLineAggregate.SystemId := SalesLine.SystemId;
        SalesInvoiceLineAggregate."Document Id" := DocumentId;
        SetTaxGroupIdAndCode(
          SalesInvoiceLineAggregate,
          SalesLine."Tax Group Code",
          SalesLine."VAT Prod. Posting Group",
          SalesLine."VAT Identifier");
        SalesInvoiceLineAggregate."VAT %" := SalesLine."VAT %";
        SalesInvoiceLineAggregate."Tax Amount" := SalesLine."Amount Including VAT" - SalesLine."VAT Base Amount";
        SalesInvoiceLineAggregate."Prices Including Tax" := PricesIncludingVAT;
        SalesInvoiceLineAggregate.SetDiscountValue();
        SalesInvoiceLineAggregate.UpdateReferencedRecordIds();
        UpdateLineAmountsFromSalesLine(SalesInvoiceLineAggregate, SalesLine);
        SalesInvoiceAggregator.SetItemVariantId(SalesInvoiceLineAggregate, SalesLine."No.", SalesLine."Variant Code");
    end;

    procedure PropagateInsertLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var TempFieldBuffer: Record "Field Buffer" temporary)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        SalesLine: Record "Sales Line";
        LastUsedSalesLine: Record "Sales Line";
    begin
        VerifyCRUDIsPossibleForLine(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate);

        SalesLine."Document Type" := SalesLine."Document Type"::Invoice;
        SalesLine."Document No." := SalesInvoiceEntityAggregate."No.";

        if SalesInvoiceLineAggregate."Line No." = 0 then begin
            LastUsedSalesLine.SetRange("Document No.", SalesInvoiceEntityAggregate."No.");
            LastUsedSalesLine.SetRange("Document Type", SalesLine."Document Type"::Invoice);
            if LastUsedSalesLine.FindLast() then
                SalesInvoiceLineAggregate."Line No." := LastUsedSalesLine."Line No." + 10000
            else
                SalesInvoiceLineAggregate."Line No." := 10000;

            SalesLine."Line No." := SalesInvoiceLineAggregate."Line No.";
        end else
            if SalesLine.Get(SalesLine."Document Type"::Invoice, SalesInvoiceEntityAggregate."No.", SalesInvoiceLineAggregate."Line No.") then
                Error(CannotInsertALineThatAlreadyExistsErr);

        TransferSalesInvoiceLineAggregateToSalesLine(SalesInvoiceLineAggregate, SalesLine, TempFieldBuffer);
        SalesLine.Insert(true);

        if not SkipUpdateDiscounts then
            RedistributeInvoiceDiscounts(SalesInvoiceEntityAggregate);

        SalesLine.Find();
        TransferFromSalesLine(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate, SalesLine);
    end;

    procedure PropagateModifyLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var TempFieldBuffer: Record "Field Buffer" temporary)
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        SalesLine: Record "Sales Line";
    begin
        VerifyCRUDIsPossibleForLine(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate);

        if not SalesLine.Get(SalesLine."Document Type"::Invoice, SalesInvoiceEntityAggregate."No.", SalesInvoiceLineAggregate."Line No.") then
            Error(CannotModifyALineThatDoesntExistErr);

        TransferSalesInvoiceLineAggregateToSalesLine(SalesInvoiceLineAggregate, SalesLine, TempFieldBuffer);

        SalesLine.Modify(true);

        if not SkipUpdateDiscounts then
            RedistributeInvoiceDiscounts(SalesInvoiceEntityAggregate);

        SalesLine.Find();
        TransferFromSalesLine(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate, SalesLine);
    end;

    procedure PropagateDeleteLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate")
    var
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        SalesLine: Record "Sales Line";
    begin
        VerifyCRUDIsPossibleForLine(SalesInvoiceLineAggregate, SalesInvoiceEntityAggregate);

        if SalesLine.Get(SalesLine."Document Type"::Invoice, SalesInvoiceEntityAggregate."No.", SalesInvoiceLineAggregate."Line No.") then begin
            SalesLine.Delete(true);
            if not SkipUpdateDiscounts then
                RedistributeInvoiceDiscounts(SalesInvoiceEntityAggregate);
        end;
    end;

    procedure PropagateMultipleLinesUpdate(var TempNewSalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate" temporary)
    var
        TempCurrentSalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate" temporary;
        SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        SalesLine: Record "Sales Line";
        TempAllFieldBuffer: Record "Field Buffer" temporary;
        GraphMgtSalesQuoteBuffer: Codeunit "Graph Mgt - Sales Quote Buffer";
    begin
        VerifyCRUDIsPossibleForLine(TempNewSalesInvoiceLineAggregate, SalesInvoiceEntityAggregate);
        GraphMgtSalesQuoteBuffer.GetFieldSetBufferWithAllFieldsSet(TempAllFieldBuffer);

        if not TempNewSalesInvoiceLineAggregate.FindFirst() then begin
            SalesLine.SetRange("Document Type", SalesLine."Document Type"::Invoice);
            SalesLine.SetRange("Document No.", SalesInvoiceEntityAggregate."No.");
            SalesLine.DeleteAll(true);
            exit;
        end;

        LoadLines(TempCurrentSalesInvoiceLineAggregate, SalesInvoiceEntityAggregate.Id);

        SkipUpdateDiscounts := true;

        // Remove deleted lines
        if TempCurrentSalesInvoiceLineAggregate.FindSet(true) then
            repeat
                if not TempNewSalesInvoiceLineAggregate.Get(SalesInvoiceEntityAggregate.Id, TempCurrentSalesInvoiceLineAggregate."Line No.") then
                    PropagateDeleteLine(TempCurrentSalesInvoiceLineAggregate);
            until TempCurrentSalesInvoiceLineAggregate.Next() = 0;

        // Update Lines
        TempNewSalesInvoiceLineAggregate.FindFirst();

        repeat
            if not TempCurrentSalesInvoiceLineAggregate.Get(
                 TempNewSalesInvoiceLineAggregate."Document Id", TempNewSalesInvoiceLineAggregate."Line No.")
            then
                PropagateInsertLine(TempNewSalesInvoiceLineAggregate, TempAllFieldBuffer)
            else
                PropagateModifyLine(TempNewSalesInvoiceLineAggregate, TempAllFieldBuffer);
        until TempNewSalesInvoiceLineAggregate.Next() = 0;

        SalesInvoiceEntityAggregate.Get(SalesInvoiceEntityAggregate."No.", SalesInvoiceEntityAggregate.Posted);
    end;

    local procedure VerifyCRUDIsPossibleForLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate")
    var
        SearchSalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate";
        DocumentIDFilter: Text;
    begin
        if IsNullGuid(SalesInvoiceLineAggregate."Document Id") then begin
            DocumentIDFilter := SalesInvoiceLineAggregate.GetFilter("Document Id");
            if DocumentIDFilter = '' then
                Error(DocumentIDNotSpecifiedForLinesErr);
            SalesInvoiceEntityAggregate.SetFilter(Id, DocumentIDFilter);

            if not SalesInvoiceEntityAggregate.FindFirst() then
                Error(DocumentDoesNotExistErr);
        end else begin
            SalesInvoiceEntityAggregate.SetRange(Id, SalesInvoiceLineAggregate."Document Id");
            if not SalesInvoiceEntityAggregate.FindFirst() then
                Error(DocumentDoesNotExistErr);
        end;

        SearchSalesInvoiceEntityAggregate.Copy(SalesInvoiceEntityAggregate);
        if SearchSalesInvoiceEntityAggregate.Next() <> 0 then
            Error(MultipleDocumentsFoundForIdErr);

        if SalesInvoiceEntityAggregate.Posted then
            Error(CannotModifyPostedInvioceErr);
    end;

    local procedure UpdateLineAmountsFromSalesLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesLine: Record "Sales Line")
    begin
        SalesInvoiceLineAggregate."Line Amount Excluding Tax" := SalesLine.GetLineAmountExclVAT();
        SalesInvoiceLineAggregate."Line Amount Including Tax" := SalesLine.GetLineAmountInclVAT();
        SalesInvoiceLineAggregate."Line Tax Amount" :=
          SalesInvoiceLineAggregate."Line Amount Including Tax" - SalesInvoiceLineAggregate."Line Amount Excluding Tax";
        UpdateInvoiceDiscountAmount(SalesInvoiceLineAggregate);
    end;

    local procedure UpdateLineAmountsFromSalesInvoiceLine(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate"; var SalesInvoiceLine: Record "Sales Invoice Line")
    begin
        SalesInvoiceLineAggregate."Line Amount Excluding Tax" := SalesInvoiceLine.GetLineAmountExclVAT();
        SalesInvoiceLineAggregate."Line Amount Including Tax" := SalesInvoiceLine.GetLineAmountInclVAT();
        SalesInvoiceLineAggregate."Line Tax Amount" :=
          SalesInvoiceLineAggregate."Line Amount Including Tax" - SalesInvoiceLineAggregate."Line Amount Excluding Tax";
        UpdateInvoiceDiscountAmount(SalesInvoiceLineAggregate);
    end;

    procedure UpdateInvoiceDiscountAmount(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate")
    begin
        if SalesInvoiceLineAggregate."Prices Including Tax" then
            SalesInvoiceLineAggregate."Inv. Discount Amount Excl. VAT" :=
              SalesInvoiceLineAggregate."Line Amount Excluding Tax" - SalesInvoiceLineAggregate.Amount
        else
            SalesInvoiceLineAggregate."Inv. Discount Amount Excl. VAT" := SalesInvoiceLineAggregate."Inv. Discount Amount";
    end;

    procedure VerifyCanUpdateUOM(var SalesInvoiceLineAggregate: Record "Sales Invoice Line Aggregate")
    begin
        if SalesInvoiceLineAggregate."API Type" <> SalesInvoiceLineAggregate."API Type"::Item then
            Error(CanOnlySetUOMForTypeItemErr);
    end;

    procedure FixInvoicesCreatedFromOrders()
    var
        SalesInvoiceHeader: Record "Sales Invoice Header";
        UpgradeTagDefinitions: Codeunit "Upgrade Tag Definitions";
        UpgradeTag: Codeunit "Upgrade Tag";
        NullGuid: Guid;
    begin
        SalesInvoiceHeader.SetFilter("Order No.", '<>''''');
        SalesInvoiceHeader.SetFilter("Draft Invoice SystemId", '<>%1', NullGuid);
        SalesInvoiceHeader.ModifyAll("Draft Invoice SystemId", NullGuid, true);
        if not (UpgradeTag.HasUpgradeTag(UpgradeTagDefinitions.GetFixAPISalesInvoicesCreatedFromOrders())) then
            UpgradeTag.SetUpgradeTag(UpgradeTagDefinitions.GetFixAPISalesInvoicesCreatedFromOrders());
    end;

    local procedure CheckValidLineRecord(var SalesLine: Record "Sales Line"): Boolean
    begin
        if SalesLine.IsTemporary then
            exit(false);

        if not GraphMgtGeneralTools.IsApiEnabled() then
            exit(false);

        if SalesLine."Document Type" <> SalesLine."Document Type"::Invoice then
            exit(false);

        exit(true);
    end;

    local procedure SetShortcutDimensions(var SalesHeader: Record "Sales Header"; SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var TempFieldBuffer: Record "Field Buffer" temporary)
    begin
        TempFieldBuffer.SetRange("Table ID", Database::"Sales Invoice Entity Aggregate");
        TempFieldBuffer.SetRange("Field ID", SalesInvoiceEntityAggregate.FieldNo("Shortcut Dimension 1 Code"));
        if not TempFieldBuffer.IsEmpty() then
            SalesHeader.Validate("Shortcut Dimension 1 Code", SalesInvoiceEntityAggregate."Shortcut Dimension 1 Code");
        TempFieldBuffer.SetRange("Field ID", SalesInvoiceEntityAggregate.FieldNo("Shortcut Dimension 2 Code"));
        if not TempFieldBuffer.IsEmpty() then
            SalesHeader.Validate("Shortcut Dimension 2 Code", SalesInvoiceEntityAggregate."Shortcut Dimension 2 Code");
    end;

    [Scope('OnPrem')]
    procedure GetIdFromDocumentIdAndSequence(DocumentId: Guid; Sequence: Integer): Text[50]
    var
        GraphMgtGeneralTools: Codeunit "Graph Mgt - General Tools";
    begin
        exit(LowerCase(GraphMgtGeneralTools.StripBrackets(Format(DocumentId))) + '-' + Format(Sequence));
    end;

    local procedure IsBackgroundPosting(var SalesHeader: Record "Sales Header"): Boolean
    begin
        if SalesHeader.IsTemporary then
            exit(false);

        exit(SalesHeader."Job Queue Status" in [SalesHeader."Job Queue Status"::"Scheduled for Posting", SalesHeader."Job Queue Status"::Posting]);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeSetStatusOptionFromSalesInvoiceHeader(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesInvoiceEntityAggregate: Record "Sales Invoice Entity Aggregate"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransferRecordIDs(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
    begin
    end;
}