Dispatch by Fully Qualified Name
Your customer's operations team keeps inventing nightly clean-up chores — reprocess stuck tickets today, recalibrate meter readings tomorrow — and until now every new chore meant redeploying the processing engine with one more hardcoded case. Business Central 28 ends that: a record can report its own fully qualified name, RecordRef.Open accepts one as text, and so does Codeunit.Run. You will build an engine whose only knowledge of tables and handlers is what a setup row tells it — the binding is data, not code.
Requirements
The starter declares two tables — keep their names, fields and types exactly as given:
"Dispatch Job"— the configuration:Code(Code[20], primary key),"Target Table Name"(Text[250], holds a fully qualified table name),"Handler Name"(Text[250], holds a fully qualified codeunit name),"Current Record ID"(RecordId)."Dispatch Run Log"— the outcome report:"Entry No."(Integer, primary key),"Job Code"(Code[20]),"Target Record ID"(RecordId),Succeeded(Boolean),"Error Message"(Text[2048]).
Implement this procedure in the codeunit "Qualified Dispatch Engine":
procedure RunJob(JobCode: Code[20])Rules:
- Fetch the
"Dispatch Job"identified byJobCode(what happens for a nonexistent job code is not graded). - If the job's
"Target Table Name"cannot be opened as a table, the engine must fail with an error of its own with a message that contains, with the placeholders filled in and the rest exactly as written:Dispatch job <Code> cannot open target table <Target Table Name>— note the casing and spacing — and it must write no log entries for that job. - Otherwise process every row of the target table, one row at a time: set
"Current Record ID"on your in-memory job record to the row's RecordId (no database write is needed), then run the codeunit named by"Handler Name", passing that job record as the run argument. Handlers are ordinary codeunits withTableNo = "Dispatch Job"that locate the row through"Current Record ID"— the grading tests supply them; you write none. - A handler that errors on a row must neither abort the run nor undo the work of rows already processed: record the failure and continue with the next row.
- When
RunJobreturns,"Dispatch Run Log"must hold exactly one entry per processed row:"Job Code"= the job's code,"Target Record ID"= the row's RecordId,Succeeded= whether the handler ran without error,"Error Message"= empty on success, otherwise the error text (truncated to the field length). Give each entry a unique"Entry No."; the numbering scheme itself is not graded.
The engine must not reference any concrete target table or handler codeunit — the grading tests bring their own probe tables and handler codeunits, declared in the AL namespace TryAL.Dispatch, and register them purely as setup data. Pick object IDs in the 50100–50199 range and reference other objects by name, never by ID.
What the tests check
The tests declare two structurally different probe tables and one handler codeunit for each — one flips a ticket's status to PROCESSED, the other doubles a meter's decimal reading (readings are randomized) — then register jobs whose "Target Table Name" comes straight from Record.FullyQualifiedName(), commit the seeded data, and call RunJob. They assert: every row of the named table is transformed by the configured handler; the other table and the other job's log stay untouched; the log holds one Succeeded entry per processed row with an empty "Error Message", linked by "Target Record ID"; a corrupted middle row makes its handler error, yet the run still processes the remaining rows, keeps the earlier rows' work, and logs the failing row with Succeeded = false and the handler's error text inside "Error Message"; and a job pointing at TryAL.Dispatch.NoSuchProbeTable fails with the exact contract error from rule 2 and writes nothing to the log.
Learn More
- Adopting namespaces in AL — what a fully qualified name is and which BC 28 overloads accept one.
- RecordRef.Open(Text) method — opening a table from its qualified name.
- Codeunit.Run(Text) method — running a codeunit from its qualified name, with a record argument and the commit behavior the return value brings.
- Record.FullyQualifiedName() method — how the tests produce the table names they register.
Record.FullyQualifiedName(), a RecordRef.Open overload that takes a Text name, and a Codeunit.Run overload that does too. Wire the happy path first: open the target table from the job's text, loop its rows with FindSet/Next, and run the handler with your job record as the argument.[TryFunction] wrapper around RecordRef.Open turns the platform error into a Boolean, and your own Error call then produces the exact message the contract demands. The same return-value trick on Codeunit.Run (if Codeunit.Run(...) then) is what lets a failing row be recorded instead of killing the run.Codeunit.Run with its return value captured commits on every successful return — and refuses to start at all while your session holds uncommitted writes. So never Insert a log entry (or Modify the job record) between two handler runs: set "Current Record ID" in memory only, buffer the outcomes in a temporary "Dispatch Run Log" record inside the loop, and write the real entries once, after the last row.