Skip to content

[Event Request] table 5407 "Prod. Order Component" #11742

Description

@Simon110394

Why do you need this change?

We need a new event inside the procedure GetNeededQty of the "Prod. Order Component" table, in order to be able to manage getneedqty calculation according to custom logics in different lines of the procedure.

No sensitive data will be exposed exploiting the new events.
The IsHandled variable, will be set to true only if a partucular condition on Rec is satisfied. It is just a simple condition to verify. So the it won't have a big impact according to the performances.

Describe the request

In procedure GetNeededQty of table 5407 "Prod. Order Component" we need an event:

[IntegrationEvent(false, false)]
    local procedure OnGetNeededQtyOnBeforeExitAfterPreviousPosting(var ProdOrderComponent: Record "Prod. Order Component"; var CompQtyBase: Decimal; var Result: Decimal; var IsHandled: Boolean)
    begin
    end;

Changes between **:

procedure GetNeededQty(CalcBasedOn: Option "Actual Output","Expected Output"; IncludePreviousPosting: Boolean): Decimal
    var
        ProdOrderLine: Record "Prod. Order Line";
        ProdOrderRtngLine: Record "Prod. Order Routing Line";
        CapLedgEntry: Record "Capacity Ledger Entry";
        MfgCostCalcMgt: Codeunit "Mfg. Cost Calculation Mgt.";
        OutputQtyBase: Decimal;
        CompQtyBase: Decimal;
        NeededQty: Decimal;
        RoundingPrecision: Decimal;
        IsHandled: Boolean;
    begin
        Item.Get("Item No.");
        RoundingPrecision := Item."Rounding Precision";
        if RoundingPrecision = 0 then
            RoundingPrecision := UOMMgt.QtyRndPrecision();

        OnGetNeededQtyOnBeforeCalcBasedOn(Rec, RoundingPrecision);
        if CalcBasedOn = CalcBasedOn::"Actual Output" then begin
            ProdOrderLine.Get(Status, "Prod. Order No.", "Prod. Order Line No.");

            ProdOrderRtngLine.SetRange(Status, Status);
            ProdOrderRtngLine.SetRange("Prod. Order No.", "Prod. Order No.");
            ProdOrderRtngLine.SetRange("Routing No.", ProdOrderLine."Routing No.");
            ProdOrderRtngLine.SetRange("Routing Reference No.", ProdOrderLine."Routing Reference No.");
            ProdOrderRtngLine.SetRange("Routing Link Code", "Routing Link Code");
            if not ProdOrderRtngLine.FindFirst() or ("Routing Link Code" = '') then begin
                ProdOrderRtngLine.SetRange("Routing Link Code");
                ProdOrderRtngLine.SetFilter("Next Operation No.", '%1', '');
                if not ProdOrderRtngLine.FindFirst() then
                    ProdOrderRtngLine."Operation No." := '';
                OnGetNeededQtyOnAfterLastOperationFound(Rec, ProdOrderRtngLine);
            end;
            if Status in [Status::Released, Status::Finished] then begin
                CapLedgEntry.SetCurrentKey("Order Type", "Order No.", "Order Line No.");
                CapLedgEntry.SetRange("Order Type", CapLedgEntry."Order Type"::Production);
                CapLedgEntry.SetRange("Order No.", "Prod. Order No.");
                CapLedgEntry.SetRange("Order Line No.", "Prod. Order Line No.");
                CapLedgEntry.SetRange("Operation No.", ProdOrderRtngLine."Operation No.");
                if CapLedgEntry.Find('-') then
                    repeat
                        IsHandled := false;
                        OnGetNeededQtyOnBeforeAddOutputQtyBase(CapLedgEntry, OutputQtyBase, IsHandled, Rec);
                        if not IsHandled then
                            OutputQtyBase := OutputQtyBase + CapLedgEntry."Output Quantity" + CapLedgEntry."Scrap Quantity";
                    until CapLedgEntry.Next() = 0;
            end;

			**Clear(Result);
			OnGetNeededQtyBeforeCalcCompQtyBase(Ishandled, Rec, OutputQtyBase, Result);
			if Ishandled then
				exit(Result);**
			
            CompQtyBase := MfgCostCalcMgt.CalcActNeededQtyBase(ProdOrderLine, Rec, OutputQtyBase);
            OnGetNeededQtyAfterCalcCompQtyBase(Rec, CompQtyBase, OutputQtyBase);

            NeededQty := UOMMgt.RoundToItemRndPrecision(CompQtyBase / "Qty. per Unit of Measure", RoundingPrecision);
            if IncludePreviousPosting then begin
                if Status in [Status::Released, Status::Finished] then
                    CalcFields("Act. Consumption (Qty)");
                OnGetNeededQtyAfterCalcActConsumptionQty(Rec);
                NeededQty :=
                  NeededQty -
                  UOMMgt.RoundToItemRndPrecision("Act. Consumption (Qty)" / "Qty. per Unit of Measure", RoundingPrecision);
                IsHandled := false;
                OnGetNeededQtyOnBeforeExitWithPreviousPosting(Rec, CompQtyBase, NeededQty, IsHandled);
                if IsHandled then
                    exit(NeededQty);
                exit(NeededQty);
            end;
            **IsHandled := false;
            OnGetNeededQtyOnBeforeExitAfterPreviousPosting(Rec, CompQtyBase, NeededQty, IsHandled);
             if IsHandled then
                    exit(NeededQty);**
            exit(NeededQty);
        end;
        OnGetNeededQtyOnAfterCalcBasedOn(Rec);
        exit(Round("Remaining Quantity", RoundingPrecision));
    end;

Provide an implementation (optional)

  • I will provide the implementation for this extensibility request

Alternatives evaluated

We evaluated the existing events exposed by Prod. Order Component.GetNeededQty, including:

  • OnGetNeededQtyOnBeforeCalcBasedOn
  • OnGetNeededQtyOnBeforeAddOutputQtyBase
  • OnGetNeededQtyAfterCalcCompQtyBase
  • OnGetNeededQtyAfterCalcActConsumptionQty
  • OnGetNeededQtyOnBeforeExitWithPreviousPosting
  • OnGetNeededQtyOnAfterCalcBasedOn

These events are useful for modifying individual inputs or intermediate values, but they do not provide a way to completely replace the standard calculation at the required points.

In particular, OnGetNeededQtyAfterCalcCompQtyBase is raised only after the standard call to MfgCostCalcMgt.CalcActNeededQtyBase has already been executed. Therefore, an extension subscribing to that event can modify CompQtyBase, but the standard calculation is still executed first. This is not sufficient for scenarios where the extension must apply a completely different calculation and the standard calculation must be bypassed.

Likewise, OnGetNeededQtyOnBeforeExitWithPreviousPosting only applies to the branch where IncludePreviousPosting is true. It cannot be used to replace the result in the branch where previous postings are not included.

We also considered implementing the customization outside GetNeededQty, for example by duplicating the calculation in the consuming process. This would require reproducing standard Business Central calculation logic outside the base application and would create a maintenance risk whenever the standard implementation changes.

For these reasons, we consider an event at the requested points necessary to provide an extension point without duplicating the standard GetNeededQty implementation.

Justification for IsHandled

The first requested event, OnGetNeededQtyBeforeCalcCompQtyBase, is required to support scenarios where the standard calculation of CompQtyBase must be completely replaced.

The standard implementation currently performs:

CompQtyBase := MfgCostCalcMgt.CalcActNeededQtyBase(ProdOrderLine, Rec, OutputQtyBase);

There are customer-specific production scenarios where the required component quantity cannot be calculated using the standard CalcActNeededQtyBase calculation. For example, a custom extension may determine the required quantity using additional component-specific rules based on fields of the Prod. Order Component record and/or custom production information.

In such a case, subscribing to OnGetNeededQtyAfterCalcCompQtyBase is not equivalent, because the standard calculation would still be executed first. The required behavior is to prevent the standard calculation from being executed and provide the custom result instead.

Therefore, the subscriber needs to be able to set CompQtyBase / Result and signal that the standard implementation must be skipped:

IsHandled := true;

The second requested event, OnGetNeededQtyOnBeforeExitAfterPreviousPosting, is required for the same reason at the end of the procedure. After the standard calculation has been completed, there are scenarios where the final NeededQty must be replaced by a custom result before the procedure exits.

A regular event without IsHandled would allow an extension to inspect or modify the value, but would not provide an explicit mechanism to indicate that the standard exit/result handling has been replaced by the subscriber's calculation.

The IsHandled flag is only set to true when the subscriber's specific business condition is satisfied. In all other cases, the standard Business Central implementation remains unchanged.

Multi-extension interaction

We understand that IsHandled events can create interaction considerations when multiple extensions subscribe to the same event.

The expected behavior is that an extension sets IsHandled := true only when its own specific condition is satisfied. If no subscriber handles the event, IsHandled remains false and the standard Business Central logic is executed.

If multiple extensions subscribe to the event and more than one extension satisfies its handling condition, the first subscriber setting IsHandled := true may prevent the standard logic from being executed, and another subscriber may also attempt to provide a custom result.

We consider this an inherent characteristic of IsHandled events and expect extensions using this event to apply sufficiently specific conditions to avoid handling records outside their own scope.

The requested events do not expose sensitive data and do not introduce additional database operations by themselves. The event invocation consists only of passing the existing procedure variables to subscribers. Subscribers can decide whether to handle the calculation based on the current Prod. Order Component record.

The requested extensibility point therefore allows customer-specific implementations without requiring extensions to copy or replace the entire GetNeededQty procedure.

The existing event OnGetNeededQtyOnBeforeCalcActNeededQtyBase does not cover our scenario.

We specifically need the requested event OnGetNeededQtyOnBeforeExitAfterPreviousPosting because our customization must be able to manage the final exit of GetNeededQty in cases where IncludePreviousPosting = false.

The existing event OnGetNeededQtyOnBeforeCalcActNeededQtyBase is raised earlier in the procedure, before the remaining standard GetNeededQty logic is executed. It therefore does not allow us to control the final result at the exit point.

Furthermore, we also need to handle cases where IncludePreviousPosting = true, but the execution has not already reached an exit through the existing OnGetNeededQtyOnBeforeExitWithPreviousPosting event.

Our requirement is specifically to have an extension point immediately before the final exit(NeededQty), so that custom logic can determine whether the standard result should be replaced in all execution paths that reach this final exit.

In particular, the requested event covers:

  • IncludePreviousPosting = false, where the existing OnGetNeededQtyOnBeforeExitWithPreviousPosting event is never raised.
  • IncludePreviousPosting = true, when the execution does not exit through the earlier OnGetNeededQtyOnBeforeExitWithPreviousPosting event and subsequently reaches the final exit(NeededQty).

Therefore, the existing OnGetNeededQtyOnBeforeCalcActNeededQtyBase event cannot provide the required extensibility point, because it is executed at an earlier stage of the procedure and cannot control the final exit/result after the subsequent standard logic has been processed.

For this reason, we still require the proposed OnGetNeededQtyOnBeforeExitAfterPreviousPosting event.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions