Skip to content

[Event Request] codeunit 6620 "Copy Document Mgt." #30210

Description

@Simon110394

Why do you need this change?

We need two events in procedure CopySalesShptLinesToDoc.
The first one is intended to calculate the boolean SplitLine in a custom way.
At the moment there are no other available events that give me the SplitLine variable as var parameter. Moreover I need to skip the standard code where SplitLine is set. That's why IsHandled variable is necessary.

The second one is needed to add a further condition before testing the variable CopyItemTrkg.
There are no other available events to skip CollectItemTrkgPerPostedDocLine, according to my custom condition, I want to add.

Describe the request

In procedure CopySalesShptLinesToDoc of codeunit 6620 "Copy Document Mgt." we need two events:

[IntegrationEvent(false, false)]
local procedure OnBeforeCopySalesShptLinesToBuffer2(var Ishandled: Boolean; var FromSalesLine: Record "Sales Line"; var FromSalesShptLine: Record "Sales Shipment Line"; var ToSalesHeader: Record "Sales Header"; var SplitLine: Boolean)
begin
end;

[IntegrationEvent(false, false)]
local procedure OnCopySalesShptLinesToDocOnBeforeCopyItemTrkg(var Ishandled: Boolean; var ToSalesHeader: Record "Sales Header")
begin
end;

Changes between **:

procedure CopySalesShptLinesToDoc(ToSalesHeader: Record "Sales Header"; var FromSalesShptLine: Record "Sales Shipment Line"; var LinesNotCopied: Integer; var MissingExCostRevLink: Boolean)
    var
        ItemLedgEntry: Record "Item Ledger Entry";
        TempTrkgItemLedgEntry: Record "Item Ledger Entry" temporary;
        FromSalesHeader: Record "Sales Header";
        FromSalesLine: Record "Sales Line";
        ToSalesLine: Record "Sales Line";
        TempFromSalesLineBuf: Record "Sales Line" temporary;
        FromSalesShptHeader: Record "Sales Shipment Header";
        TempItemTrkgEntry: Record "Reservation Entry" temporary;
        TempDocSalesLine: Record "Sales Line" temporary;
        PostedAssemblyHeader: Record "Posted Assembly Header";
        ItemTrackingMgt: Codeunit "Item Tracking Management";
        OldDocNo: Code[20];
        NextLineNo: Integer;
        NextItemTrkgEntryNo: Integer;
        FromLineCounter: Integer;
        ToLineCounter: Integer;
        CopyItemTrkg: Boolean;
        SplitLine: Boolean;
        FillExactCostRevLink: Boolean;
        CopyLine: Boolean;
        InsertDocNoLine: Boolean;
        IsHandled: Boolean;
    begin
        MissingExCostRevLink := false;
        InitCurrency(ToSalesHeader."Currency Code");
        OpenWindow();

        OnBeforeCopySalesShptLinesToDoc(TempDocSalesLine, ToSalesHeader, FromSalesShptLine);

        if FromSalesShptLine.FindSet() then
            repeat
                FromLineCounter := FromLineCounter + 1;
                if IsTimeForUpdate() then
                    UpdateWindow(1, FromLineCounter);
                if FromSalesShptHeader."No." <> FromSalesShptLine."Document No." then begin
                    FromSalesShptHeader.Get(FromSalesShptLine."Document No.");
                    TransferOldExtLines.ClearLineNumbers();
                end;

                IsHandled := false;
                OnCopySalesShptLinesToDocOnBeforeTestPricesInclVAT(ToSalesHeader, IncludeHeader, RecalculateLines, IsHandled);
                if not IsHandled then
                    FromSalesShptHeader.TestField("Prices Including VAT", ToSalesHeader."Prices Including VAT");

                OnCopySalesShptLinesToDocOnBeforeFromSalesHeaderTransferFields(FromSalesShptHeader, FromSalesHeader, ToSalesHeader, FromSalesShptLine);
                FromSalesHeader.TransferFields(FromSalesShptHeader);
                OnCopySalesShptLinesToDocOnAfterFromSalesHeaderTransferFields(FromSalesShptHeader, FromSalesHeader);
                FillExactCostRevLink :=
                  IsSalesFillExactCostRevLink(ToSalesHeader, 0, FromSalesHeader."Currency Code");
                FromSalesLine.TransferFields(FromSalesShptLine);
                FromSalesLine."Appl.-from Item Entry" := 0;
                FromSalesLine."Copied From Posted Doc." := true;

                CheckUpdateOldDocumentNoFromSalesShptLine(FromSalesShptLine, OldDocNo, InsertDocNoLine);

                OnBeforeCopySalesShptLinesToBuffer(FromSalesLine, FromSalesShptLine, ToSalesHeader);

				**Ishandled := false;
				OnBeforeCopySalesShptLinesToBuffer2(Ishandled, FromSalesLine, FromSalesShptLine, ToSalesHeader, SplitLine);
				if not Ishandled then begin**
					SplitLine := true;
					FromSalesShptLine.FilterPstdDocLnItemLedgEntries(ItemLedgEntry);
					OnCopySalesShptLinesToDocOnBeforeSplitPstdSalesLinesPerILE(ItemLedgEntry, FromSalesShptLine);
					if not SplitPstdSalesLinesPerILE(
						 ToSalesHeader, FromSalesHeader, ItemLedgEntry, TempFromSalesLineBuf,
						 FromSalesLine, TempDocSalesLine, NextLineNo, CopyItemTrkg, MissingExCostRevLink, FillExactCostRevLink, true)
					then
						if CopyItemTrkg then
							SplitLine :=
							  SplitSalesDocLinesPerItemTrkg(
								ItemLedgEntry, TempItemTrkgEntry, TempFromSalesLineBuf,
								FromSalesLine, TempDocSalesLine, NextLineNo, NextItemTrkgEntryNo, MissingExCostRevLink, true)
						else
							SplitLine := false;
				**end;**

                if not SplitLine then begin
                    TempFromSalesLineBuf := FromSalesLine;
                    CopyLine := true;
                end else
                    CopyLine := TempFromSalesLineBuf.FindSet() and FillExactCostRevLink;

                OnCopySalesShptLinesToDocOnAfterSplitPstdSalesLinesPerILE(TempFromSalesLineBuf, FromSalesShptLine);

                UpdateWindow(1, FromLineCounter);
                if CopyLine then begin
                    NextLineNo := GetLastToSalesLineNo(ToSalesHeader);
                    OnCopySalesShptLinesToDocOnAfterCalcNextLineNo(ToSalesHeader, FromSalesShptLine, FromSalesHeader, NextLineNo, InsertDocNoLine, FromLineCounter, TempFromSalesLineBuf);
                    AsmHdrExistsForFromDocLine := FromSalesShptLine.AsmToShipmentExists(PostedAssemblyHeader);
                    InitAsmCopyHandling(true);
                    if AsmHdrExistsForFromDocLine then begin
                        QtyToAsmToOrder := FromSalesShptLine.Quantity;
                        QtyToAsmToOrderBase := FromSalesShptLine."Quantity (Base)";
                        GenerateAsmDataFromPosted(PostedAssemblyHeader, ToSalesHeader."Document Type");
                    end;
                    if InsertDocNoLine then begin
                        InsertOldSalesDocNoLine(ToSalesHeader, FromSalesShptLine."Document No.", 1, NextLineNo);
                        InsertDocNoLine := false;
                    end;
                    repeat
                        ToLineCounter := ToLineCounter + 1;
                        if IsTimeForUpdate() then
                            UpdateWindow(2, ToLineCounter);

                        OnCopySalesShptLinesToDocOnBeforeCopySalesLine(ToSalesHeader, TempFromSalesLineBuf, FromSalesShptLine, CopyItemTrkg);

                        if CopySalesDocLine(
                             ToSalesHeader, ToSalesLine, FromSalesHeader, TempFromSalesLineBuf, NextLineNo, LinesNotCopied, false,
                             "Sales Document Type From"::"Posted Shipment", CopyPostedDeferral, TempFromSalesLineBuf."Line No.")
                        then begin
							**Ishandled := false;
							OnCopySalesShptLinesToDocOnBeforeCopyItemTrkg(Ishandled, ToSalesHeader);
							if not Ishandled then**
								if CopyItemTrkg then begin
									if SplitLine then
										ItemTrackingDocMgt.CollectItemTrkgPerPostedDocLine(
										  TempItemTrkgEntry, TempTrkgItemLedgEntry, false, TempFromSalesLineBuf."Document No.", TempFromSalesLineBuf."Line No.")
									else
										ItemTrackingDocMgt.CopyItemLedgerEntriesToTemp(TempTrkgItemLedgEntry, ItemLedgEntry);

									ItemTrackingMgt.CopyItemLedgEntryTrkgToSalesLn(
									  TempTrkgItemLedgEntry, ToSalesLine,
									  FillExactCostRevLink and ExactCostRevMandatory, MissingExCostRevLink,
									  FromSalesHeader."Prices Including VAT", ToSalesHeader."Prices Including VAT", true);
								end;
                            OnAfterCopySalesLineFromSalesShptLineBuffer(
                              ToSalesLine, FromSalesShptLine, IncludeHeader, RecalculateLines, TempDocSalesLine, ToSalesHeader, TempFromSalesLineBuf, ExactCostRevMandatory);
                        end;
                        OnCopySalesShptLinesToDocOnAfterCopySalesLine(ToSalesHeader, ToSalesLine, FromSalesShptLine);
                    until TempFromSalesLineBuf.Next() = 0;
                end;
                OnCopySalesShptLinesToDocOnAfterCopySalesShptLineToSalesLine(FromSalesShptLine, ToSalesLine);
            until FromSalesShptLine.Next() = 0;

        CloseWindow();

        OnAfterCopySalesShptLinesToDoc(ToSalesHeader, FromSalesShptLine, LinesNotCopied, MissingExCostRevLink);
    end;

Performance considerations

CopySalesShptLinesToDoc is executed only when users create sales documents from posted shipment lines (for example, when creating sales returns or credit memos from posted shipments). The requested events are raised once for each processed shipment line and do not introduce additional database operations or expensive calculations by themselves.

The platform impact is therefore negligible. Any additional performance cost would depend entirely on the subscriber implementation.


Data sensitivity/security review

The events expose only records that are already available within the standard execution context:

  • Sales Header
  • Sales Line
  • Sales Shipment Line
  • Boolean variables (SplitLine, IsHandled)

No credentials, secrets, personal data, or new categories of sensitive information are exposed. The requested change therefore does not introduce any security or data privacy concerns.


Multi-extension interaction considerations

Both events use the standard IsHandled extensibility pattern.

If multiple extensions subscribe to the same event, the main risk is that one extension may set IsHandled := true, thereby bypassing the standard logic and preventing another extension from executing its intended behavior.

For OnBeforeCopySalesShptLinesToBuffer2, multiple subscribers could also assign different values to SplitLine, and the last subscriber executed would determine the final value.

This behavior is consistent with the existing Business Central extensibility model and is an accepted characteristic of IsHandled events. Extensions that target the same business process are expected to coordinate their implementations when coexistence is required.

Loop-based IsHandled events

We understand the recommendation to avoid introducing new IsHandled publishers directly inside loops whenever possible.

In this case, however, both requested extensibility points must execute once for each processed Sales Shipment Line, because the decision depends on the current line being processed.

OnBeforeCopySalesShptLinesToBuffer2

The value of SplitLine is determined individually for every shipment line, based on the current:

FromSalesShptLine
FromSalesLine
ToSalesHeader

The customization may decide to replace the standard split calculation only for specific shipment lines, while allowing the standard behavior for all remaining lines.

Moving the event outside the loop would not be sufficient because the subscriber would no longer have the opportunity to make a per-line decision.

OnCopySalesShptLinesToDocOnBeforeCopyItemTrkg

Likewise, the decision whether to execute CollectItemTrkgPerPostedDocLine() depends on the current line being copied.

The customization must be able to bypass the standard item tracking collection only for selected shipment lines according to custom business rules, while preserving the standard behavior for all other lines.

Therefore, both events need to be evaluated for each iteration of the processing loop.

The proposed events themselves introduce only an event invocation and a Boolean check, without additional database operations.

SplitLine override scope

We also agree that the standard implementation would better follow the extensibility guidelines if the current SplitLine calculation were first extracted into a dedicated local helper procedure.

Our preferred implementation would therefore be:

IsHandled := false;
OnBeforeDetermineSplitLine(
IsHandled,
FromSalesLine,
FromSalesShptLine,
ToSalesHeader,
SplitLine);

if not IsHandled then
SplitLine :=
DetermineSplitLine(
ToSalesHeader,
FromSalesHeader,
FromSalesLine,
FromSalesShptLine,
TempFromSalesLineBuf,
TempDocSalesLine,
ItemLedgEntry,
TempItemTrkgEntry,
NextLineNo,
NextItemTrkgEntryNo,
CopyItemTrkg,
MissingExCostRevLink,
FillExactCostRevLink);

where DetermineSplitLine() contains the current standard implementation.

The extensibility requirement is not to customize individual calls such as:

SplitPstdSalesLinesPerILE()
SplitSalesDocLinesPerItemTrkg()

but to replace the entire decision process that determines the final value of SplitLine.

For some custom scenarios, partners need to execute a completely different algorithm while still remaining inside CopySalesShptLinesToDoc(), because the subsequent flow of the procedure depends directly on the resulting value of SplitLine.

Therefore, even after extracting the standard logic into a helper procedure, an IsHandled event immediately before invoking that helper would still be required. This keeps the standard code encapsulated while allowing extensions to replace the overall SplitLine determination without duplicating the entire CopySalesShptLinesToDoc() procedure.

Internal work item: AB#641619

Metadata

Metadata

Assignees

No one assigned

    Labels

    IntegrationGitHub request for Integration areaevent-requestRequest for adding an event

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions