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
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:
Changes between **:
Performance considerations
CopySalesShptLinesToDocis 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 HeaderSales LineSales Shipment LineSplitLine,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
IsHandledextensibility 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 toSplitLine, 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
IsHandledevents. 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