Skip to content

[W1][Report][840][Suggest Worksheet Lines] New OnSkip event before InsertCFLineForJobPlanningLine #30440

Description

@MortegaITB

Why do you need this change?

Report 840 "Suggest Worksheet Lines" calls InsertCFLineForJobPlanningLine() unconditionally for every billable Job Planning Line, with no supported way to prevent the call for specific lines (e.g. lines belonging to projects with Status = Completed).

Problem statement: there is no event today that fires immediately before InsertCFLineForJobPlanningLine() and can prevent its execution. The existing event OnJobPlanningLineOnAfterGetRecordOnBeforeInsertCFLineForJobPlanningLine only allows reacting to the record, not controlling whether the subsequent call is executed. We previously requested adding a skip parameter to that existing event and understand this is not acceptable, since it would change the contract for existing subscribers. This request instead asks for a brand-new, additive event.

Alternatives evaluated:

  • Filtering "Job No." with SetFilter (exclusion list) at OnPreDataItem: rejected, AL filter strings have a length limit and don't scale once excluded projects grow (e.g. 500 out of 10,000 lines).
  • Post-processing/deleting already-inserted Cash Flow Worksheet Line records: rejected, redundant work and risk of transient inconsistent state.
  • Duplicating report logic in a custom extension: rejected, ongoing maintenance liability with every BC update.
  • Requesting a modification to the existing OnJobPlanningLineOnAfterGetRecordOnBeforeInsertCFLineForJobPlanningLine event to add a skip parameter: rejected per prior review, since it would change the contract of an existing event that other subscribers already rely on.
  • A generic IsHandled/Handled event: rejected in favor of the OnSkip pattern. Per [Types of events for extensibility](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-events-overview), Handled events are classified Low Value because "only one subscriber can process the request" and "it's unclear whether the code was handled, and why." The same guidance recommends OnSkip events instead, and documents this exact scenario as a precedent (OnInsertReverseEntryOnBeforeInsertMaintenanceLedgerEntryBuffer(var MaintenanceLedgerEntry; var SkipInsertOfMaintenanceLedgerEntry: Boolean) in Codeunit 5600 "FA Insert Ledger Entry").

[L14]
Why OnSkip, not IsHandled: the requirement is to conditionally prevent InsertCFLineForJobPlanningLine() (and its side effect of updating/persisting the Job Planning Line via UpdatePlannedDueDate()/Modify()) from being called at all. A specifically named skip parameter (SkipInsertCFLineForJobPlanningLine) documents exactly what is being skipped and, following the OnSkip pattern, lets multiple independent subscribers each contribute to the decision without needing to check whether another subscriber already set it — unlike a generic IsHandled, which is Low Value precisely because it doesn't scale across multiple subscribers.

Performance considerations: the event fires once per "Job Planning Line" record already being iterated by the dataitem — no new loop, no additional recordset iteration, no filter string built or concatenated. A typical subscriber (e.g. Job.Get("Job No.")) is an O(1) primary-key lookup, so total cost scales linearly with lines already processed, not with the number of excluded projects.

Data sensitivity review: no new or sensitive data is exposed. "Job Planning Line" is the same record already available in the existing (unrelated) event in this object; no additional fields, tables, or security-relevant data are introduced.

Multi-extension interaction: multiple subscribers can each independently set SkipInsertCFLineForJobPlanningLine := true for their own exclusion criteria (e.g. completed projects, on-hold projects) without needing to check whether another subscriber already set it. Since the outcome is identical regardless of which criterion triggered it (the procedure call is skipped), there is no conflict risk. The current OnAfterGetRecord processing continues normally; the only difference is that InsertCFLineForJobPlanningLine() is not executed for the current line.

Benefit: enables partners/customers to prevent cash flow line generation for specific Job Planning Line records without workarounds, filter-length limits, or duplicated report logic — as a new, additive, high-quality OnSkip event that does not alter the behavior or contract of any existing event or subscriber.


Describe the request

Add a new event publisher OnBeforeInsertCFLineForJobPlanningLine to Report 840 "Suggest Worksheet Lines", raised in "Job Planning Line" - OnAfterGetRecord immediately before the call to InsertCFLineForJobPlanningLine(), using a specifically named skip parameter (SkipInsertCFLineForJobPlanningLine) following the OnSkip pattern.

This is a new, additive event — it does not modify the signature or behavior of the existing OnJobPlanningLineOnAfterGetRecordOnBeforeInsertCFLineForJobPlanningLine event.

Proposed publisher location:

  • Object: Report 840 "Suggest Worksheet Lines"
  • Procedure: "Job Planning Line" - OnAfterGetRecord
  • Placement rationale: immediately before InsertCFLineForJobPlanningLine(), the exact point where the decision needs to be made whether the procedure should be executed. No other code is skipped or wrapped.

Before

trigger OnAfterGetRecord()
begin
    Window.Update(2, JobsMsg);
    Window.Update(3, "Job No.");

    if not ("Line Type" in ["Line Type"::Billable, "Line Type"::"Both Budget and Billable"]) then
        exit;

    OnJobPlanningLineOnAfterGetRecordOnBeforeInsertCFLineForJobPlanningLine("Job Planning Line");
    InsertCFLineForJobPlanningLine();
end;

After

trigger OnAfterGetRecord()
var
    SkipInsertCFLineForJobPlanningLine: Boolean;
begin
    Window.Update(2, JobsMsg);
    Window.Update(3, "Job No.");

    if not ("Line Type" in ["Line Type"::Billable, "Line Type"::"Both Budget and Billable"]) then
        exit;

    OnJobPlanningLineOnAfterGetRecordOnBeforeInsertCFLineForJobPlanningLine("Job Planning Line");

    SkipInsertCFLineForJobPlanningLine := false;
    OnBeforeInsertCFLineForJobPlanningLine("Job Planning Line", SkipInsertCFLineForJobPlanningLine);

    if not SkipInsertCFLineForJobPlanningLine then
        InsertCFLineForJobPlanningLine();
end;

[IntegrationEvent(false, false)]
local procedure OnBeforeInsertCFLineForJobPlanningLine(JobPlanningLine: Record "Job Planning Line"; var SkipInsertCFLineForJobPlanningLine: Boolean)
begin
end;

The important distinction is that SkipInsertCFLineForJobPlanningLine controls only whether InsertCFLineForJobPlanningLine() is called. It does not cause an exit from the OnAfterGetRecord trigger.

Subscriber example

[EventSubscriber(ObjectType::Report, Report::"Suggest Worksheet Lines", 'OnBeforeInsertCFLineForJobPlanningLine', '', false, false)]
local procedure SkipCFLineForCompletedProjects(
    JobPlanningLine: Record "Job Planning Line";
    var SkipInsertCFLineForJobPlanningLine: Boolean)
var
    Job: Record Job;
begin
    if Job.Get(JobPlanningLine."Job No.") and (Job.Status = Job.Status::Completed) then
        SkipInsertCFLineForJobPlanningLine := true;
end;

Multiple subscribers can each independently set SkipInsertCFLineForJobPlanningLine := true for their own exclusion criteria (e.g. completed projects, on-hold projects) without needing to check whether another subscriber already set it. Any subscriber setting it to true is sufficient to prevent the procedure call.

The OnAfterGetRecord trigger itself continues after the conditional check; only the call to InsertCFLineForJobPlanningLine() is skipped.


Notes on naming and scope

  • The new event follows the standard OnBefore<ProcedureName> naming convention, matching the procedure it precedes (InsertCFLineForJobPlanningLine).
  • The skip parameter is specifically named (SkipInsertCFLineForJobPlanningLine), not a generic IsHandled/Handled, following the OnSkip pattern guidance.
  • It is fully additive: the existing OnJobPlanningLineOnAfterGetRecordOnBeforeInsertCFLineForJobPlanningLine event keeps its current signature and firing point, unchanged, immediately before this new event.
  • SkipInsertCFLineForJobPlanningLine defaults to false, so InsertCFLineForJobPlanningLine() executes exactly as today when no subscriber sets it — zero behavior change for existing installations with no subscriber.
  • When the parameter is set to true, only the call to InsertCFLineForJobPlanningLine() is skipped. The OnAfterGetRecord trigger does not exit because of the skip parameter.
  • Because the procedure itself is not executed when skipped, its side effects are also not performed, including the creation/insertion of the Cash Flow Worksheet Line, the call to UpdatePlannedDueDate(), and the subsequent Modify() of the Job Planning Line.
  • No dependent objects; single-object, minimal diff.

EventRequest (optional structured format)

[W1][Report][840][Suggest Worksheet Lines]
"Job Planning Line" - OnAfterGetRecord
___

New OnSkip event raised immediately before InsertCFLineForJobPlanningLine(), with a specifically named SkipInsertCFLineForJobPlanningLine parameter, to allow subscribers to prevent InsertCFLineForJobPlanningLine() from being called for specific Job Planning Line records (e.g. completed projects) without workarounds or filter-length limits. The skip parameter controls only the procedure call and does not exit the OnAfterGetRecord trigger. See "Why do you need this change?" and "Describe the request" above for full justification.
___

[IntegrationEvent(false, false)]
local procedure OnBeforeInsertCFLineForJobPlanningLine(JobPlanningLine: Record "Job Planning Line"; var SkipInsertCFLineForJobPlanningLine: Boolean)
begin
end;

Behavior confirmation for skip event

Yes, skipping the call to InsertCFLineForJobPlanningLine() is the intended behavior.

When SkipInsertCFLineForJobPlanningLine := true, the report should not call InsertCFLineForJobPlanningLine() for the current Job Planning Line.

Importantly, the skip parameter is intended to control only the procedure call. It does not cause an exit from the "Job Planning Line" - OnAfterGetRecord trigger.

Therefore:

  • SkipInsertCFLineForJobPlanningLine = falseInsertCFLineForJobPlanningLine() is called exactly as today.
  • SkipInsertCFLineForJobPlanningLine = trueInsertCFLineForJobPlanningLine() is not called, while the OnAfterGetRecord trigger itself continues normally.

Because InsertCFLineForJobPlanningLine() is not executed when skipped, all side effects performed by that procedure are intentionally skipped as well, including:

  • The creation/insertion of the corresponding Cash Flow Worksheet Line.
  • The call to UpdatePlannedDueDate().
  • The subsequent Modify() of the current Job Planning Line.

This is intentional and is the purpose of the requested OnSkip event. The requirement is to prevent the complete InsertCFLineForJobPlanningLine() code path from executing for a specific Job Planning Line, while not changing the remaining flow of the OnAfterGetRecord trigger.

For example, when a Job Planning Line belongs to a project with Status = Completed, the extension should be able to set:

SkipInsertCFLineForJobPlanningLine := true;

The report would then evaluate:

if not SkipInsertCFLineForJobPlanningLine then
    InsertCFLineForJobPlanningLine();

and therefore would not invoke InsertCFLineForJobPlanningLine() for that line.

This is why the event is requested immediately before the procedure call rather than inside or after it. The subscriber needs to make the decision before any of the procedure's side effects occur.

When SkipInsertCFLineForJobPlanningLine remains false (the default), the existing behavior is completely unchanged: InsertCFLineForJobPlanningLine() executes normally, including all of its current side effects.

No partial execution of InsertCFLineForJobPlanningLine() is intended when the procedure call is skipped.

The requested event does not intend to alter the behavior of InsertCFLineForJobPlanningLine() itself; it only provides an extensibility point to decide whether the procedure should be invoked for the current record.

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

    missing-infoThe issue misses information that prevents it from completion.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions