Codeunit 99000832 Sales Line-Reserve

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

Procedures, 44Events, 52Obsolete, 10

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Sales/Document/SalesLineReserve.Codeunit.al2121 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.Document;

using Microsoft.Foundation.Navigate;
using Microsoft.Foundation.UOM;
using Microsoft.Inventory.Item;
using Microsoft.Inventory.Journal;
using Microsoft.Inventory.Ledger;
using Microsoft.Inventory.Location;
using Microsoft.Inventory.Planning;
using Microsoft.Inventory.Requisition;
using Microsoft.Inventory.Tracking;
using Microsoft.Purchases.Document;
using Microsoft.Sales.History;
using Microsoft.Warehouse.Document;

/// <summary>
/// Manages item reservations for sales lines including creation, modification, and verification.
/// </summary>
codeunit 99000832 "Sales Line-Reserve"
{
    Permissions = TableData "Reservation Entry" = rimd,
                  TableData "Planning Assignment" = rimd;

    trigger OnRun()
    begin
    end;

    var
        FromTrackingSpecification: Record "Tracking Specification";
        CreateReservEntry: Codeunit "Create Reserv. Entry";
        ReservationEngineMgt: Codeunit "Reservation Engine Mgt.";
        ReservationManagement: Codeunit "Reservation Management";
        ItemTrackingManagement: Codeunit "Item Tracking Management";
        UnitOfMeasureManagement: Codeunit "Unit of Measure Management";
        Blocked: Boolean;
        ApplySpecificItemTracking: Boolean;
        OverruleItemTracking: Boolean;
        DeleteItemTracking: Boolean;
        ItemTrkgAlreadyOverruled: Boolean;

        ReservedQtyTooLargeErr: Label 'Reserved quantity cannot be greater than %1.', Comment = '%1: not reserved quantity on Sales Line';
        ValueIsEmptyErr: Label 'must be filled in when a quantity is reserved';
        ValueNotEmptyErr: Label 'must not be filled in when a quantity is reserved';
        ValueChangedErr: Label 'must not be changed when a quantity is reserved';
        CodeunitInitErr: Label 'Codeunit is not initialized correctly.';
        SalesTxt: Label 'Sales';
        SummaryTypeTxt: Label '%1, %2', Locked = true;
        SourceDoc3Txt: Label '%1 %2 %3', Locked = true;

    /// <summary>
    /// Creates a reservation entry for a sales line with the specified tracking and quantity details.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to create the reservation for.</param>
    /// <param name="Description">Specifies the description for the reservation entry.</param>
    /// <param name="ExpectedReceiptDate">Specifies the expected receipt date for the reservation.</param>
    /// <param name="Quantity">Specifies the quantity to reserve.</param>
    /// <param name="QuantityBase">Specifies the quantity to reserve in base unit of measure.</param>
    /// <param name="ForReservationEntry">Specifies the reservation entry to link the new reservation to.</param>
    procedure CreateReservation(SalesLine: Record "Sales Line"; Description: Text[100]; ExpectedReceiptDate: Date; Quantity: Decimal; QuantityBase: Decimal; ForReservationEntry: Record "Reservation Entry")
    var
        ShipmentDate: Date;
        SignFactor: Integer;
        IsHandled: Boolean;
        DoCreateReservationEntry: Boolean;
    begin
        IsHandled := false;
        OnBeforeCreateReservation(SalesLine, IsHandled);
        if IsHandled then
            exit;

        if FromTrackingSpecification."Source Type" = 0 then
            Error(CodeunitInitErr);

        SalesLine.TestField(Type, SalesLine.Type::Item);
        SalesLine.TestField("No.");
        SalesLine.TestField("Shipment Date");
        SalesLine.CalcFields("Reserved Qty. (Base)");

        IsHandled := false;
        OnCreateReservationOnBeforeCheckReservedQty(SalesLine, IsHandled, QuantityBase);
        if IsHandled then
            exit;

        if Abs(SalesLine."Outstanding Qty. (Base)") < Abs(SalesLine."Reserved Qty. (Base)") + QuantityBase then
            Error(
              ReservedQtyTooLargeErr,
              Abs(SalesLine."Outstanding Qty. (Base)") - Abs(SalesLine."Reserved Qty. (Base)"));

        IsHandled := false;
        OnCreateReservationOnBeforeTestVariantCode(SalesLine, FromTrackingSpecification, IsHandled);
        if not IsHandled then
            SalesLine.TestField("Variant Code", FromTrackingSpecification."Variant Code");
        IsHandled := false;
        OnCreateReservationOnBeforeTestLocationCode(SalesLine, FromTrackingSpecification, IsHandled);
        if not IsHandled then
            SalesLine.TestField("Location Code", FromTrackingSpecification."Location Code");

        if SalesLine."Document Type" = SalesLine."Document Type"::"Return Order" then
            SignFactor := 1
        else
            SignFactor := -1;

        if QuantityBase * SignFactor < 0 then
            ShipmentDate := SalesLine."Shipment Date"
        else begin
            ShipmentDate := ExpectedReceiptDate;
            ExpectedReceiptDate := SalesLine."Shipment Date";
        end;

        IsHandled := false;
        DoCreateReservationEntry := true;
        OnCreateReservationOnBeforeCreateReservEntry(SalesLine, Quantity, QuantityBase, ForReservationEntry, IsHandled, FromTrackingSpecification, ExpectedReceiptDate, Description, ShipmentDate, DoCreateReservationEntry);
        if not IsHandled then begin
            CreateReservEntry.CreateReservEntryFor(
                Database::"Sales Line", SalesLine."Document Type".AsInteger(),
                SalesLine."Document No.", '', 0, SalesLine."Line No.", SalesLine."Qty. per Unit of Measure",
                Quantity, QuantityBase, ForReservationEntry);
            CreateReservEntry.CreateReservEntryFrom(FromTrackingSpecification);
        end;
        if DoCreateReservationEntry then
            CreateReservEntry.CreateReservEntry(
                SalesLine."No.", SalesLine."Variant Code", SalesLine."Location Code",
                Description, ExpectedReceiptDate, ShipmentDate, 0);

        FromTrackingSpecification."Source Type" := 0;

        OnAfterCreateReservation(SalesLine);
    end;

    /// <summary>
    /// Creates a binding reservation for a sales line without linking to an existing reservation entry.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to create the reservation for.</param>
    /// <param name="Description">Specifies the description for the reservation entry.</param>
    /// <param name="ExpectedReceiptDate">Specifies the expected receipt date for the reservation.</param>
    /// <param name="Quantity">Specifies the quantity to reserve.</param>
    /// <param name="QuantityBase">Specifies the quantity to reserve in base unit of measure.</param>
    procedure CreateBindingReservation(SalesLine: Record "Sales Line"; Description: Text[100]; ExpectedReceiptDate: Date; Quantity: Decimal; QuantityBase: Decimal)
    var
        DummyReservationEntry: Record "Reservation Entry";
    begin
        CreateReservation(SalesLine, Description, ExpectedReceiptDate, Quantity, QuantityBase, DummyReservationEntry);
    end;

    /// <summary>
    /// Sets the tracking specification to use when creating reservation entries.
    /// </summary>
    /// <param name="TrackingSpecification">Specifies the tracking specification to use as the source.</param>
    procedure CreateReservationSetFrom(TrackingSpecification: Record "Tracking Specification")
    begin
        FromTrackingSpecification := TrackingSpecification;
    end;

    /// <summary>
    /// Sets the binding type for reservation entries to be created.
    /// </summary>
    /// <param name="Binding">Specifies the reservation binding type.</param>
    procedure SetBinding(Binding: Enum "Reservation Binding")
    begin
        CreateReservEntry.SetBinding(Binding);
    end;

    /// <summary>
    /// Sets whether cancellation of reservation entries is disallowed.
    /// </summary>
    /// <param name="DisallowCancellation">Specifies whether cancellation is disallowed.</param>
    procedure SetDisallowCancellation(DisallowCancellation: Boolean)
    begin
        CreateReservEntry.SetDisallowCancellation(DisallowCancellation);
    end;

    /// <summary>
    /// Calculates the quantity available for reservation on a sales line.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to calculate reservation quantity for.</param>
    /// <param name="QtyToReserve">Returns the quantity available for reservation.</param>
    /// <param name="QtyToReserveBase">Returns the quantity available for reservation in base unit of measure.</param>
    procedure ReservQuantity(SalesLine: Record "Sales Line"; var QtyToReserve: Decimal; var QtyToReserveBase: Decimal)
    begin
        case SalesLine."Document Type" of
            SalesLine."Document Type"::Quote,
            SalesLine."Document Type"::Order,
            SalesLine."Document Type"::Invoice,
            SalesLine."Document Type"::"Blanket Order":
                begin
                    QtyToReserve := SalesLine."Outstanding Quantity";
                    QtyToReserveBase := SalesLine."Outstanding Qty. (Base)";
                end;
            SalesLine."Document Type"::"Return Order",
            SalesLine."Document Type"::"Credit Memo":
                begin
                    QtyToReserve := -SalesLine."Outstanding Quantity";
                    QtyToReserveBase := -SalesLine."Outstanding Qty. (Base)"
                end;
        end;

        OnAfterReservQuantity(SalesLine, QtyToReserve, QtyToReserveBase);
    end;

    /// <summary>
    /// Returns a caption describing the sales line for reservation purposes.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to get the caption for.</param>
    /// <returns>The caption text for the sales line.</returns>
    procedure Caption(SalesLine: Record "Sales Line") CaptionText: Text
    begin
        CaptionText := SalesLine.GetSourceCaption();
    end;

    /// <summary>
    /// Finds the last reservation entry for the specified sales line.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to find reservation entries for.</param>
    /// <param name="ReservationEntry">Returns the found reservation entry record.</param>
    /// <returns>True if a reservation entry was found, otherwise false.</returns>
    procedure FindReservEntry(SalesLine: Record "Sales Line"; var ReservationEntry: Record "Reservation Entry"): Boolean
    begin
        ReservationEntry.InitSortingAndFilters(false);
        SalesLine.SetReservationFilters(ReservationEntry);
        exit(ReservationEntry.FindLast());
    end;

    /// <summary>
    /// Calculates the quantity reserved from inventory for a sales line.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to calculate reserved quantity for.</param>
    /// <returns>The quantity reserved from inventory in base unit of measure.</returns>
    procedure GetReservedQtyFromInventory(SalesLine: Record "Sales Line"): Decimal
    var
        ReservationEntry: Record "Reservation Entry";
        QtyReservedFromItemLedger: Query "Qty. Reserved From Item Ledger";
    begin
        SalesLine.SetReservationEntry(ReservationEntry);
        QtyReservedFromItemLedger.SetSourceFilter(ReservationEntry);
        QtyReservedFromItemLedger.Open();
        if QtyReservedFromItemLedger.Read() then
            exit(QtyReservedFromItemLedger.Quantity__Base_);

        exit(0);
    end;

    /// <summary>
    /// Calculates the total quantity reserved from inventory for all lines on a sales header.
    /// </summary>
    /// <param name="SalesHeader">Specifies the sales header to calculate reserved quantity for.</param>
    /// <returns>The total quantity reserved from inventory in base unit of measure.</returns>
    procedure GetReservedQtyFromInventory(SalesHeader: Record "Sales Header"): Decimal
    var
        ReservationEntry: Record "Reservation Entry";
        QtyReservedFromItemLedger: Query "Qty. Reserved From Item Ledger";
    begin
        ReservationEntry.SetSource(Database::"Sales Line", SalesHeader."Document Type".AsInteger(), SalesHeader."No.", 0, '', 0);
        QtyReservedFromItemLedger.SetSourceFilter(ReservationEntry);
        QtyReservedFromItemLedger.Open();
        if QtyReservedFromItemLedger.Read() then
            exit(QtyReservedFromItemLedger.Quantity__Base_);

        exit(0);
    end;

    /// <summary>
    /// Checks whether a reservation entry exists for the specified sales line.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to check.</param>
    /// <returns>True if a reservation entry exists, otherwise false.</returns>
    procedure ReservEntryExist(SalesLine: Record "Sales Line"): Boolean
    begin
        exit(SalesLine.ReservEntryExist());
    end;

    /// <summary>
    /// Verifies and handles changes to a sales line that affect reservations.
    /// </summary>
    /// <param name="NewSalesLine">Specifies the new version of the sales line.</param>
    /// <param name="OldSalesLine">Specifies the original version of the sales line.</param>
    procedure VerifyChange(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line")
    var
        SalesLine: Record "Sales Line";
        ShowError: Boolean;
        HasError: Boolean;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeVerifyChange(NewSalesLine, OldSalesLine, IsHandled);
        if IsHandled then
            exit;

        if (NewSalesLine.Type <> NewSalesLine.Type::Item) and (OldSalesLine.Type <> OldSalesLine.Type::Item) then
            exit;
        if Blocked then
            exit;
        if NewSalesLine."Line No." = 0 then
            if not SalesLine.Get(
                 NewSalesLine."Document Type", NewSalesLine."Document No.", NewSalesLine."Line No.")
            then
                exit;

        NewSalesLine.CalcFields("Reserved Qty. (Base)");
        ShowError := NewSalesLine."Reserved Qty. (Base)" <> 0;

        HasError := TestSalesLineModification(OldSalesLine, NewSalesLine, ShowError);

        OnVerifyChangeOnBeforeHasError(NewSalesLine, OldSalesLine, HasError, ShowError);

        if HasError then
            ClearReservation(OldSalesLine, NewSalesLine);

        if HasError or (NewSalesLine."Shipment Date" <> OldSalesLine."Shipment Date") then begin
            AssignForPlanning(NewSalesLine);
            if (NewSalesLine."No." <> OldSalesLine."No.") or
               (NewSalesLine."Variant Code" <> OldSalesLine."Variant Code") or
               (NewSalesLine."Location Code" <> OldSalesLine."Location Code")
            then
                AssignForPlanning(OldSalesLine);
        end;
    end;

    /// <summary>
    /// Verifies and handles quantity changes on a sales line that affect reservations.
    /// </summary>
    /// <param name="NewSalesLine">Specifies the new version of the sales line.</param>
    /// <param name="OldSalesLine">Specifies the original version of the sales line.</param>
    procedure VerifyQuantity(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line")
    var
        SalesLine: Record "Sales Line";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeVerifyQuantity(NewSalesLine, IsHandled, OldSalesLine);
        if IsHandled then
            exit;

        if Blocked then
            exit;

        if NewSalesLine.Type <> NewSalesLine.Type::Item then
            exit;
        if NewSalesLine."Document Type" = OldSalesLine."Document Type" then
            if NewSalesLine."Line No." = OldSalesLine."Line No." then
                if NewSalesLine."Quantity (Base)" = OldSalesLine."Quantity (Base)" then
                    exit;
        if NewSalesLine."Line No." = 0 then
            if not SalesLine.Get(NewSalesLine."Document Type", NewSalesLine."Document No.", NewSalesLine."Line No.") then
                exit;
        ReservationManagement.SetReservSource(NewSalesLine);
        DeleteSalesReservEntries(NewSalesLine, OldSalesLine);
        ReservationManagement.ClearSurplus();
        ReservationManagement.AutoTrack(NewSalesLine."Outstanding Qty. (Base)");
        AssignForPlanning(NewSalesLine);
    end;

    /// <summary>
    /// Sets the quantity to ship on a sales line to the outstanding quantity. Called as an action from error handling.
    /// </summary>
    /// <param name="ErrorInfo">Specifies the error information containing the sales line record ID.</param>
    procedure SetSaleShipQty(ErrorInfo: ErrorInfo)
    var
        CurrSalesLine: Record "Sales Line";
    begin
        CurrSalesLine.Get(ErrorInfo.RecordId);
        CurrSalesLine.Validate("Qty. to Ship", CurrSalesLine."Outstanding Quantity");
        CurrSalesLine.Modify(true);
    end;

    /// <summary>
    /// Sets the quantity to invoice on a sales line to the maximum quantity. Called as an action from error handling.
    /// </summary>
    /// <param name="ErrorInfo">Specifies the error information containing the sales line record ID.</param>
    procedure SetSalesQtyInvoice(ErrorInfo: ErrorInfo)
    var
        CurrSalesLine: Record "Sales Line";
    begin
        CurrSalesLine.Get(ErrorInfo.RecordId);
        CurrSalesLine.Validate("Qty. to Invoice", CurrSalesLine.MaxQtyToInvoice());
        CurrSalesLine.Modify(true);
    end;

    local procedure DeleteSalesReservEntries(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line")
    var
        IsHandled: Boolean;
        ShouldDeleteAllReservationEntries: Boolean;
    begin
        IsHandled := false;
        OnBeforeDeleteSalesReservEntries(NewSalesLine, OldSalesLine, ReservationManagement, IsHandled);
        if IsHandled then
            exit;

        if NewSalesLine."Qty. per Unit of Measure" <> OldSalesLine."Qty. per Unit of Measure" then
            ReservationManagement.ModifyUnitOfMeasure();

        ShouldDeleteAllReservationEntries := NewSalesLine."Outstanding Qty. (Base)" * OldSalesLine."Outstanding Qty. (Base)" < 0;
        if not ShouldDeleteAllReservationEntries then
            ShouldDeleteAllReservationEntries := CheckQuantityReducedOnSalesLine(NewSalesLine, OldSalesLine);

        if ShouldDeleteAllReservationEntries then
            ReservationManagement.DeleteReservEntries(true, 0)
        else
            ReservationManagement.DeleteReservEntries(false, NewSalesLine."Outstanding Qty. (Base)");
    end;

    /// <summary>
    /// Transfers reservation entries from a sales line to an item journal line during posting.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to transfer reservations from.</param>
    /// <param name="ItemJournalLine">Specifies the item journal line to transfer reservations to.</param>
    /// <param name="TransferQty">Specifies the quantity to transfer.</param>
    /// <param name="CheckApplFromItemEntry">Specifies whether to check the applies-from item entry. Returns whether further checks are needed.</param>
    /// <param name="OnlyILEReservations">Specifies whether to transfer only item ledger entry reservations.</param>
    /// <returns>The remaining quantity that was not transferred.</returns>
    procedure TransferSalesLineToItemJnlLine(var SalesLine: Record "Sales Line"; var ItemJournalLine: Record "Item Journal Line"; TransferQty: Decimal; var CheckApplFromItemEntry: Boolean; OnlyILEReservations: Boolean): Decimal
    var
        OldReservationEntry: Record "Reservation Entry";
        OppositeReservationEntry: Record "Reservation Entry";
        ItemTrackingSetup: Record "Item Tracking Setup";
        NotFullyReserved: Boolean;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeTransferSalesLineToItemJnlLine(SalesLine, IsHandled, ItemJournalLine);
        if IsHandled then
            exit;

        if not FindReservEntry(SalesLine, OldReservationEntry) then
            exit(TransferQty);

        OldReservationEntry.LockTable(); //Lock the entry related to the sales line.
        OldReservationEntry.FindLast();

        // Handle Item Tracking on drop shipment:
        Clear(CreateReservEntry);

        if OverruleItemTracking then
            if ItemJournalLine.TrackingExists() then begin
                CreateReservEntry.SetNewTrackingFromItemJnlLine(ItemJournalLine);
                CreateReservEntry.SetOverruleItemTracking(not ItemTrkgAlreadyOverruled);
                // Try to match against Item Tracking on the sales order line:
                OldReservationEntry.SetTrackingFilterFromItemJnlLine(ItemJournalLine);
                if OldReservationEntry.IsEmpty() then
                    exit(TransferQty);
            end;


        IsHandled := false;
        OnTransferSalesLineToItemJnlLineOnBeforeItemJournalLineTest(SalesLine, IsHandled, ItemJournalLine, TransferQty);
        if not IsHandled then
            ItemJournalLine.TestItemFields(SalesLine."No.", SalesLine."Variant Code", SalesLine."Location Code");

        if TransferQty = 0 then
            exit;

        if ItemJournalLine."Invoiced Quantity" <> 0 then
            CreateReservEntry.SetUseQtyToInvoice(true);

        OnTransferSalesLineToItemJnlLineOnBeforeInitRecordSet(OldReservationEntry);
        if ReservationEngineMgt.InitRecordSet(OldReservationEntry) then begin
            repeat
                IsHandled := false;
                OnTransferSalesLineToItemJnlLineOnBeforeOldReservEntryTest(SalesLine, IsHandled, ItemJournalLine);
                if not IsHandled then
                    OldReservationEntry.TestItemFields(SalesLine."No.", SalesLine."Variant Code", SalesLine."Location Code");

                if ApplySpecificItemTracking and (ItemJournalLine."Applies-to Entry" <> 0) then begin
                    CreateReservEntry.SetItemLedgEntryNo(ItemJournalLine."Applies-to Entry");
                    CheckApplFromItemEntry := false;
                end;

                if ItemJournalLine."Assemble to Order" then begin
                    ItemTrackingSetup.CopyTrackingFromReservEntry(OldReservationEntry);
                    OldReservationEntry."Appl.-to Item Entry" := SalesLine.FindOpenATOEntry(ItemTrackingSetup);
                end;

                if CheckApplFromItemEntry then begin
                    if OldReservationEntry."Reservation Status" = OldReservationEntry."Reservation Status"::Reservation then begin
                        OppositeReservationEntry.Get(OldReservationEntry."Entry No.", not OldReservationEntry.Positive);
                        if OppositeReservationEntry."Source Type" <> Database::"Item Ledger Entry" then
                            NotFullyReserved := true;
                    end else
                        NotFullyReserved := true;

                    if OldReservationEntry."Item Tracking" <> OldReservationEntry."Item Tracking"::None then begin
                        IsHandled := false;
                        OnTransferSalesLineToItemJnlLineOnBeforeApplFromItemEntryTestField(SalesLine, OldReservationEntry, IsHandled, ItemJournalLine);
                        if not IsHandled then
                            OldReservationEntry.TestField("Appl.-from Item Entry");
                        CreateReservEntry.SetApplyFromEntryNo(OldReservationEntry."Appl.-from Item Entry");
                        CheckApplFromItemEntry := false;
                    end;
                end;

                IsHandled := false;
                OnTransferSalesLineToItemJnlLineOnBeforeTransferReservationEntry(OldReservationEntry, SalesLine, ItemJournalLine, IsHandled);
                if not IsHandled then
                    if not (ItemJournalLine."Assemble to Order" xor OldReservationEntry."Disallow Cancellation") then
                        if not VerifyPickedQtyReservToInventory(OldReservationEntry, SalesLine, TransferQty) then
                            if OnlyILEReservations and OppositeReservationEntry.Get(OldReservationEntry."Entry No.", not OldReservationEntry.Positive) then begin
                                if OppositeReservationEntry."Source Type" = Database::"Item Ledger Entry" then
                                    TransferReservationEntry(TransferQty, ItemJournalLine, OldReservationEntry);
                            end else
                                TransferReservationEntry(TransferQty, ItemJournalLine, OldReservationEntry);
            until (ReservationEngineMgt.NEXTRecord(OldReservationEntry) = 0) or (TransferQty = 0);
            CheckApplFromItemEntry := CheckApplFromItemEntry and NotFullyReserved;
        end;
        exit(TransferQty);
    end;

    local procedure TransferReservationEntry(var TransferQty: Decimal; var ItemJournalLine: Record "Item Journal Line"; var ReservationEntry: Record "Reservation Entry")
    begin
        if ItemJournalLine.IsSourceSales() then
            TransferQty :=
                CreateReservEntry.TransferReservEntry(
                    Database::"Item Journal Line", ItemJournalLine."Entry Type".AsInteger(), ItemJournalLine."Document No.",
                    ItemJournalLine."Journal Batch Name", 0, ItemJournalLine."Document Line No.",
                    ItemJournalLine."Qty. per Unit of Measure", ReservationEntry, TransferQty)
        else
            TransferQty :=
                CreateReservEntry.TransferReservEntry(
                    Database::"Item Journal Line", ItemJournalLine."Entry Type".AsInteger(), ItemJournalLine."Journal Template Name",
                    ItemJournalLine."Journal Batch Name", 0, ItemJournalLine."Line No.",
                    ItemJournalLine."Qty. per Unit of Measure", ReservationEntry, TransferQty);
    end;

    /// <summary>
    /// Transfers reservation entries from one sales line to another. Used when converting quotes or blanket orders to orders.
    /// </summary>
    /// <param name="OldSalesLine">Specifies the source sales line.</param>
    /// <param name="NewSalesLine">Specifies the destination sales line.</param>
    /// <param name="TransferQty">Specifies the quantity to transfer.</param>
    procedure TransferSaleLineToSalesLine(var OldSalesLine: Record "Sales Line"; var NewSalesLine: Record "Sales Line"; TransferQty: Decimal)
    var
        OldReservationEntry: Record "Reservation Entry";
        ReservationStatus: Enum "Reservation Status";
        IsHandled: Boolean;
    begin
        // Used for sales quote and blanket order when transferred to order
        IsHandled := false;
        OnBeforeTransferSaleLineToSalesLine(OldSalesLine, NewSalesLine, TransferQty, IsHandled);
        if IsHandled then
            exit;

        if not FindReservEntry(OldSalesLine, OldReservationEntry) then
            exit;

        OldReservationEntry.Lock();

        NewSalesLine.TestItemFields(OldSalesLine."No.", OldSalesLine."Variant Code", OldSalesLine."Location Code");

        for ReservationStatus := ReservationStatus::Reservation to ReservationStatus::Prospect do begin
            if TransferQty = 0 then
                exit;
            OldReservationEntry.SetRange("Reservation Status", ReservationStatus);
            if OldReservationEntry.FindSet() then
                repeat
                    OldReservationEntry.TestItemFields(OldSalesLine."No.", OldSalesLine."Variant Code", OldSalesLine."Location Code");
                    if (OldReservationEntry."Reservation Status" = OldReservationEntry."Reservation Status"::Prospect) and
                       (OldSalesLine."Document Type" in [OldSalesLine."Document Type"::Quote,
                                                         OldSalesLine."Document Type"::"Blanket Order"])
                    then
                        OldReservationEntry."Reservation Status" := OldReservationEntry."Reservation Status"::Surplus;

                    IsHandled := false;
                    OnTransferSaleLineToSalesLineOnBeforeCalcTransferQty(NewSalesLine, OldReservationEntry, IsHandled);
                    if not IsHandled then
                        TransferQty :=
                            CreateReservEntry.TransferReservEntry(
                                Database::"Sales Line",
                                NewSalesLine."Document Type".AsInteger(), NewSalesLine."Document No.", '', 0,
                                NewSalesLine."Line No.", NewSalesLine."Qty. per Unit of Measure", OldReservationEntry, TransferQty);

                until (OldReservationEntry.Next() = 0) or (TransferQty = 0);
        end;
    end;

    /// <summary>
    /// Prompts the user to confirm deletion of item tracking when deleting a sales line with reservations.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to check.</param>
    /// <returns>True if deletion is confirmed or no reservations exist, otherwise false.</returns>
    procedure DeleteLineConfirm(var SalesLine: Record "Sales Line"): Boolean
    begin
        if not SalesLine.ReservEntryExist() then
            exit(true);

        if GuiAllowed then
            Commit();

        ReservationManagement.SetReservSource(SalesLine);
        if ReservationManagement.DeleteItemTrackingConfirm() then
            DeleteItemTracking := true;

        exit(DeleteItemTracking);
    end;

    /// <summary>
    /// Deletes all reservation entries associated with a sales line.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to delete reservations for.</param>
    procedure DeleteLine(var SalesLine: Record "Sales Line")
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeDeleteLine(SalesLine, IsHandled);
        if not IsHandled then begin
            ReservationManagement.SetReservSource(SalesLine);
            if DeleteItemTracking then
                ReservationManagement.SetItemTrackingHandling(1); // Allow Deletion
            ReservationManagement.DeleteReservEntries(true, 0);
            DeleteInvoiceSpecFromLine(SalesLine);
            SalesLine.CalcFields("Reserved Qty. (Base)");
            AssignForPlanning(SalesLine);
        end;
    end;

    /// <summary>
    /// Creates or updates a planning assignment for a sales order line.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to create a planning assignment for.</param>
    procedure AssignForPlanning(var SalesLine: Record "Sales Line")
    var
        PlanningAssignment: Record "Planning Assignment";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeAssignForPlanning(SalesLine, IsHandled);
        if IsHandled then
            exit;

        if SalesLine."Document Type" <> SalesLine."Document Type"::Order then
            exit;
        if SalesLine.Type <> SalesLine.Type::Item then
            exit;
        if SalesLine."No." <> '' then
            PlanningAssignment.ChkAssignOne(SalesLine."No.", SalesLine."Variant Code", SalesLine."Location Code", SalesLine."Shipment Date");
    end;

    /// <summary>
    /// Opens the Item Tracking Lines page for a sales line.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to manage item tracking for.</param>
    procedure CallItemTracking(var SalesLine: Record "Sales Line")
    var
        TrackingSpecification: Record "Tracking Specification";
        ItemTrackingLines: Page "Item Tracking Lines";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeCallItemTracking(SalesLine, IsHandled);
        if IsHandled then
            exit;

        InitFromSalesLine(TrackingSpecification, SalesLine);
        if ((SalesLine."Document Type" = SalesLine."Document Type"::Invoice) and
            (SalesLine."Shipment No." <> '')) or
           ((SalesLine."Document Type" = SalesLine."Document Type"::"Credit Memo") and
            (SalesLine."Return Receipt No." <> ''))
        then
            ItemTrackingLines.SetRunMode(Enum::"Item Tracking Run Mode"::"Combined Ship/Rcpt");
        if SalesLine."Drop Shipment" then begin
            ItemTrackingLines.SetRunMode(Enum::"Item Tracking Run Mode"::"Drop Shipment");
            if SalesLine."Purchase Order No." <> '' then
                ItemTrackingLines.SetSecondSourceRowID(ItemTrackingManagement.ComposeRowID(
                    Database::"Purchase Line",
                    1, SalesLine."Purchase Order No.", '', 0, SalesLine."Purch. Order Line No."));
        end;
        ItemTrackingLines.SetSourceSpec(TrackingSpecification, SalesLine."Shipment Date");
        ItemTrackingLines.SetInbound(SalesLine.IsInbound());
        OnCallItemTrackingOnBeforeItemTrackingLinesRunModal(SalesLine, ItemTrackingLines);
        ItemTrackingLines.RunModal();
    end;

    /// <summary>
    /// Opens the Item Tracking Lines page for a sales line with a second source quantity array.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to manage item tracking for.</param>
    /// <param name="SecondSourceQuantityArray">Specifies the second source quantity array for tracking.</param>
    procedure CallItemTracking(var SalesLine: Record "Sales Line"; SecondSourceQuantityArray: array[3] of Decimal)
    begin
        CallItemTrackingSecondSource(SalesLine, SecondSourceQuantityArray, false);
    end;

    /// <summary>
    /// Opens the Item Tracking Lines page for a sales line with a second source and assembly-to-order option.
    /// </summary>
    /// <param name="SalesLine">Specifies the sales line to manage item tracking for.</param>
    /// <param name="SecondSourceQuantityArray">Specifies the second source quantity array for tracking.</param>
    /// <param name="AsmToOrder">Specifies whether this is an assemble-to-order scenario.</param>
    procedure CallItemTrackingSecondSource(var SalesLine: Record "Sales Line"; SecondSourceQuantityArray: array[3] of Decimal; AsmToOrder: Boolean)
    var
        TrackingSpecification: Record "Tracking Specification";
        ItemTrackingLines: Page "Item Tracking Lines";
        IsHandled: Boolean;
    begin
        if SecondSourceQuantityArray[1] = Database::"Warehouse Shipment Line" then
            ItemTrackingLines.SetSecondSourceID(Database::"Warehouse Shipment Line", AsmToOrder);

        InitFromSalesLine(TrackingSpecification, SalesLine);
        if SecondSourceQuantityArray[1] = Database::"Warehouse Shipment Line" then
            TrackingSpecification."Bin Code" := '';

        IsHandled := false;
        OnCallItemTrackingSecondSourceOnBeforeOpenItemTrackingLines(SalesLine, TrackingSpecification, SecondSourceQuantityArray, IsHandled);
        if not IsHandled then begin
            ItemTrackingLines.SetSourceSpec(TrackingSpecification, SalesLine."Shipment Date");
            ItemTrackingLines.SetSecondSourceQuantity(SecondSourceQuantityArray);
            OnCallItemTrackingSecondSourceOnBeforeItemTrackingLinesRun(SalesLine, ItemTrackingLines);
            ItemTrackingLines.RunModal();
        end;
    end;

    /// <summary>
    /// Retrieves the invoice specification tracking data for a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line to retrieve specification for.</param>
    /// <param name="TempInvoicingTrackingSpecification">Returns the temporary tracking specification records.</param>
    /// <returns>True if specifications were found.</returns>
    procedure RetrieveInvoiceSpecification(var SalesLine: Record "Sales Line"; var TempInvoicingTrackingSpecification: Record "Tracking Specification" temporary) OK: Boolean
    var
        SourceTrackingSpecification: Record "Tracking Specification";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeRetrieveInvoiceSpecification(SalesLine, OK, IsHandled, TempInvoicingTrackingSpecification);
        if IsHandled then
            exit;

        Clear(TempInvoicingTrackingSpecification);
        if SalesLine.Type <> SalesLine.Type::Item then
            exit;
        if ((SalesLine."Document Type" = SalesLine."Document Type"::Invoice) and
            (SalesLine."Shipment No." <> '')) or
           ((SalesLine."Document Type" = SalesLine."Document Type"::"Credit Memo") and
            (SalesLine."Return Receipt No." <> ''))
        then
            OK := RetrieveInvoiceSpecification2(SalesLine, TempInvoicingTrackingSpecification)
        else begin
            InitFromSalesLine(SourceTrackingSpecification, SalesLine);
            OK := ItemTrackingManagement.RetrieveInvoiceSpecification(SourceTrackingSpecification, TempInvoicingTrackingSpecification);
        end;
    end;

    /// <summary>
    /// Retrieves invoice specification for combined shipment or return scenarios.
    /// </summary>
    /// <param name="SalesLine">The sales line to retrieve specification for.</param>
    /// <param name="TempInvoicingTrackingSpecification">Returns the temporary tracking specification records.</param>
    /// <returns>True if specifications were found.</returns>
    procedure RetrieveInvoiceSpecification2(var SalesLine: Record "Sales Line"; var TempInvoicingTrackingSpecification: Record "Tracking Specification" temporary) OK: Boolean
    var
        TrackingSpecification: Record "Tracking Specification";
        ReservationEntry: Record "Reservation Entry";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeRetrieveInvoiceSpecification2(SalesLine, OK, IsHandled);
        if IsHandled then
            exit;

        // Used for combined shipment/return:
        if SalesLine.Type <> SalesLine.Type::Item then
            exit;
        if not FindReservEntry(SalesLine, ReservationEntry) then
            exit;
        ReservationEntry.FindSet();
        repeat
            ReservationEntry.TestField("Reservation Status", ReservationEntry."Reservation Status"::Prospect);
            ReservationEntry.TestField("Item Ledger Entry No.");
            TrackingSpecification.Get(ReservationEntry."Item Ledger Entry No.");
            TempInvoicingTrackingSpecification := TrackingSpecification;
            TempInvoicingTrackingSpecification."Qty. to Invoice (Base)" := ReservationEntry."Qty. to Invoice (Base)";
            TempInvoicingTrackingSpecification."Qty. to Invoice" :=
              Round(ReservationEntry."Qty. to Invoice (Base)" / ReservationEntry."Qty. per Unit of Measure", UnitOfMeasureManagement.QtyRndPrecision());
            TempInvoicingTrackingSpecification."Buffer Status" := TempInvoicingTrackingSpecification."Buffer Status"::MODIFY;
            OnRetrieveInvoiceSpecificationOnBeforeInsert(TempInvoicingTrackingSpecification, ReservationEntry);
            TempInvoicingTrackingSpecification.Insert();
            ReservationEntry.Delete();
        until ReservationEntry.Next() = 0;

        OK := TempInvoicingTrackingSpecification.FindFirst();
    end;

    /// <summary>
    /// Deletes invoice specifications for all lines in a sales header.
    /// </summary>
    /// <param name="SalesHeader">The sales header whose specifications to delete.</param>
    procedure DeleteInvoiceSpecFromHeader(var SalesHeader: Record "Sales Header")
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeDeleteInvoiceSpecFromHeader(SalesHeader, IsHandled);
        if IsHandled then
            exit;

        ItemTrackingManagement.DeleteInvoiceSpecFromHeader(
          Database::"Sales Line", SalesHeader."Document Type".AsInteger(), SalesHeader."No.");
    end;

    local procedure DeleteInvoiceSpecFromLine(var SalesLine: Record "Sales Line")
    var
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeDeleteInvoiceSpecFromLine(SalesLine, IsHandled);
        if IsHandled then
            exit;

        ItemTrackingManagement.DeleteInvoiceSpecFromLine(
          Database::"Sales Line", SalesLine."Document Type".AsInteger(), SalesLine."Document No.", SalesLine."Line No.");
    end;

    /// <summary>
    /// Updates item tracking quantities after posting a sales document.
    /// </summary>
    /// <param name="SalesHeader">The sales header that was posted.</param>
    procedure UpdateItemTrackingAfterPosting(SalesHeader: Record "Sales Header")
    var
        ReservationEntry: Record "Reservation Entry";
    begin
        // Used for updating Quantity to Handle and Quantity to Invoice after posting
        ReservationEntry.Reset();
        ReservationEntry.SetSourceFilter(Database::"Sales Line", SalesHeader."Document Type".AsInteger(), SalesHeader."No.", -1, true);
        ReservationEntry.SetSourceFilter('', 0);
        CreateReservEntry.UpdateItemTrackingAfterPosting(ReservationEntry);
    end;

    /// <summary>
    /// Sets whether to apply specific item tracking when reserving.
    /// </summary>
    /// <param name="ApplySpecific">Whether to apply specific item tracking.</param>
    procedure SetApplySpecificItemTracking(ApplySpecific: Boolean)
    begin
        ApplySpecificItemTracking := ApplySpecific;
    end;

    /// <summary>
    /// Sets whether to overrule existing item tracking during reservation.
    /// </summary>
    /// <param name="Overrule">Whether to overrule item tracking.</param>
    procedure SetOverruleItemTracking(Overrule: Boolean)
    begin
        OverruleItemTracking := Overrule;
    end;

    /// <summary>
    /// Sets the blocked state for reservation operations.
    /// </summary>
    /// <param name="SetBlocked">Whether to block reservation operations.</param>
    procedure Block(SetBlocked: Boolean)
    begin
        Blocked := SetBlocked;
    end;

    /// <summary>
    /// Sets whether item tracking has already been overruled.
    /// </summary>
    /// <param name="HasBeenOverruled">Whether item tracking has been overruled.</param>
    procedure SetItemTrkgAlreadyOverruled(HasBeenOverruled: Boolean)
    begin
        ItemTrkgAlreadyOverruled := HasBeenOverruled;
    end;

    local procedure VerifyPickedQtyReservToInventory(OldReservationEntry: Record "Reservation Entry"; SalesLine: Record "Sales Line"; TransferQty: Decimal): Boolean
    var
        WarehouseShipmentLine: Record "Warehouse Shipment Line";
        NewReservationEntry: Record "Reservation Entry";
    begin
        if not WarehouseShipmentLine.ReadPermission then
            exit(false);

        WarehouseShipmentLine.SetSourceFilter(Database::"Sales Line", SalesLine."Document Type".AsInteger(), SalesLine."Document No.", SalesLine."Line No.", false);
        WarehouseShipmentLine.SetRange(Status, WarehouseShipmentLine.Status::"Partially Picked");
        exit
            (WarehouseShipmentLine.FindFirst() and NewReservationEntry.Get(OldReservationEntry."Entry No.", not OldReservationEntry.Positive) and
            (OldReservationEntry."Reservation Status" = OldReservationEntry."Reservation Status"::Reservation) and
            (NewReservationEntry."Source Type" <> Database::"Item Ledger Entry") and (WarehouseShipmentLine."Qty. Picked (Base)" >= TransferQty));
    end;

    /// <summary>
    /// Binds a sales line to item tracking with order-to-order binding.
    /// </summary>
    /// <param name="SalesLine">The sales line to bind.</param>
    /// <param name="TrackingSpecification">The tracking specification to bind to.</param>
    /// <param name="Description">The reservation description.</param>
    /// <param name="ExpectedDate">The expected date for the reservation.</param>
    /// <param name="ReservQty">The quantity to reserve.</param>
    /// <param name="ReservQtyBase">The quantity to reserve in base unit of measure.</param>
    procedure BindToTracking(SalesLine: Record "Sales Line"; TrackingSpecification: Record "Tracking Specification"; Description: Text[100]; ExpectedDate: Date; ReservQty: Decimal; ReservQtyBase: Decimal)
    begin
        SetBinding("Reservation Binding"::"Order-to-Order");
        CreateReservationSetFrom(TrackingSpecification);
        CreateBindingReservation(SalesLine, Description, ExpectedDate, ReservQty, ReservQtyBase);
    end;






    local procedure CheckItemNo(var SalesLine: Record "Sales Line"): Boolean
    var
        ReservationEntry: Record "Reservation Entry";
    begin
        ReservationEntry.SetFilter("Item No.", '<>%1', SalesLine."No.");
        ReservationEntry.SetRange("Source Type", Database::"Sales Line");
        ReservationEntry.SetRange("Source Subtype", SalesLine."Document Type");
        ReservationEntry.SetRange("Source ID", SalesLine."Document No.");
        ReservationEntry.SetRange("Source Ref. No.", SalesLine."Line No.");
        exit(ReservationEntry.IsEmpty);
    end;

    local procedure ClearReservation(OldSalesLine: Record "Sales Line"; NewSalesLine: Record "Sales Line")
    var
        DummyReservationEntry: Record "Reservation Entry";
    begin
        if (NewSalesLine."No." <> OldSalesLine."No.") or FindReservEntry(NewSalesLine, DummyReservationEntry) then begin
            if (NewSalesLine."No." <> OldSalesLine."No.") or (NewSalesLine.Type <> OldSalesLine.Type) then begin
                ReservationManagement.SetReservSource(OldSalesLine);
                ReservationManagement.DeleteReservEntries(true, 0);
                ReservationManagement.SetReservSource(NewSalesLine);
            end else begin
                ReservationManagement.SetReservSource(NewSalesLine);
                ReservationManagement.DeleteReservEntries(true, 0);
            end;
            ReservationManagement.AutoTrack(NewSalesLine."Outstanding Qty. (Base)");
        end;
    end;

    local procedure TestSalesLineModification(OldSalesLine: Record "Sales Line"; NewSalesLine: Record "Sales Line"; ThrowError: Boolean) HasError: Boolean
    var
        IsHandled: Boolean;
    begin
        if (NewSalesLine."Shipment Date" = 0D) and (OldSalesLine."Shipment Date" <> 0D) then begin
            if ThrowError then
                NewSalesLine.FieldError("Shipment Date", ValueIsEmptyErr);
            HasError := true;
        end;

        OnTestSalesLineModificationOnBeforeTestJobNo(NewSalesLine, IsHandled);
        if not IsHandled then
            if NewSalesLine."Job No." <> '' then begin
                if ThrowError then
                    NewSalesLine.FieldError("Job No.", ValueNotEmptyErr);
                HasError := true;
            end;

        if NewSalesLine."Purchase Order No." <> '' then begin
            if ThrowError then
                NewSalesLine.FieldError("Purchase Order No.", ValueNotEmptyErr);
            HasError := NewSalesLine."Purchase Order No." <> OldSalesLine."Purchase Order No.";
        end;

        if NewSalesLine."Purch. Order Line No." <> 0 then begin
            if ThrowError then
                NewSalesLine.FieldError("Purch. Order Line No.", ValueNotEmptyErr);
            HasError := NewSalesLine."Purch. Order Line No." <> OldSalesLine."Purch. Order Line No.";
        end;

        if NewSalesLine."Drop Shipment" and not OldSalesLine."Drop Shipment" then begin
            if ThrowError then
                NewSalesLine.FieldError("Drop Shipment", ValueNotEmptyErr);
            HasError := true;
        end;

        if NewSalesLine."Special Order" and not OldSalesLine."Special Order" then begin
            if ThrowError then
                NewSalesLine.FieldError("Special Order", ValueNotEmptyErr);
            HasError := true;
        end;

        if (NewSalesLine."No." <> OldSalesLine."No.") and not CheckItemNo(NewSalesLine) then begin
            if ThrowError then
                NewSalesLine.FieldError("No.", ValueChangedErr);
            HasError := true;
        end;

        IsHandled := false;
        OnTestSalesLineModificationOnBeforeTestVariantCode(NewSalesLine, OldSalesLine, IsHandled, HasError, ThrowError);
        if not IsHandled then
            if NewSalesLine."Variant Code" <> OldSalesLine."Variant Code" then begin
                if ThrowError then
                    NewSalesLine.FieldError("Variant Code", ValueChangedErr);
                HasError := true;
            end;

        IsHandled := false;
        OnTestSalesLineModificationOnBeforeTestLocationCode(NewSalesLine, OldSalesLine, IsHandled, HasError);
        if not IsHandled then
            if NewSalesLine."Location Code" <> OldSalesLine."Location Code" then begin
                if ThrowError then
                    NewSalesLine.FieldError("Location Code", ValueChangedErr);
                HasError := true;
            end;

        IsHandled := false;
        OnTestSalesLineModificationOnBeforeTestBinCode(NewSalesLine, OldSalesLine, IsHandled);
        if not IsHandled then
            if (OldSalesLine.Type = OldSalesLine.Type::Item) and (NewSalesLine.Type = NewSalesLine.Type::Item) then
                if (NewSalesLine."Bin Code" <> OldSalesLine."Bin Code") and
                (not ReservationManagement.CalcIsAvailTrackedQtyInBin(
                    NewSalesLine."No.", NewSalesLine."Bin Code",
                    NewSalesLine."Location Code", NewSalesLine."Variant Code",
                    Database::"Sales Line", NewSalesLine."Document Type".AsInteger(),
                    NewSalesLine."Document No.", '', 0, NewSalesLine."Line No."))
                then begin
                    if ThrowError then
                        NewSalesLine.FieldError("Bin Code", ValueChangedErr);
                    HasError := true;
                end;

        if NewSalesLine."Line No." <> OldSalesLine."Line No." then
            HasError := true;

        if NewSalesLine.Type <> OldSalesLine.Type then
            HasError := true;
    end;

    /// <summary>
    /// Deletes a sales line along with its item tracking entries.
    /// </summary>
    /// <param name="SalesLine">The sales line to delete.</param>
    procedure DeleteLineWithItemTracking(var SalesLine: Record "Sales Line")
    begin
        DeleteItemTracking := true;
        DeleteLine(SalesLine);
    end;

    /// <summary>
    /// Sets whether to delete item tracking when deleting lines.
    /// </summary>
    /// <param name="NewDeleteItemTracking">Whether to delete item tracking.</param>
    procedure SetDeleteItemTracking(NewDeleteItemTracking: Boolean)
    begin
        DeleteItemTracking := NewDeleteItemTracking
    end;

    /// <summary>
    /// Copies reservation entries from a sales line to a temporary table.
    /// </summary>
    /// <param name="TempReservationEntry">The temporary table to copy entries to.</param>
    /// <param name="OldSalesLine">The sales line whose reservations to copy.</param>
    procedure CopyReservEntryToTemp(var TempReservationEntry: Record "Reservation Entry" temporary; OldSalesLine: Record "Sales Line")
    var
        ReservationEntry: Record "Reservation Entry";
    begin
        ReservationEntry.Reset();
        ReservationEntry.SetSourceFilter(
          Database::"Sales Line", OldSalesLine."Document Type".AsInteger(), OldSalesLine."Document No.", OldSalesLine."Line No.", true);
        if ReservationEntry.FindSet() then
            repeat
                TempReservationEntry := ReservationEntry;
                TempReservationEntry.Insert();
            until ReservationEntry.Next() = 0;
        ReservationEntry.DeleteAll();
    end;

    /// <summary>
    /// Copies reservation entries from a temporary table back to the database with a new source reference.
    /// </summary>
    /// <param name="TempReservationEntry">The temporary table containing the reservations.</param>
    /// <param name="OldSalesLine">The original sales line reference.</param>
    /// <param name="NewSourceRefNo">The new source reference number (line number).</param>
    procedure CopyReservEntryFromTemp(var TempReservationEntry: Record "Reservation Entry" temporary; OldSalesLine: Record "Sales Line"; NewSourceRefNo: Integer)
    var
        ReservationEntry: Record "Reservation Entry";
    begin
        TempReservationEntry.Reset();
        TempReservationEntry.SetSourceFilter(
          Database::"Sales Line", OldSalesLine."Document Type".AsInteger(), OldSalesLine."Document No.", OldSalesLine."Line No.", true);
        if TempReservationEntry.FindSet() then
            repeat
                ReservationEntry := TempReservationEntry;
                ReservationEntry."Source Ref. No." := NewSourceRefNo;
                ReservationEntry.Insert();
            until TempReservationEntry.Next() = 0;
        TempReservationEntry.DeleteAll();
    end;

    [EventSubscriber(ObjectType::Page, PAGE::Reservation, 'OnGetQtyPerUOMFromSourceRecRef', '', false, false)]
    local procedure OnGetQtyPerUOMFromSourceRecRef(SourceRecRef: RecordRef; var QtyPerUOM: Decimal; var QtyReserved: Decimal; var QtyReservedBase: Decimal; var QtyToReserve: Decimal; var QtyToReserveBase: Decimal)
    var
        SalesLine: Record "Sales Line";
    begin
        if MatchThisTable(SourceRecRef.Number) then begin
            SourceRecRef.SetTable(SalesLine);
            SalesLine.Find();
            QtyPerUOM := SalesLine.GetReservationQty(QtyReserved, QtyReservedBase, QtyToReserve, QtyToReserveBase);
        end;
    end;

    local procedure SetReservSourceFor(SourceRecordRef: RecordRef; var ReservationEntry: Record "Reservation Entry"; var CaptionText: Text)
    var
        SalesLine: Record "Sales Line";
    begin
        SourceRecordRef.SetTable(SalesLine);
        TestSourceTableFields(SalesLine);
        SalesLine.SetReservationEntry(ReservationEntry);
        CaptionText := SalesLine.GetSourceCaption();
    end;

    local procedure TestSourceTableFields(SalesLine: Record "Sales Line")
    begin
        SalesLine.TestField("Job No.", '');
        SalesLine.TestField("Drop Shipment", false);
        SalesLine.TestField(Type, SalesLine.Type::Item);
        SalesLine.TestField("Shipment Date");

        OnAfterTestSourceTableFields(SalesLine);
    end;

    /// <summary>
    /// Raised after testing source table fields for the sales line during reservation validation.
    /// </summary>
    /// <param name="SalesLine">The sales line that was validated.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterTestSourceTableFields(SalesLine: Record "Sales Line")
    begin
    end;

    local procedure EntryStartNo(): Integer
    begin
        exit(Enum::"Reservation Summary Type"::"Sales Quote".AsInteger());
    end;

    local procedure MatchThisEntry(EntryNo: Integer): Boolean
    begin
        exit(EntryNo in [Enum::"Reservation Summary Type"::"Sales Quote".AsInteger() ..
                         Enum::"Reservation Summary Type"::"Sales Return Order".AsInteger()]);
    end;

    local procedure MatchThisTable(TableID: Integer): Boolean
    begin
        exit(TableID = Database::"Sales Line");
    end;

    [EventSubscriber(ObjectType::Page, Page::Reservation, 'OnSetReservSource', '', false, false)]
    local procedure OnSetReservSource(SourceRecRef: RecordRef; var ReservEntry: Record "Reservation Entry"; var CaptionText: Text)
    begin
        if MatchThisTable(SourceRecRef.Number) then
            SetReservSourceFor(SourceRecRef, ReservEntry, CaptionText);
    end;

    [EventSubscriber(ObjectType::Page, Page::Reservation, 'OnDrillDownTotalQuantity', '', false, false)]
    local procedure ReservationOnDrillDownTotalQuantity(SourceRecRef: RecordRef; ReservEntry: Record "Reservation Entry"; EntrySummary: Record "Entry Summary"; Location: Record Location; MaxQtyToReserve: Decimal)
    var
        AvailableSalesLines: page "Available - Sales Lines";
    begin
        if MatchThisEntry(EntrySummary."Entry No.") then begin
            Clear(AvailableSalesLines);
            AvailableSalesLines.SetCurrentSubType(EntrySummary."Entry No." - EntryStartNo());
            AvailableSalesLines.SetSource(SourceRecRef, ReservEntry, ReservEntry.GetTransferDirection());
            AvailableSalesLines.RunModal();
        end;
    end;

    [EventSubscriber(ObjectType::Page, Page::Reservation, 'OnFilterReservEntry', '', false, false)]
    local procedure OnFilterReservEntry(var FilterReservEntry: Record "Reservation Entry"; ReservEntrySummary: Record "Entry Summary")
    begin
        if MatchThisEntry(ReservEntrySummary."Entry No.") then begin
            FilterReservEntry.SetRange("Source Type", DATABASE::"Sales Line");
            FilterReservEntry.SetRange("Source Subtype", ReservEntrySummary."Entry No." - EntryStartNo());
        end;
    end;

    [EventSubscriber(ObjectType::Page, Page::Reservation, 'OnAfterRelatesToSummEntry', '', false, false)]
    local procedure OnRelatesToEntrySummary(var FilterReservEntry: Record "Reservation Entry"; FromEntrySummary: Record "Entry Summary"; var IsHandled: Boolean)
    begin
        if MatchThisEntry(FromEntrySummary."Entry No.") then
            IsHandled :=
                (FilterReservEntry."Source Type" = DATABASE::"Sales Line") and
                (FilterReservEntry."Source Subtype" = FromEntrySummary."Entry No." - EntryStartNo());
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Ledger Entry-Reserve", 'OnDrillDownTotalQuantity', '', false, false)]
    local procedure ItemLedgerEntryOnDrillDownTotalQuantity(SourceRecRef: RecordRef; EntrySummary: Record "Entry Summary" temporary; ReservEntry: Record "Reservation Entry"; Location: Record Location; MaxQtyToReserve: Decimal; var IsHandled: Boolean; sender: Codeunit "Item Ledger Entry-Reserve")
    var
        CheckOutbound: Boolean;
    begin
        if MatchThisTable(ReservEntry."Source Type") then begin
            CheckOutbound := Location."Bin Mandatory" or Location."Require Pick";
            sender.DrillDownTotalQuantity(SourceRecRef, EntrySummary, ReservEntry, MaxQtyToReserve, CheckOutbound, true);
            IsHandled := true;
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnCreateReservation', '', false, false)]
    local procedure OnCreateReservation(SourceRecRef: RecordRef; TrackingSpecification: Record "Tracking Specification"; ForReservEntry: Record "Reservation Entry"; Description: Text[100]; ExpectedDate: Date; Quantity: Decimal; QuantityBase: Decimal)
    var
        SalesLine: Record "Sales Line";
    begin
        if MatchThisTable(ForReservEntry."Source Type") then begin
            CreateReservationSetFrom(TrackingSpecification);
            SourceRecRef.SetTable(SalesLine);
            OnCreateReservationOnBeforeCreateReservation(SalesLine, TrackingSpecification, Description, ExpectedDate, Quantity, QuantityBase, ForReservEntry);
            CreateReservation(SalesLine, Description, ExpectedDate, Quantity, QuantityBase, ForReservEntry);
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnLookupDocument', '', false, false)]
    local procedure OnLookupDocument(SourceType: Integer; SourceSubtype: Integer; SourceID: Code[20])
    var
        SalesHeader: Record "Sales Header";
    begin
        if MatchThisTable(SourceType) then begin
            SalesHeader.Reset();
            SalesHeader.SetRange("Document Type", SourceSubtype);
            SalesHeader.SetRange("No.", SourceID);
            case SourceSubtype of
                0:
                    PAGE.RunModal(PAGE::"Sales Quote", SalesHeader);
                1:
                    PAGE.RunModal(PAGE::"Sales Order", SalesHeader);
                2:
                    PAGE.RunModal(PAGE::"Sales Invoice", SalesHeader);
                3:
                    PAGE.RunModal(PAGE::"Sales Credit Memo", SalesHeader);
                5:
                    PAGE.RunModal(PAGE::"Sales Return Order", SalesHeader);
            end;
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnLookupLine', '', false, false)]
    local procedure OnLookupLine(SourceType: Integer; SourceSubtype: Integer; SourceID: Code[20]; SourceRefNo: Integer)
    var
        SalesLine: Record "Sales Line";
    begin
        if MatchThisTable(SourceType) then begin
            SalesLine.Reset();
            SalesLine.SetRange("Document Type", SourceSubtype);
            SalesLine.SetRange("Document No.", SourceID);
            SalesLine.SetRange("Line No.", SourceRefNo);
            PAGE.Run(PAGE::"Sales Lines", SalesLine);
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnFilterReservFor', '', false, false)]
    local procedure OnFilterReservFor(SourceRecRef: RecordRef; var ReservEntry: Record "Reservation Entry"; var CaptionText: Text)
    var
        SalesLine: Record "Sales Line";
    begin
        if MatchThisTable(SourceRecRef.Number) then begin
            SourceRecRef.SetTable(SalesLine);
            SalesLine.SetReservationFilters(ReservEntry);
            CaptionText := SalesLine.GetSourceCaption();
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnCalculateRemainingQty', '', false, false)]
    local procedure OnCalculateRemainingQty(SourceRecRef: RecordRef; var ReservEntry: Record "Reservation Entry"; var RemainingQty: Decimal; var RemainingQtyBase: Decimal)
    var
        SalesLine: Record "Sales Line";
    begin
        if MatchThisTable(ReservEntry."Source Type") then begin
            SourceRecRef.SetTable(SalesLine);
            SalesLine.GetRemainingQty(RemainingQty, RemainingQtyBase);
        end;
    end;

    local procedure GetSourceValue(ReservationEntry: Record "Reservation Entry"; var SourceRecordRef: RecordRef; ReturnOption: Option "Net Qty. (Base)","Gross Qty. (Base)"): Decimal
    var
        SalesLine: Record "Sales Line";
    begin
        SalesLine.Get(ReservationEntry."Source Subtype", ReservationEntry."Source ID", ReservationEntry."Source Ref. No.");
        SourceRecordRef.GetTable(SalesLine);
        case ReturnOption of
            ReturnOption::"Net Qty. (Base)":
                exit(SalesLine."Outstanding Qty. (Base)");
            ReturnOption::"Gross Qty. (Base)":
                exit(SalesLine."Quantity (Base)");
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnGetSourceRecordValue', '', false, false)]
    local procedure OnGetSourceRecordValue(var ReservEntry: Record "Reservation Entry"; ReturnOption: Option; var ReturnQty: Decimal; var SourceRecRef: RecordRef)
    begin
        if MatchThisTable(ReservEntry."Source Type") then
            ReturnQty := GetSourceValue(ReservEntry, SourceRecRef, ReturnOption);
    end;

    local procedure UpdateStatistics(ReservationEntry: Record "Reservation Entry"; var TempEntrySummary: Record "Entry Summary" temporary; AvailabilityDate: Date; DocumentType: Enum "Sales Document Type"; Positive: Boolean; var TotalQuantity: Decimal)
    var
        SalesLine: Record "Sales Line";
        AvailabilityFilter: Text;
    begin
        if not SalesLine.ReadPermission then
            exit;

        SalesLine.SetAutoCalcFields("Reserved Qty. (Base)");
        AvailabilityFilter := ReservationEntry.GetAvailabilityFilter(AvailabilityDate, Positive);
        SalesLine.FilterLinesForReservation(ReservationEntry, DocumentType, AvailabilityFilter, Positive);
        if SalesLine.FindSet() then
            repeat
                TempEntrySummary."Total Reserved Quantity" -= SalesLine."Reserved Qty. (Base)";
                TotalQuantity += SalesLine."Outstanding Qty. (Base)";
            until SalesLine.Next() = 0;

        if TotalQuantity = 0 then
            exit;

        if (Positive = (TotalQuantity < 0)) and (DocumentType <> SalesLine."Document Type"::"Return Order") or
            (Positive = (TotalQuantity > 0)) and (DocumentType = SalesLine."Document Type"::"Return Order")
        then begin
            TempEntrySummary."Table ID" := DATABASE::"Sales Line";
            TempEntrySummary."Summary Type" :=
                CopyStr(StrSubstNo(SummaryTypeTxt, SalesLine.TableCaption(), SalesLine."Document Type"),
                1, MaxStrLen(TempEntrySummary."Summary Type"));
            if DocumentType = SalesLine."Document Type"::"Return Order" then
                TempEntrySummary."Total Quantity" := TotalQuantity
            else
                TempEntrySummary."Total Quantity" := -TotalQuantity;
            TempEntrySummary."Total Available Quantity" := TempEntrySummary."Total Quantity" - TempEntrySummary."Total Reserved Quantity";
            if not TempEntrySummary.Insert() then
                TempEntrySummary.Modify();
        end;
    end;

    [EventSubscriber(ObjectType::Page, Page::"Reservation Entries", 'OnLookupReserved', '', false, false)]
    local procedure OnLookupReserved(var ReservationEntry: Record "Reservation Entry")
    begin
        if MatchThisTable(ReservationEntry."Source Type") then
            ShowSourceLines(ReservationEntry);
    end;

    local procedure ShowSourceLines(var ReservationEntry: Record "Reservation Entry")
    var
        SalesLine: Record "Sales Line";
    begin
        SalesLine.Reset();
        SalesLine.SetRange("Document Type", ReservationEntry."Source Subtype");
        SalesLine.SetRange("Document No.", ReservationEntry."Source ID");
        SalesLine.SetRange("Line No.", ReservationEntry."Source Ref. No.");
        PAGE.RunModal(PAGE::"Sales Lines", SalesLine);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnUpdateStatistics', '', false, false)]
    local procedure OnUpdateStatistics(CalcReservEntry: Record "Reservation Entry"; var ReservSummEntry: Record "Entry Summary"; AvailabilityDate: Date; Positive: Boolean; var TotalQuantity: Decimal)
    begin
        if ReservSummEntry."Entry No." in [Enum::"Reservation Summary Type"::"Sales Order".AsInteger(),
                                           Enum::"Reservation Summary Type"::"Sales Return Order".AsInteger()] then
            UpdateStatistics(
                CalcReservEntry, ReservSummEntry, AvailabilityDate, Enum::"Sales Document Type".FromInteger(ReservSummEntry."Entry No." - 31), Positive, TotalQuantity);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnAfterAutoReserveOneLine', '', false, false)]
    local procedure OnAfterAutoReserveOneLine(ReservSummEntryNo: Integer; var RemainingQtyToReserve: Decimal; var RemainingQtyToReserveBase: Decimal; Description: Text[100]; AvailabilityDate: Date; Search: Text[1]; NextStep: Integer; CalcReservEntry: Record "Reservation Entry"; CalcReservEntry2: Record "Reservation Entry"; Positive: Boolean; var sender: Codeunit "Reservation Management")
    begin
        if MatchThisEntry(ReservSummEntryNo) then
            AutoReserveSalesLine(
                CalcReservEntry, sender, ReservSummEntryNo, RemainingQtyToReserve, RemainingQtyToReserveBase,
                Description, AvailabilityDate, Search, NextStep, Positive);
    end;

    local procedure AutoReserveSalesLine(var CalcReservEntry: Record "Reservation Entry"; var sender: Codeunit "Reservation Management"; ReservSummEntryNo: Integer; var RemainingQtyToReserve: Decimal; var RemainingQtyToReserveBase: Decimal; Description: Text[100]; AvailabilityDate: Date; Search: Text[1]; NextStep: Integer; Positive: Boolean)
    var
        CallTrackingSpecification: Record "Tracking Specification";
        SalesLine: Record "Sales Line";
        QtyThisLine: Decimal;
        QtyThisLineBase: Decimal;
        ReservQty: Decimal;
        IsReserved: Boolean;
    begin
        IsReserved := false;
        OnBeforeAutoReserveSalesLine(
          ReservSummEntryNo, RemainingQtyToReserve, RemainingQtyToReserveBase, Description, AvailabilityDate, IsReserved, Search, NextStep, CalcReservEntry);
        if IsReserved then
            exit;

        SalesLine.SetAutoCalcFields("Reserved Qty. (Base)");
        SalesLine.FilterLinesForReservation(
          CalcReservEntry, Enum::"Sales Document Type".FromInteger(ReservSummEntryNo - Enum::"Reservation Summary Type"::"Sales Quote".AsInteger()),
          sender.GetAvailabilityFilter(AvailabilityDate), Positive);
        if SalesLine.Find(Search) then
            repeat
                QtyThisLine := SalesLine."Outstanding Quantity";
                QtyThisLineBase := SalesLine."Outstanding Qty. (Base)";
                if ReservSummEntryNo = Enum::"Reservation Summary Type"::"Sales Return Order".AsInteger() then // Return Order
                    ReservQty := -SalesLine."Reserved Qty. (Base)"
                else
                    ReservQty := SalesLine."Reserved Qty. (Base)";
                if (Positive = (QtyThisLineBase > 0)) and (ReservSummEntryNo <> Enum::"Reservation Summary Type"::"Sales Return Order".AsInteger()) or
                   (Positive = (QtyThisLineBase < 0)) and (ReservSummEntryNo = Enum::"Reservation Summary Type"::"Sales Return Order".AsInteger())
                then begin
                    QtyThisLine := 0;
                    QtyThisLineBase := 0;
                end;

                sender.SetQtyToReserveDownToTrackedQuantity(CalcReservEntry, SalesLine.RowID1(), QtyThisLine, QtyThisLineBase);

                CallTrackingSpecification.InitTrackingSpecification(
                  Database::"Sales Line", SalesLine."Document Type".AsInteger(), SalesLine."Document No.", '', 0, SalesLine."Line No.",
                  SalesLine."Variant Code", SalesLine."Location Code", SalesLine."Qty. per Unit of Measure");
                CallTrackingSpecification.CopyTrackingFromReservEntry(CalcReservEntry);

                sender.InsertReservationEntries(
                    RemainingQtyToReserve, RemainingQtyToReserveBase, ReservQty,
                    Description, SalesLine."Shipment Date", QtyThisLine, QtyThisLineBase, CallTrackingSpecification);
            until (SalesLine.Next(NextStep) = 0) or (RemainingQtyToReserveBase = 0);
    end;

    local procedure CheckQuantityReducedOnSalesLine(NewSalesLine: Record "Sales Line"; OldSalesLine: Record "Sales Line"): Boolean
    var
        Item: Record Item;
    begin
        if (NewSalesLine.Type <> NewSalesLine.Type::Item) or (NewSalesLine.Quantity = 0) or (NewSalesLine.Reserve <> NewSalesLine.Reserve::Always) then
            exit(false);

        if ShipmentExists(NewSalesLine) then
            exit(false);

        Item.SetLoadFields("Costing Method");
        Item.Get(NewSalesLine."No.");

        if Item."Costing Method" <> Item."Costing Method"::FIFO then
            exit(false);

        exit(NewSalesLine.Quantity < OldSalesLine.Quantity);
    end;

    local procedure ShipmentExists(SalesLine: Record "Sales Line"): Boolean
    var
        SalesShipmentLine: Record "Sales Shipment Line";
    begin
        SalesShipmentLine.SetRange("Document No.", SalesLine."Shipment No.");
        SalesShipmentLine.SetRange("Line No.", SalesLine."Shipment Line No.");
        exit(not SalesShipmentLine.IsEmpty());
    end;

    /// <summary>
    /// Raised after calculating the quantity to reserve for a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which the quantity was calculated.</param>
    /// <param name="QtyToReserve">The quantity to reserve in the sales unit of measure.</param>
    /// <param name="QtyToReserveBase">The quantity to reserve in base unit of measure.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterReservQuantity(SalesLine: Record "Sales Line"; var QtyToReserve: Decimal; var QtyToReserveBase: Decimal)
    begin
    end;

    /// <summary>
    /// Raised before assigning a sales line for planning purposes.
    /// </summary>
    /// <param name="SalesLine">The sales line to assign for planning.</param>
    /// <param name="IsHandled">Set to true to skip the default assignment logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeAssignForPlanning(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;


    /// <summary>
    /// Raised before deleting sales reservation entries when a sales line is modified.
    /// </summary>
    /// <param name="NewSalesLine">The modified sales line.</param>
    /// <param name="OldSalesLine">The original sales line before modification.</param>
    /// <param name="ReservMgt">The reservation management codeunit instance.</param>
    /// <param name="IsHandled">Set to true to skip the default deletion logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeDeleteSalesReservEntries(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line"; var ReservMgt: Codeunit "Reservation Management"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before retrieving the invoice specification for item tracking on a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which to retrieve the specification.</param>
    /// <param name="OK">Set to the result of the retrieval operation.</param>
    /// <param name="IsHandled">Set to true to skip the default retrieval logic.</param>
    /// <param name="TempInvoicingSpecification">The temporary tracking specification record for invoicing.</param>
    [IntegrationEvent(true, false)]
    local procedure OnBeforeRetrieveInvoiceSpecification(var SalesLine: Record "Sales Line"; var OK: Boolean; var IsHandled: Boolean; var TempInvoicingSpecification: Record "Tracking Specification" temporary)
    begin
    end;

    /// <summary>
    /// Raised before retrieving the invoice specification (variant 2) for item tracking on a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which to retrieve the specification.</param>
    /// <param name="OK">Set to the result of the retrieval operation.</param>
    /// <param name="IsHandled">Set to true to skip the default retrieval logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeRetrieveInvoiceSpecification2(var SalesLine: Record "Sales Line"; var OK: Boolean; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before transferring reservation entries from one sales line to another.
    /// </summary>
    /// <param name="OldSalesLine">The source sales line from which to transfer.</param>
    /// <param name="NewSalesLine">The destination sales line to which to transfer.</param>
    /// <param name="TransferQty">The quantity to transfer.</param>
    /// <param name="IsHandled">Set to true to skip the default transfer logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransferSaleLineToSalesLine(var OldSalesLine: Record "Sales Line"; var NewSalesLine: Record "Sales Line"; var TransferQty: Decimal; var IsHandled: Boolean);
    begin
    end;

    /// <summary>
    /// Raised before verifying changes to a sales line that may affect reservations.
    /// </summary>
    /// <param name="NewSalesLine">The modified sales line.</param>
    /// <param name="OldSalesLine">The original sales line before modification.</param>
    /// <param name="IsHandled">Set to true to skip the default verification logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeVerifyChange(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the variant code during reservation creation.
    /// </summary>
    /// <param name="SalesLine">The sales line for which the reservation is being created.</param>
    /// <param name="FromTrackingSpecification">The tracking specification containing variant information.</param>
    /// <param name="IsHandled">Set to true to skip the default variant code test.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateReservationOnBeforeTestVariantCode(SalesLine: Record "Sales Line"; FromTrackingSpecification: Record "Tracking Specification"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the location code during reservation creation.
    /// </summary>
    /// <param name="SalesLine">The sales line for which the reservation is being created.</param>
    /// <param name="FromTrackingSpecification">The tracking specification containing location information.</param>
    /// <param name="IsHandled">Set to true to skip the default location code test.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateReservationOnBeforeTestLocationCode(SalesLine: Record "Sales Line"; FromTrackingSpecification: Record "Tracking Specification"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the bin code when verifying sales line modifications.
    /// </summary>
    /// <param name="NewSalesLine">The modified sales line.</param>
    /// <param name="OldSalesLine">The original sales line before modification.</param>
    /// <param name="IsHandled">Set to true to skip the default bin code test.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTestSalesLineModificationOnBeforeTestBinCode(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the job number when verifying sales line modifications.
    /// </summary>
    /// <param name="SalesLine">The sales line being tested.</param>
    /// <param name="IsHandled">Set to true to skip the default job number test.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTestSalesLineModificationOnBeforeTestJobNo(SalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the variant code when verifying sales line modifications.
    /// </summary>
    /// <param name="NewSalesLine">The modified sales line.</param>
    /// <param name="OldSalesLine">The original sales line before modification.</param>
    /// <param name="IsHandled">Set to true to skip the default variant code test.</param>
    /// <param name="HasError">Set to true if an error condition was detected.</param>
    /// <param name="ThrowError">Indicates whether errors should be thrown.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTestSalesLineModificationOnBeforeTestVariantCode(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line"; var IsHandled: Boolean; var HasError: Boolean; ThrowError: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the location code when verifying sales line modifications.
    /// </summary>
    /// <param name="NewSalesLine">The modified sales line.</param>
    /// <param name="OldSalesLine">The original sales line before modification.</param>
    /// <param name="IsHandled">Set to true to skip the default location code test.</param>
    /// <param name="HasError">Set to true if an error condition was detected.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTestSalesLineModificationOnBeforeTestLocationCode(var NewSalesLine: Record "Sales Line"; var OldSalesLine: Record "Sales Line"; var IsHandled: Boolean; var HasError: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the item journal line during transfer from sales line.
    /// </summary>
    /// <param name="SalesLine">The source sales line.</param>
    /// <param name="IsHandled">Set to true to skip the default test.</param>
    /// <param name="ItemJnlLine">The destination item journal line.</param>
    /// <param name="TransferQty">The quantity being transferred.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferSalesLineToItemJnlLineOnBeforeItemJournalLineTest(SalesLine: Record "Sales Line"; var IsHandled: Boolean; var ItemJnlLine: Record "Item Journal Line"; var TransferQty: Decimal);
    begin
    end;

    /// <summary>
    /// Raised before initializing the record set during transfer of sales line to item journal line.
    /// </summary>
    /// <param name="OldReservationEntry">The reservation entry being processed.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferSalesLineToItemJnlLineOnBeforeInitRecordSet(var OldReservationEntry: Record "Reservation Entry")
    begin
    end;

    /// <summary>
    /// Raised before testing the old reservation entry during transfer from sales line to item journal line.
    /// </summary>
    /// <param name="SalesLine">The source sales line.</param>
    /// <param name="IsHandled">Set to true to skip the default test.</param>
    /// <param name="ItemJnlLine">The destination item journal line.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferSalesLineToItemJnlLineOnBeforeOldReservEntryTest(SalesLine: Record "Sales Line"; var IsHandled: Boolean; var ItemJnlLine: Record "Item Journal Line");
    begin
    end;

    /// <summary>
    /// Raised before transferring the reservation entry from sales line to item journal line.
    /// </summary>
    /// <param name="ReservationEntry">The reservation entry being transferred.</param>
    /// <param name="SalesLine">The source sales line.</param>
    /// <param name="ItemJournalLine">The destination item journal line.</param>
    /// <param name="IsHandled">Set to true to skip the default transfer logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferSalesLineToItemJnlLineOnBeforeTransferReservationEntry(var ReservationEntry: Record "Reservation Entry"; SalesLine: Record "Sales Line"; ItemJournalLine: Record "Item Journal Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before calculating the transfer quantity when transferring between sales lines.
    /// </summary>
    /// <param name="NewSalesLine">The destination sales line.</param>
    /// <param name="OldReservationEntry">The source reservation entry.</param>
    /// <param name="IsHandled">Set to true to skip the default calculation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferSaleLineToSalesLineOnBeforeCalcTransferQty(var NewSalesLine: Record "Sales Line"; var OldReservationEntry: Record "Reservation Entry"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before determining if an error occurred when verifying changes to a sales line.
    /// </summary>
    /// <param name="NewSalesLine">The modified sales line.</param>
    /// <param name="OldSalesLine">The original sales line before modification.</param>
    /// <param name="HasError">Set to true if an error condition was detected.</param>
    /// <param name="ShowError">Set to true if the error should be displayed to the user.</param>
    [IntegrationEvent(false, false)]
    local procedure OnVerifyChangeOnBeforeHasError(NewSalesLine: Record "Sales Line"; OldSalesLine: Record "Sales Line"; var HasError: Boolean; var ShowError: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before testing the Appl.-from Item Entry field during transfer from sales line to item journal line.
    /// </summary>
    /// <param name="SalesLine">The source sales line.</param>
    /// <param name="OldReservEntry">The source reservation entry.</param>
    /// <param name="IsHandled">Set to true to skip the default test.</param>
    /// <param name="ItemJnlLine">The destination item journal line.</param>
    [IntegrationEvent(false, false)]
    local procedure OnTransferSalesLineToItemJnlLineOnBeforeApplFromItemEntryTestField(SalesLine: Record "Sales Line"; OldReservEntry: Record "Reservation Entry"; var IsHandled: Boolean; var ItemJnlLine: Record "Item Journal Line")
    begin
    end;

    /// <summary>
    /// Raised before calling the item tracking page for a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which to open item tracking.</param>
    /// <param name="IsHandled">Set to true to skip the default item tracking call.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCallItemTracking(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before creating a reservation for a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which to create the reservation.</param>
    /// <param name="IsHandled">Set to true to skip the default reservation creation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCreateReservation(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before deleting invoice specifications from a sales header.
    /// </summary>
    /// <param name="SalesHeader">The sales header from which to delete specifications.</param>
    /// <param name="IsHandled">Set to true to skip the default deletion logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeDeleteInvoiceSpecFromHeader(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before deleting invoice specifications from a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line from which to delete specifications.</param>
    /// <param name="IsHandled">Set to true to skip the default deletion logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeDeleteInvoiceSpecFromLine(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before verifying quantity changes on a sales line that may affect reservations.
    /// </summary>
    /// <param name="NewSalesLine">The modified sales line.</param>
    /// <param name="IsHandled">Set to true to skip the default quantity verification.</param>
    /// <param name="OldSalesLine">The original sales line before modification.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeVerifyQuantity(var NewSalesLine: Record "Sales Line"; var IsHandled: Boolean; var OldSalesLine: Record "Sales Line")
    begin
    end;

    /// <summary>
    /// Raised before transferring a sales line to an item journal line.
    /// </summary>
    /// <param name="SalesLine">The source sales line.</param>
    /// <param name="IsHandled">Set to true to skip the default transfer logic.</param>
    /// <param name="ItemJnlLine">The destination item journal line.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeTransferSalesLineToItemJnlLine(var SalesLine: Record "Sales Line"; var IsHandled: Boolean; var ItemJnlLine: Record "Item Journal Line")
    begin
    end;

    /// <summary>
    /// Raised before running the Item Tracking Lines page modally for a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which item tracking is being opened.</param>
    /// <param name="ItemTrackingLines">The Item Tracking Lines page instance.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCallItemTrackingOnBeforeItemTrackingLinesRunModal(var SalesLine: Record "Sales Line"; var ItemTrackingLines: Page "Item Tracking Lines")
    begin
    end;

    /// <summary>
    /// Raised before running the Item Tracking Lines page for a secondary source on a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which item tracking is being opened.</param>
    /// <param name="ItemTrackingLines">The Item Tracking Lines page instance.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCallItemTrackingSecondSourceOnBeforeItemTrackingLinesRun(var SalesLine: Record "Sales Line"; var ItemTrackingLines: Page "Item Tracking Lines")
    begin
    end;

    /// <summary>
    /// Raised before opening item tracking lines for a secondary source on a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which item tracking is being opened.</param>
    /// <param name="TrackingSpecification">The tracking specification for the item.</param>
    /// <param name="SecondSourceQuantityArray">Array containing secondary source quantities.</param>
    /// <param name="IsHandled">Set to true to skip the default item tracking opening.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCallItemTrackingSecondSourceOnBeforeOpenItemTrackingLines(var SalesLine: Record "Sales Line"; TrackingSpecification: Record "Tracking Specification"; SecondSourceQuantityArray: array[3] of Decimal; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before inserting a tracking specification when retrieving invoice specifications.
    /// </summary>
    /// <param name="TempInvoicingSpecification">The temporary invoicing specification to insert.</param>
    /// <param name="ReservEntry">The source reservation entry.</param>
    [IntegrationEvent(false, false)]
    local procedure OnRetrieveInvoiceSpecificationOnBeforeInsert(var TempInvoicingSpecification: Record "Tracking Specification" temporary; ReservEntry: Record "Reservation Entry")
    begin
    end;

    /// <summary>
    /// Raised before checking the reserved quantity during reservation creation.
    /// </summary>
    /// <param name="SalesLine">The sales line for which the reservation is being created.</param>
    /// <param name="IsHandled">Set to true to skip the default reserved quantity check.</param>
    /// <param name="QuantityBase">The base quantity to check against.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateReservationOnBeforeCheckReservedQty(var SalesLine: Record "Sales Line"; var IsHandled: Boolean; QuantityBase: Decimal)
    begin
    end;

    /// <summary>
    /// Raised before creating a reservation entry during reservation creation.
    /// </summary>
    /// <param name="SalesLine">The sales line for which the reservation is being created.</param>
    /// <param name="Quantity">The quantity to reserve.</param>
    /// <param name="QuantityBase">The base quantity to reserve.</param>
    /// <param name="ForReservEntry">The reservation entry template.</param>
    /// <param name="IsHandled">Set to true to skip the default reservation entry creation.</param>
    /// <param name="FromTrackingSpecification">The source tracking specification.</param>
    /// <param name="ExpectedReceiptDate">The expected receipt date for the reservation.</param>
    /// <param name="Description">The description for the reservation entry.</param>
    /// <param name="ShipmentDate">The shipment date for the reservation.</param>
    /// <param name="DoCreateReservationEntry">Set to false to prevent the reservation entry from being created.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateReservationOnBeforeCreateReservEntry(var SalesLine: Record "Sales Line"; var Quantity: Decimal; var QuantityBase: Decimal; var ForReservEntry: Record "Reservation Entry"; var IsHandled: Boolean; var FromTrackingSpecification: Record "Tracking Specification"; ExpectedReceiptDate: Date; Description: Text[100]; ShipmentDate: Date; var DoCreateReservationEntry: Boolean)
    begin
    end;

    /// <summary>
    /// Raised after creating a reservation for a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line for which the reservation was created.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterCreateReservation(var SalesLine: Record "Sales Line")
    begin
    end;

    /// <summary>
    /// Raised before deleting reservation entries for a sales line.
    /// </summary>
    /// <param name="SalesLine">The sales line being deleted.</param>
    /// <param name="IsHandled">Set to true to skip the default deletion logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeDeleteLine(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before automatically reserving a sales line.
    /// </summary>
    /// <param name="ReservSummEntryNo">The reservation summary entry number.</param>
    /// <param name="RemainingQtyToReserve">The remaining quantity to reserve.</param>
    /// <param name="RemainingQtyToReserveBase">The remaining base quantity to reserve.</param>
    /// <param name="Description">The description for the reservation.</param>
    /// <param name="AvailabilityDate">The availability date for the reservation.</param>
    /// <param name="IsReserved">Set to true if the reservation was handled.</param>
    /// <param name="Search">The search direction for finding entries.</param>
    /// <param name="NextStep">The next step value for iterating through entries.</param>
    /// <param name="CalcReservEntry">The reservation entry used for calculation.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeAutoReserveSalesLine(ReservSummEntryNo: Integer; var RemainingQtyToReserve: Decimal; var RemainingQtyToReserveBase: Decimal; Description: Text[100]; AvailabilityDate: Date; var IsReserved: Boolean; Search: Text[1]; NextStep: Integer; CalcReservEntry: Record "Reservation Entry")
    begin
    end;

    /// <summary>
    /// Raised before creating a reservation from the reservation management event handler.
    /// </summary>
    /// <param name="SalesLine">The sales line for which the reservation is being created.</param>
    /// <param name="TrackingSpecification">The tracking specification for the reservation.</param>
    /// <param name="Description">The description for the reservation.</param>
    /// <param name="ExpectedDate">The expected date for the reservation.</param>
    /// <param name="Quantity">The quantity to reserve.</param>
    /// <param name="QuantityBase">The base quantity to reserve.</param>
    /// <param name="ReservationEntry">The reservation entry template.</param>
    [IntegrationEvent(false, false)]
    local procedure OnCreateReservationOnBeforeCreateReservation(var SalesLine: Record "Sales Line"; var TrackingSpecification: Record "Tracking Specification"; var Description: Text[100]; var ExpectedDate: Date; var Quantity: Decimal; var QuantityBase: Decimal; var ReservationEntry: Record "Reservation Entry")
    begin
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnAutoTrackOnCheckSourceType', '', false, false)]
    local procedure OnAutoTrackOnCheckSourceType(var ReservationEntry: Record "Reservation Entry"; var ShouldExit: Boolean)
    begin
        if ReservationEntry."Source Type" = Database::"Sales Line" then
            if not (ReservationEntry."Source Subtype" in [1, 5]) then
                ShouldExit := true; // Only order, return order
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnTestItemType', '', false, false)]
    local procedure OnTestItemType(SourceRecRef: RecordRef)
    var
        SalesLine: Record "Sales Line";
    begin
        if SourceRecRef.Number = Database::"Sales Line" then begin
            SourceRecRef.SetTable(SalesLine);
            SalesLine.TestField(Type, SalesLine.Type::Item);
        end;
    end;

#if not CLEAN27
    [Obsolete('This event is never raised.', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnSetSourceForReservationOnBeforeUpdateReservation(var ReservEntry: Record "Reservation Entry"; SalesLine: Record "Sales Line")
    begin
    end;

    [Obsolete('This event is never raised.', '27.0')]
    [IntegrationEvent(false, false)]
    local procedure OnAfterSetSourceForReservation(var CalcReservEntry: Record "Reservation Entry"; SalesLine: Record "Sales Line")
    begin
    end;
#endif
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Management", 'OnAutoReserveOnBeforeStopReservation', '', false, false)]
    local procedure OnAutoReserveOnBeforeStopReservation(var CalcReservEntry: Record "Reservation Entry"; var StopReservation: Boolean; SourceRecRef: RecordRef);
    var
        SalesLine: Record "Sales Line";
    begin
        if MatchThisTable(CalcReservEntry."Source Type") then begin
            StopReservation := not (CalcReservEntry."Source Subtype" in [1, 5]);  // Only order and return order
            SourceRecRef.SetTable(SalesLine);
            if (CalcReservEntry."Source Subtype" = 1) and (SalesLine.Quantity < 0) then
                StopReservation := true;
            if (CalcReservEntry."Source Subtype" = 5) and (SalesLine.Quantity >= 0) then
                StopReservation := true;
        end;
    end;

    // codeunit Create Reserv. entry subscribers

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Create Reserv. Entry", 'OnCheckSourceTypeSubtype', '', false, false)]
    local procedure CheckSourceTypeSubtype(var ReservationEntry: Record "Reservation Entry"; var IsError: Boolean)
    var
        IsHandled: Boolean;
    begin
        if MatchThisTable(ReservationEntry."Source Type") then begin
            OnBeforeCheckSourceTypeSubtypeOnBeforeIsError(ReservationEntry, IsError, IsHandled);
            if IsHandled then
                exit;
            IsError := not (ReservationEntry."Source Subtype" in [1, 5]);
        end;
    end;

    // codeunit Reservation Engine Mgt. subscribers

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Engine Mgt.", 'OnRevertDateToSourceDate', '', false, false)]
    local procedure OnRevertDateToSourceDate(var ReservEntry: Record "Reservation Entry")
    var
        SalesLine: Record "Sales Line";
    begin
        if ReservEntry."Source Type" = Database::"Sales Line" then begin
            SalesLine.Get(ReservEntry."Source Subtype", ReservEntry."Source ID", ReservEntry."Source Ref. No.");
            if ReservEntry.Positive then
                ReservEntry."Shipment Date" := 0D
            else begin
                ReservEntry."Expected Receipt Date" := 0D;
                ReservEntry."Shipment Date" := SalesLine."Shipment Date";
            end;
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Engine Mgt.", 'OnGetActivePointerFieldsOnBeforeAssignArrayValues', '', false, false)]
    local procedure OnGetActivePointerFieldsOnBeforeAssignArrayValues(TableID: Integer; var PointerFieldIsActive: array[6] of Boolean; var IsHandled: Boolean)
    begin
        if TableID = Database::"Sales Line" then begin
            PointerFieldIsActive[1] := true;  // Type
            PointerFieldIsActive[2] := true;  // SubType
            PointerFieldIsActive[3] := true;  // ID
            PointerFieldIsActive[6] := true;  // RefNo
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Reservation Engine Mgt.", 'OnCreateText', '', false, false)]
    local procedure OnAfterCreateText(ReservationEntry: Record "Reservation Entry"; var Description: Text[80])
    begin
        if ReservationEntry."Source Type" = Database::"Sales Line" then
            Description :=
                StrSubstNo(SourceDoc3Txt, SalesTxt,
                Enum::"Sales Document Type".FromInteger(ReservationEntry."Source Subtype"), ReservationEntry."Source ID");
    end;

    /// <summary>
    /// Initializes a tracking specification from a sales line for item tracking purposes.
    /// </summary>
    /// <param name="TransactionSpecification">Returns the initialized tracking specification.</param>
    /// <param name="SalesLine">The sales line to initialize from.</param>
    procedure InitFromSalesLine(var TransactionSpecification: Record "Tracking Specification"; SalesLine: Record "Sales Line")
    begin
        TransactionSpecification.Init();
        TransactionSpecification.SetItemData(
          SalesLine."No.", SalesLine.Description, SalesLine."Location Code", SalesLine."Variant Code", SalesLine."Bin Code",
          SalesLine."Qty. per Unit of Measure", SalesLine."Qty. Rounding Precision (Base)");
        TransactionSpecification.SetSource(
          Database::"Sales Line", SalesLine."Document Type".AsInteger(), SalesLine."Document No.", SalesLine."Line No.", '', 0);
        if SalesLine.IsCreditDocType() then
            TransactionSpecification.SetQuantities(
              SalesLine."Quantity (Base)", SalesLine."Return Qty. to Receive", SalesLine."Return Qty. to Receive (Base)",
              SalesLine."Qty. to Invoice", SalesLine."Qty. to Invoice (Base)", SalesLine."Return Qty. Received (Base)",
              SalesLine."Qty. Invoiced (Base)")
        else
            TransactionSpecification.SetQuantities(
              SalesLine."Quantity (Base)", SalesLine."Qty. to Ship", SalesLine."Qty. to Ship (Base)", SalesLine."Qty. to Invoice",
              SalesLine."Qty. to Invoice (Base)", SalesLine."Qty. Shipped (Base)", SalesLine."Qty. Invoiced (Base)");

        OnAfterInitFromSalesLine(TransactionSpecification, SalesLine);
    end;

    /// <summary>
    /// Raised after initializing a tracking specification from a sales line.
    /// </summary>
    /// <param name="TrackingSpecification">The tracking specification that was initialized.</param>
    /// <param name="SalesLine">The source sales line.</param>
    [IntegrationEvent(false, false)]
    local procedure OnAfterInitFromSalesLine(var TrackingSpecification: Record "Tracking Specification"; SalesLine: Record "Sales Line")
    begin
    end;

    [EventSubscriber(ObjectType::Table, Database::"Tracking Specification", 'OnBeforeCheckApplyFromItemEntrySourceType', '', false, false)]
    local procedure OnBeforeCheckApplyFromItemEntrySourceType(var TrackingSpecification: Record "Tracking Specification"; var IsHandled: Boolean)
    begin
        if not MatchThisTable(TrackingSpecification."Source Type") then
            exit;

        if ((TrackingSpecification."Source Subtype" in [3, 5]) and (TrackingSpecification."Quantity (Base)" < 0)) or
            ((TrackingSpecification."Source Subtype" in [1, 2]) and (TrackingSpecification."Quantity (Base)" > 0)) // sale
        then
            TrackingSpecification.FieldError("Quantity (Base)");

        IsHandled := true;
    end;

    [EventSubscriber(ObjectType::Table, Database::"Reservation Entry", 'OnAfterSummEntryNo', '', false, false)]
    local procedure OnBeforeSummEntryNo(ReservationEntry: Record "Reservation Entry"; var ReturnValue: Integer)
    begin
        if MatchThisTable(ReservationEntry."Source Type") then
            ReturnValue := Enum::"Reservation Summary Type"::"Sales Quote".AsInteger() + ReservationEntry."Source Subtype";
    end;

    [EventSubscriber(ObjectType::Table, Database::"Reservation Entry", 'OnUpdateSourceCost', '', false, false)]
    local procedure ReservationEntryOnUpdateSourceCost(ReservationEntry: Record "Reservation Entry"; UnitCost: Decimal)
    var
        SalesLine: Record "Sales Line";
        QtyToReserveNonBase: Decimal;
        QtyToReserve: Decimal;
        QtyReservedNonBase: Decimal;
        QtyReserved: Decimal;
    begin
        if MatchThisTable(ReservationEntry."Source Type") then begin
            SalesLine.Get(ReservationEntry."Source Subtype", ReservationEntry."Source ID", ReservationEntry."Source Ref. No.");
            SalesLine.GetReservationQty(QtyReservedNonBase, QtyReserved, QtyToReserveNonBase, QtyToReserve);
            if SalesLine."Qty. per Unit of Measure" <> 0 then
                SalesLine."Unit Cost (LCY)" :=
                    Round(SalesLine."Unit Cost (LCY)" / SalesLine."Qty. per Unit of Measure");
            if SalesLine."Quantity (Base)" <> 0 then
                SalesLine."Unit Cost (LCY)" :=
                    Round(
                    (SalesLine."Unit Cost (LCY)" *
                        (SalesLine."Quantity (Base)" - QtyReserved) +
                        UnitCost * QtyReserved) / SalesLine."Quantity (Base)", 0.00001);
            if SalesLine."Qty. per Unit of Measure" <> 0 then
                SalesLine."Unit Cost (LCY)" :=
                    Round(SalesLine."Unit Cost (LCY)" * SalesLine."Qty. per Unit of Measure");
            SalesLine.Validate("Unit Cost (LCY)");
            SalesLine.Modify();
        end;
    end;

    [EventSubscriber(ObjectType::Table, Database::"Requisition Line", 'OnReserveBindingOrder', '', false, false)]
    local procedure OnReserveBindingOrder(var RequisitionLine: Record "Requisition Line"; TrackingSpecification: Record "Tracking Specification"; SourceDescription: Text[100]; ExpectedDate: Date; ReservQty: Decimal; ReservQtyBase: Decimal; UpdateReserve: Boolean)
    begin
        if RequisitionLine."Demand Type" = Database::"Sales Line" then
            SalesLineBindToTracking(RequisitionLine, TrackingSpecification, SourceDescription, ExpectedDate, ReservQty, ReservQtyBase, UpdateReserve);
    end;

    local procedure SalesLineBindToTracking(RequisitionLine: Record "Requisition Line"; TrackingSpecification: Record "Tracking Specification"; Description: Text[100]; ExpectedDate: Date; ReservQty: Decimal; ReservQtyBase: Decimal; UpdateReserve: Boolean)
    var
        SalesLine: Record "Sales Line";
    begin
        SalesLine.Get(RequisitionLine."Demand Subtype", RequisitionLine."Demand Order No.", RequisitionLine."Demand Line No.");
        if TrackingSpecification."Source Type" <> Database::"Requisition Line" then begin
            BindToTracking(SalesLine, TrackingSpecification, Description, ExpectedDate, ReservQty, ReservQtyBase);
            if UpdateReserve then
                SalesLine.SetReserveToOptional();
        end else begin
            if not SalesLine."Drop Shipment" then
                SalesLine.SetReserveToOptional();
            if SalesLine.Reserve <> SalesLine.Reserve::Never then
                BindToTracking(SalesLine, TrackingSpecification, Description, ExpectedDate, ReservQty, ReservQtyBase);
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::OrderTrackingManagement, 'OnSetSourceRecord', '', false, false)]
    local procedure OrderTrackingManagementOnSetSourceRecord(var SourceRecordVar: Variant; var ReservationEntry: Record "Reservation Entry"; var ItemLedgerEntry2: Record "Item Ledger Entry")
    var
        SalesLine: Record "Sales Line";
        SourceRecRef: RecordRef;
    begin
        SourceRecRef.GetTable(SourceRecordVar);
        if MatchThisTable(SourceRecRef.Number) then begin
            SalesLine := SourceRecordVar;
            SetSalesLine(SalesLine, ReservationEntry, ItemLedgerEntry2);
        end;
    end;

    local procedure SetSalesLine(var SalesLine: Record "Sales Line"; var ReservEntry: Record "Reservation Entry"; var ItemLedgerEntry: Record "Item Ledger Entry")
    var
        SaleShptLine: Record "Sales Shipment Line";
    begin
        SalesLine.TestField(Type, SalesLine.Type::Item);
        ReservEntry."Source Type" := DATABASE::"Sales Line";

        ReservEntry.InitSortingAndFilters(false);
        SalesLine.SetReservationFilters(ReservEntry);

        if SalesLine."Qty. Shipped (Base)" <> 0 then begin
            SaleShptLine.SetCurrentKey("Order No.", "Order Line No.");
            SaleShptLine.SetRange("Order No.", SalesLine."Document No.");
            SaleShptLine.SetRange("Order Line No.", SalesLine."Line No.");
            if SaleShptLine.Find('-') then
                repeat
                    if ItemLedgerEntry.Get(SaleShptLine."Item Shpt. Entry No.") then
                        ItemLedgerEntry.Mark(true);
                until SaleShptLine.Next() = 0;
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::OrderTrackingManagement, 'OnInsertOrderTrackingEntry', '', false, false)]
    local procedure OnInsertOrderTrackingEntry(var OrderTrackingEntry: Record "Order Tracking Entry")
    var
        SalesLine: Record "Sales Line";
    begin
        if OrderTrackingEntry."For Type" = DATABASE::"Sales Line" then
            if SalesLine.Get(OrderTrackingEntry."For Subtype", OrderTrackingEntry."For ID", OrderTrackingEntry."For Ref. No.") then begin
                OrderTrackingEntry."Starting Date" := SalesLine."Shipment Date";
                OrderTrackingEntry."Ending Date" := SalesLine."Shipment Date";
            end;
    end;

    /// <summary>
    /// Raised before checking the source type and subtype for error conditions in reservation entries.
    /// </summary>
    /// <param name="ReservationEntry">The reservation entry being checked.</param>
    /// <param name="IsError">Set to true if an error condition was detected.</param>
    /// <param name="IsHandled">Set to true to skip the default check logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforeCheckSourceTypeSubtypeOnBeforeIsError(var ReservationEntry: Record "Reservation Entry"; var IsError: Boolean; var IsHandled: Boolean)
    begin
    end;

    [EventSubscriber(ObjectType::Table, Database::"Tracking Specification", 'OnGetSourceShipmentDate', '', false, false)]
    local procedure OnGetSourceShipmentDate(var TrackingSpecification: Record "Tracking Specification"; var ShipmentDate: Date);
    var
        SalesLine: Record "Sales Line";
    begin
        if TrackingSpecification."Source Type" = Database::"Sales Line" then begin
            SalesLine.Get(TrackingSpecification."Source Subtype", TrackingSpecification."Source ID", TrackingSpecification."Source Ref. No.");
            ShipmentDate := SalesLine."Shipment Date";
        end;
    end;
}