Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[Bug]: Subcontracting: SetIsSubcontracting calls CurrPage.Update() with SaveRecord = true from OnAfterGetCurrRecord, aborting the Transfer Order page

Open Beginner friendly
#12,063 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Domain
frontend

Research direction

Inspect src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderSub.PageExt.al at SetIsSubcontracting and its caller in SubcTransferOrder.PageExt.al. Confirm the page refresh does not save the record, then replay 00_Repro_Setup.yml and 01_Repro_R1_Release.yml on the stated Business Central environments; Release and Create Whse. Shipment should complete without either Transfer Line error.

Written by the indexing model from the issue text.

Description

Team: SCM
Describe the issue

On the Transfer Order page, Subc. Trans. Order Sub. (PageExtension 20529) forces a saving page update from inside a record-fetch trigger. That aborts the page during the normal transfer document flow.

The statement

https://github.com/microsoft/BCApps/blob/1a7e9d7b07f85189c2f6809b97f6faf40d9ba34f/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransOrderSub.PageExt.al#L179-L183

internal procedure SetIsSubcontracting(IsSubcontractingRelated: Boolean)
begin
    HasSubcontractingContext := IsSubcontractingRelated;
    CurrPage.Update();          // SaveRecord defaults to true
end;

CurrPage.Update() with no argument is CurrPage.Update(true), so it saves the record. The procedure only assigns a visibility Boolean, so there is nothing that needs saving.

The caller

https://github.com/microsoft/BCApps/blob/1a7e9d7b07f85189c2f6809b97f6faf40d9ba34f/src/Apps/W1/Subcontracting/App/src/Transfer/SubcTransferOrder.PageExt.al#L98-L107

trigger OnAfterGetCurrRecord()
begin
#if not CLEAN29
    if not SubcontractingEnabled then
        exit;

#endif
    ShowSubcontractingFactBox := SubcTransferManagement.IsSubcontractingTransferDocument(Rec);
    CurrPage.TransferLines.Page.SetIsSubcontracting(ShowSubcontractingFactBox);
end;

OnAfterGetCurrRecord fires on every current-record refresh, including moments when the subform line is not in a clean committed state. The forced save then fails in one of two ways:

  • Brand-new subform line, Line No. not yet assigned. The platform resolves the save as a rename, which reaches Table 5741 "Transfer Line".OnRename - an unconditional Error. A Transfer Line can never be renamed, so the abort is guaranteed.
  • Existing line whose page copy is older than the stored record. Optimistic concurrency rejects the write.
Observed errors

Business Central 29.0.54011, India (IN) localisation, clean sandbox, no third-party extensions installed.

Variant A - uncommitted line:

You cannot rename a Transfer Line
Microsoft.Dynamics.Nav.Runtime.FormAbortException: Page Transfer Order has to close

Variant B - committed line, stale page copy:

The changes to the Transfer Line record cannot be saved because some information on the
page is not up-to-date. Close the page, reopen it, and try again.
Identification fields and values: Document No.='1006', Line No.='10000'
Call stack

Copied from the client's Share details pane. Identical for both variants, two frames, both in this extension.

"Subc. Trans. Order Sub."(PageExtension 20529).SetIsSubcontracting line 3
  - Subcontracting by Microsoft version 29.0.54011.54955
"Subc. Transfer Order"(PageExtension 20526).OnAfterGetCurrRecord(Trigger) line 8
  - Subcontracting by Microsoft version 29.0.54011.54955

SetIsSubcontracting line 3 is the CurrPage.Update() statement. The trigger's reported line 8 does not line up with line 8 of the source above because the #if not CLEAN29 directives are compiled out of the shipped artifact; line 8 in the compiled object is the SetIsSubcontracting call.

Image

IN localisation, no third-party extensions. Release itself succeeded here and the failure landed on the next action, which is why we say the trigger, not the action, is the cause.

One more observation

The subform's own OnAfterGetCurrRecord guards itself against repeated work:

local procedure SetSubcontractingVisibility()
begin
    if Rec."Document No." = TransferHeader."No." then
        exit;

The call arriving from the parent page has no equivalent guard, so SetIsSubcontracting and its forced save run on every refresh.

Expected behavior

Adding a line to a Transfer Order and then invoking Release, or Create Whse. Shipment, should complete normally: the order is released and the warehouse shipment is created. No extension should force a saving page update from a record-fetch trigger.

SetIsSubcontracting assigns a visibility Boolean and nothing else, so it should refresh the page without writing the record:

 internal procedure SetIsSubcontracting(IsSubcontractingRelated: Boolean)
 begin
     HasSubcontractingContext := IsSubcontractingRelated;
-    CurrPage.Update();
+    CurrPage.Update(false);
 end;

CurrPage.Update(false) redraws the page and re-evaluates the Visible properties bound to HasSubcontractingContext, which is all this procedure needs. It cannot trigger a write, so neither failure mode can occur.

Steps to reproduce

About two minutes. No item tracking, no extensions, no special configuration.

  1. Provision a Business Central 29.0.54011 sandbox on the IN (India) localisation, standard CRONUS, with no third-party apps installed.
  2. Open Transfer Orders and choose New.
  3. Set Transfer-from Code, Transfer-to Code and In-Transit Code. On CRONUS IN: EAST, WEST, OUT. LOG.
  4. On the lines subform, enter an Item No. (for example 1896-S) and Quantity 10.
  5. Without changing focus after typing the quantity, click Release.
  6. Then click Create Whse. Shipment.

The error appears at step 5 or step 6. Which one you get depends only on whether the subform line had already been committed when the trigger fired; both come from the same code path and produce the same call stack.

Tested on artifacts 29.0.54011.54884, .54955, .55000 and .55007. Present on IN in all of them, including the newest.

Additional context
Why it surfaces only on IN

We ran the same automated suite of 1435 tests twice from one pipeline, minutes apart, against the same artifact sandbox/29.0.54011.55007, with the localisation as the only difference:

Localisation Result
W1 / base 1435 tests, 0 failures
IN 1435 tests, 6 failures - every one with the error and call stack above

Same split on artifact .54884. We also reproduced it by hand in the web client on IN and confirmed it passing on W1, using byte-identical BC Page Scripting recordings that create every object they touch, which rules out demo data and configuration.

Image

W1 / base. The same two recordings, byte for byte, complete without errors.

The Subcontracting app is installed on both localisations - on W1 it is listed in Extension Management as published Global and installed - so the presence of the app is not the discriminator.

Our reading of the gate

The likely gate is the #if not CLEAN29 guard in PageExtension 20526:

#if not CLEAN29
    if not SubcontractingEnabled then
        exit;
#endif

SubcontractingEnabled comes from Subc. Feature Flag Handler.IsSubcontractingEnabled(), which returns not ManufacturingSetup."Legacy Subcontracting". So on a company where Legacy Subcontracting is still on, the trigger exits at the guard and the bug is invisible; where it is off, the forced save runs. That would make this a demo-data difference rather than localised code.

We have not confirmed the flag's value on each localisation, so please treat that as our reading rather than as established.

It also means the guard disappears entirely once CLEAN29 is defined, at which point the forced save runs unconditionally on every localisation.

Attached: the Page Scripting recordings

I have attached the two BC Page Scripting recordings we used -
00_Repro_Setup.yml
01_Repro_R1_Release.yml

File What it does
00_Repro_Setup.yml Creates everything the repro needs, from base functionality only: an item tracking code, an item, and three locations (from, to, and in-transit). Run once per container.
01_Repro_R1_Release.yml Creates the transfer order and its line, invokes Release and then Create Whse. Shipment, and validates the resulting warehouse shipment line.

Both use base objects only and create every object they touch, so the same two files run unchanged on any localisation with no demo-data dependency. Import them through the Page Scripting pane in the web client (Preview feature) and replay.

On IN the second script stops at Release or at Create Whse. Shipment. On W1 it runs to the end with every validation passing. That is the comparison shown in the two screenshots above.

Impact

Releasing a transfer order and creating its warehouse shipment is core inventory functionality, and this reproduces with a stock item, stock locations, no item tracking and no extensions, so it is not configuration-dependent. It also breaks any partner or ISV extension that drives the Transfer Order page on an affected company.

We have the two BC Page Scripting .yml recordings that reproduce it in a single run, plus the full client error details including session and activity IDs. Happy to attach any of it on request.

I will provide a fix for a bug
  • I will provide a fix for a bug
Dominant language
AL
Stars
683
Forks
459
Avg merge
2d 21h
Merged PRs (30d)
510

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/BCApps

All issues in microsoft/BCApps

Similar issues

More Web Dev issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.