0107

Take the Deposit First

prepayment
sales-order
posting
dimensions

Take the Deposit First

Nobody starts a bespoke build without money on the table. Business Central calls that money a prepayment: you tell the sales order what share of it is a deposit, invoice that share on its own, and when the goods finally ship the deposit comes off the final bill automatically. Doing it from the Sales Order page is three clicks. Doing it from code — and getting the deposit to land on the right account, on the right lines, and off the right invoice — is this task.

Write one codeunit named "Sales Deposit Manager". The grading tests bind to these four signatures character for character, so copy them exactly:

procedure SetDeposit(var SalesHeader: Record "Sales Header"; DepositPct: Decimal; CompressLines: Boolean)
procedure PostDeposit(var SalesHeader: Record "Sales Header"): Code[20]
procedure PostFinalInvoice(var SalesHeader: Record "Sales Header"): Code[20]
procedure RemoveLine(var SalesLine: Record "Sales Line")

Pick an object id in the range 50100–50199, and reference every other object by name, never by id.

What the tests hand you

The tests own the fixture; your code never creates master data. Before each call they build a customer, a G/L account to sell, a general posting setup whose "Sales Prepayments Account" is a real G/L account, and a sales document of type Order with one or two G/L Account lines whose line amounts are whole hundreds. Nothing on the order is prepaid yet: "Prepayment %" is zero and the lines' prepayment fields are empty.

Requirements

  1. SetDeposit puts DepositPct on the order and on each of its lines: afterwards every line's "Prepayment %" is DepositPct, and its "Prepmt. Line Amount" is that percentage of the line amount, rounded to the cent. It also records CompressLines in the order's "Compress Prepayment" field. All of it must reach the database — the tests re-read the order and its lines from the database, not from the record variable they passed in.
  2. PostDeposit posts the order's prepayment invoice and returns the number of the posted prepayment invoice. The posted document must be a real prepayment invoice raised against this order ("Prepayment Invoice" set, "Prepayment Order No." pointing back at the order), its amount must land on the general posting setup's "Sales Prepayments Account" as a credit for the deposit excluding VAT, and every order line must come back carrying its own share of the deposit in "Prepmt. Amt. Inv.". The order itself survives — this is an invoice for part of it, not the end of it.
  3. PostFinalInvoice ships and invoices the whole order in one go and returns the number of the posted sales invoice. The customer ledger entry behind that invoice must charge the order total including VAT minus the deposit already invoiced, to the cent.
  4. RemoveLine takes a line off the order. A line whose deposit has already been invoiced may not be removed: the attempt must fail with an error that names the field Prepmt. Amt. Inv., and the line must still be on the order afterwards. A line with nothing invoiced on it is simply removed, leaving the rest of the order alone.
  5. Neither posting routine may commit. Most grading tests run inside a single transaction that is rolled back at the end, so a commit performed by your code — or by the platform code your code calls — fails them outright with a commit-not-allowed error.

Two things the tests take care of, so don't build them yourself: posting a deposit moves the order into the Pending Prepayment status, and the tests reopen the order themselves before they try to remove a line.

What the tests check

Eleven tests walk the round trip: a generated deposit percentage reaching the header and both lines with the right "Prepmt. Line Amount" on each; CompressLines = false landing in "Compress Prepayment"; PostDeposit returning a posted document flagged as a prepayment invoice for this order; the sales prepayments account credited with exactly the deposit excluding VAT; "Prepmt. Amt. Inv." on each line equal to that line's share; the final invoice's customer ledger entry equal to the order total including VAT less the deposit. Three compression cases: two lines sharing the prepayment account and the dimensions collapse into one prepayment line carrying both deposits, the same two split in two once each carries a different dimension value, and without compression every order line always gets its own prepayment line. Two removal cases: a prepaid line refused with an error naming Prepmt. Amt. Inv. and still on the order afterwards, and a line with nothing invoiced on it gone while its neighbour is untouched. Amounts are compared exactly, except the final invoice total, which is allowed one cent of rounding.

Learn More

Hint 1
Prepayments are a first-class part of the sales order, not something you calculate on the side: the header carries the deposit settings and every line carries its own share of the deposit. Changing the deposit percentage on the header from code is what pushes the new share down onto the lines — the same thing the Sales Order page does when it asks whether to update the lines.
Hint 2
A prepayment invoice is not posted by the codeunit that posts the order; it has its own posting codeunit, which also takes the sales header and leaves the order alive. Both posting codeunits expose the same switch that lets them run inside a caller's transaction instead of committing on their own, and both hand the posted document number back on the header afterwards. For the removal, remember that a table's delete trigger is where Business Central keeps its own guards.
Hint 3
"Sales-Post Prepayments": SetSuppressCommit(true), then Invoice(SalesHeader); the posted number comes back in the header's "Last Prepayment No.". For the final bill, set Ship and Invoice on the header, suppress the commit on "Sales-Post" as well, run it, and read "Last Posting No.". Deleting a sales line with its trigger running raises the prepayment guard for free.
ALBusiness Central 28.5
Press Compile to check your code compiles — Submit runs the tests.
The code editor is desktop-only
Open this problem on a computer to write and run code. Reading the description, tests and discussion works fine here.