Skip to content

[Event Request] codeunit 90 "Purch.-Post" #30216

Description

@Simon110394

Why do you need this change?

We need an event OnBeforePrepareJobLinePrepareJobLine in order to be able to assign a different value to the field "Qty. to Invoice" according to a particular custom condition as well as run a different custom procedure instead of PrepareJobLine.
At the moment there are no alternative events to be able to skip the procedure PrepareJobLine or running an custom procedure instead if a condition base do custom fields is satisfied.

Describe the request

In procedure PostItemJnlLineJobConsumption of codeunit 90 "Purch.-Post" we need an event:

[IntegrationEvent(false, false)]
local procedure OnBeforePrepareJobLinePrepareJobLine(var Ishandled: Boolean; var PurchLine: Record "Purchase Line"; QtyToBeInvoiced: Decimal; var PurchHeader: Record "Purchase Header"; var PurchLineACY: Record "Purchase Line")
begin
end;

Changes between **:

local procedure PostItemJnlLineJobConsumption(PurchHeader: Record "Purchase Header"; var PurchLine: Record "Purchase Line"; ItemJournalLine: Record "Item Journal Line"; var TempPurchReservEntry: Record "Reservation Entry" temporary; QtyToBeInvoiced: Decimal; QtyToBeReceived: Decimal; var TempTrackingSpecification: Record "Tracking Specification" temporary; PurchItemLedgEntryNo: Integer)
    var
        ItemLedgEntry: Record "Item Ledger Entry";
        TempReservationEntry: Record "Reservation Entry" temporary;
        JobPlanningLine: Record "Job Planning Line";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnPostItemJnlLineJobConsumption(
          PurchHeader, PurchLine, ItemJournalLine, TempPurchReservEntry, QtyToBeInvoiced, QtyToBeReceived,
          TempTrackingSpecification, PurchItemLedgEntryNo, IsHandled, ItemJnlPostLine, PurchInvHeader, PurchCrMemoHeader, SrcCode);
        if IsHandled then
            exit;

        if PurchLine."Job No." <> '' then begin
            ItemJournalLine."Entry Type" := ItemJournalLine."Entry Type"::"Negative Adjmt.";
            Job.Get(PurchLine."Job No.");
            ItemJournalLine."Source No." := Job."Bill-to Customer No.";
            if PurchHeader.Invoice then begin
                ItemLedgEntry.Reset();
                ItemLedgEntry.SetRange("Document Type", ItemLedgEntry."Document Type"::"Purchase Return Shipment");
                if PurchLine."Return Shipment No." <> '' then
                    ItemLedgEntry.SetRange("Document No.", PurchLine."Return Shipment No.")
                else
                    ItemLedgEntry.SetRange("Document No.", PurchHeader."Last Return Shipment No.");
                ItemLedgEntry.SetRange("Item No.", PurchLine."No.");
                ItemLedgEntry.SetRange("Entry Type", ItemLedgEntry."Entry Type"::"Negative Adjmt.");
                ItemLedgEntry.SetRange("Completely Invoiced", false);
                OnPostItemJnlLineJobConsumptionOnAfterItemLedgEntrySetFilters(ItemLedgEntry, PurchLine, ItemJournalLine);
                if ItemLedgEntry.FindFirst() then
                    ItemJournalLine."Item Shpt. Entry No." := ItemLedgEntry."Entry No.";
            end;
            JobPlanningLine.SetLoadFields("Job Contract Entry No.");
            if JobPlanningLine.Get(PurchLine."Job No.", PurchLine."Job Task No.", PurchLine."Job Planning Line No.") then
                ItemJournalLine."Job Contract Entry No." := JobPlanningLine."Job Contract Entry No.";
            ItemJournalLine."Source Type" := ItemJournalLine."Source Type"::Customer;
            ItemJournalLine."Discount Amount" := 0;

            GetAppliedItemLedgEntryNo(ItemJournalLine, PurchLine."Quantity Received");

            if QtyToBeReceived <> 0 then
                CopyJobConsumptionReservation(
                  TempReservationEntry, TempPurchReservEntry, ItemJournalLine, TempTrackingSpecification,
                  PurchItemLedgEntryNo, PurchLine.IsNonInventoriableItem());

            OnPostItemJnlLineJobConsumptionOnBeforeRunItemJnlPostLineWithReservation(ItemJournalLine, TempReservationEntry, PurchLine);
            RunItemJnlPostLineWithReservation(ItemJournalLine, TempReservationEntry);

            IsHandled := false;
            OnPostItemJnlLineJobConsumptionOnBeforeJobPost(
                PurchHeader, PurchInvHeader, PurchCrMemoHeader, PurchRcptHeader, ReturnShptHeader, PurchLine, SrcCode, QtyToBeReceived, IsHandled, QtyToBeInvoiced);
            if IsHandled then
                exit;

            if PurchLine."Job Line Type" = PurchLine."Job Line Type"::" " then
                ValidateMatchingJobPlanningLine(PurchLine);

            if QtyToBeInvoiced <> 0 then begin
                PurchLine."Qty. to Invoice" := QtyToBeInvoiced;
				**IsHandled := false;
				OnBeforePrepareJobLinePrepareJobLine(Ishandled, PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY);
				if not IsHandled then**
					InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);
            end;
        end;
    end;

Alternative extensibility options reviewed

We reviewed the existing extensibility points in codeunit 90 "Purch.-Post", in particular:

OnPostItemJnlLineJobConsumption
OnPostItemJnlLineJobConsumptionOnBeforeJobPost

These events are not sufficient for our scenario because they are raised too early in the execution flow and do not provide a hook immediately before InvoicePostingInterface.PrepareJobLine(...).

Our requirement is to:

modify PurchLine."Qty. to Invoice" after all standard validations and calculations have been completed;
optionally bypass PrepareJobLine and execute custom logic instead;
execute this customization only when specific custom fields and business conditions are satisfied.

There is currently no event at this exact location that allows replacing or skipping the standard call.

Why an IsHandled event is required

A regular event before or after PrepareJobLine is not sufficient.

In our scenario, under particular custom conditions based on extension fields, we must completely replace the standard PrepareJobLine execution with a custom implementation and assign a different value to PurchLine."Qty. to Invoice".

If the standard code continues to execute after our custom logic, PrepareJobLine would run twice or would overwrite the values calculated by our customization. Therefore, an IsHandled pattern is required to allow subscribers to bypass the standard behavior and execute an alternative implementation.

Performance considerations

This event is executed only during purchase posting when:

the purchase line is linked to a job (Job No. <> ''); and
QtyToBeInvoiced <> 0.

Therefore, the execution frequency is relatively low and limited to a specific posting scenario. The proposed event only adds a single event publisher and a Boolean check, so the expected performance impact is negligible.

Sensitive data exposure

The proposed event does not expose any sensitive or private data.

The parameters (PurchHeader, PurchLine, PurchLineACY, and QtyToBeInvoiced) are already available in the current posting context and contain standard Business Central business data only. No credentials, personal information, or security-sensitive information are exposed.

Multi-extension interaction risks

As with any IsHandled event, if multiple extensions subscribe and set IsHandled := true, only one implementation should take control of the process.

This is an accepted and well-known extensibility pattern in Business Central. Extensions that subscribe to this event are expected to coordinate ownership of the customization and avoid conflicting implementations, exactly as with existing IsHandled events throughout the base application.

Why the existing downstream event is insufficient

We evaluated the existing OnBeforePostJobOnPurchaseLine(...) event in JobPostLine.Codeunit.al, but it does not satisfy our scenario because it is raised after InvoicePostingInterface.PrepareJobLine(...) has already been executed.

Our requirement is to intervene before the call to:

InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);

for the following reasons:

under specific custom conditions we need to assign a different value to PurchLine."Qty. to Invoice" immediately before PrepareJobLine() uses it;
in those same scenarios we must completely replace the execution of PrepareJobLine() with our own implementation.

The downstream event in JobPostLine.Codeunit cannot achieve this because, when it is raised:

PrepareJobLine() has already copied the purchase line information into the temporary job posting structures;
the standard preparation logic has already been executed using the original values;
changing PurchLine."Qty. to Invoice" at that point no longer affects the preparation performed by PrepareJobLine().

Therefore, replacing the logic in JobPostLine is too late for this scenario.

The required extensibility point must be located immediately before the call to InvoicePostingInterface.PrepareJobLine() so that extensions can:

modify PurchLine."Qty. to Invoice" before it is consumed by the standard preparation logic;
optionally skip the standard PrepareJobLine() call entirely and execute an alternative implementation instead.

This cannot be achieved through the existing downstream event because the preparation phase has already completed.

Internal work item: AB#641618

Metadata

Metadata

Assignees

No one assigned

    Labels

    SCMGitHub request for SCM areaevent-requestRequest for adding an eventext-ready-to-implementReviewed and ready to implement and create PR

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions