Extending release behavior without touching codeunit 80
A partner must enforce a rule at sales order release: if the customer is rental-blocked, or the order total exceeds the customer's credit override, the release must stop. The developer proposes commenting out lines in codeunit 80 "Sales-Post" and inserting an IF block, plus a loop over Sales Lines calling Modify on each line to stamp a "Released" flag. Volumes are 800 orders a day at an average of 12 lines. Since the change, users report release taking 6 to 10 seconds, and two orders were released twice, creating duplicate warehouse shipments. The solution is a per-tenant extension containing codeunit 50100 "Rental Mgmt".
1. How should the release check be implemented, and why is modifying codeunit 80 unacceptable?
Read the worked answer
Subscribe to the release event and leave codeunit 80 untouched.
- Why not modify base code: in the cloud the base application is not modifiable at all; even on-premises a base modification is overwritten by every upgrade and blocks the customer from the update cadence. It also prevents other extensions from installing cleanly.
- Where: the release request event for the sales document, raised before the base release logic runs. Subscribe in codeunit 50100 and raise an
Errorwhen the rule fails, which aborts the operation before any posting side effects. - Guarding scope: check the document type and only act where the rule applies, so the subscriber does not slow down unrelated documents. Read what you need with
GetandSetLoadFieldsrather than loading the full record. - Error semantics: use
Errorfor a hard stop; the transaction rolls back and the order stays unreleased. Do not useMessage/Confirmin a subscriber that runs inside a posting transaction unless the override is genuinely intended - and remember the user's answer must be matched by explicit handling logic. - Test the untested path: the rule must also fire for releases triggered by job queue, APIs or other extensions, which is another reason the check belongs in an event subscriber rather than a page action.
Judgment point: identify the correct extensibility point and prove it covers every caller, instead of patching the one code path the tester used.
2. Explain the 6 to 10 second release time and give a concrete fix for the line stamping.
Read the worked answer
The loop calls Modify once per line inside the release transaction. Each Modify is a separate round trip and a separate write, holds locks for the duration of the transaction, and fires table triggers and any subscribers on those triggers. Twelve lines times 800 orders is roughly 9,600 individual writes per day, plus lock contention with anything else touching the same lines.
Fixes:
- Single set-based update where the flag does not depend on per-row logic:
ModifyAllwith the sales document filter, which is one server-side statement. - If per-row logic is genuinely required, use
FindSetwith a key that matches the filter andSetLoadFieldsfor only the modified field, thenModify(false)so validation does not re-run for a purely technical stamp. - Do not open a
Find/Repeatover all lines when a filteredFindSetwill do; and never callModifyinside a loop over a table you are also reading in the outer transaction. - Remove the stamp if it is derived data. If "Released" is inferable from document status, do not persist it - the cheapest write is the one you do not make.
- Measure with the performance profiler on a realistic data set; a 12-line order should not produce 12 statements in the profile.
Judgment point: an operation that was fast on a demo dataset and slow at 800 orders/day is almost always a per-record write pattern.
3. Two orders were released twice and produced duplicate shipments. How do you diagnose and prevent it?
Read the worked answer
Duplicate release means the operation was not idempotent under concurrency or retry.
Diagnosis:
- Check whether the subscriber raises a confirm dialog or a handled flag that allows base logic to run and then the user retries, so two attempts execute.
- Check for double submission: a user double-clicking, an API retry after timeout, or a job queue retry after an error that rolled back partially.
- Check whether the release check is executed outside the transaction that performs the release; if the check locks nothing, two concurrent sessions can both pass the check.
Prevention:
- Lock at the header: read the sales header with a lock (
LockTable) inside the transaction before validating status, so the second session sees the updated status or blocks. - Re-read status at the start of the subscriber and exit if the document is already released - a cheap guard that makes the operation idempotent.
- Let errors roll back the whole transaction rather than catching and continuing; a partially released document is what produces orphan shipments.
- Make external retries safe: the integration that triggers release, if any, should carry an idempotency key so a retry does not create a new shipment.
- Instrument the subscriber with telemetry so a duplicate attempt is visible before a user reports it.
Judgment point: the duplicate is a concurrency and idempotency defect, not a data-entry mistake. The fix is a lock plus a status guard in the same transaction.