legal.retainer-drawdown Unreviewed
Apply money held on account in client account to bills in order: what each bill takes, what stays due, what is left.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 11 tests, run in TypeScript, Python and Rust.
Unreviewed. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified solicitor has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
Not professional advice. This capability calculates legal figures from published rules. It is a software component for developers, not legal advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a solicitor review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
Money a client pays on account of costs is held in client account until a bill is delivered; then it may be transferred to pay that bill. This applies the money on account to the bills in the order given, each bill taking as much as it can until the money runs out, and reports what each bill took, what each still has due and what is left on account. Everything is in exact minor units: the applied and outstanding amounts of a bill always add to the bill, and the total applied plus the balance is always the money on account.
The order is the caller's: pass the bills oldest first to pay them in the order they were delivered, which is the usual term in a client care letter. A bill of zero is allowed and takes nothing. A negative bill (a credit note) is an error: credit notes reduce a bill before this step, they do not add money to the account.
For example
retainerDrawdown(£2,000.00, bills ×2)→ bills ×2, applied £2,000.00, outstanding £700.00, balance £0.00 2,000 on account pays the first bill and part of the secondretainerDrawdown(£5,000.00, bills ×2)→ bills ×2, applied £2,700.00, outstanding £0.00, balance £2,300.00 more on account than billed leaves a balanceretainerDrawdown(£2,700.00, bills ×2)→ bills ×2, applied £2,700.00, outstanding £0.00, balance £0.00 exactly enough leaves nothing either way
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
export function retainerDrawdown(onAccount: Money, bills: readonly RetainerBill[]): RetainerDrawdown
| onAccount | Money | money held on account of costs for this client and matter, 0 or more |
| bills | RetainerBill[] | bills delivered, in the order they are to be paid: usually oldest first |
| returns | RetainerDrawdown |
The types it declares, generated into your project
/** A bill the money on account may pay. */
export interface RetainerBill {
readonly reference: string;
/** the bill total, VAT and disbursements included; 0 or more */
readonly amount: Money;
}
/** What happened to one bill. */
export interface RetainerApplication {
readonly reference: string;
readonly billed: Money;
/** taken from the money on account */
readonly applied: Money;
/** still due from the client */
readonly outstanding: Money;
}
/** The money on account after paying the bills. */
export interface RetainerDrawdown {
/** in the order given */
readonly bills: readonly RetainerApplication[];
/** the total transferred from client account */
readonly applied: Money;
/** the total still due on the bills */
readonly outstanding: Money;
/** money left on account */
readonly balance: Money;
}
Your code names it in one line, in the file that uses it
import { retainerDrawdown } from "#fune/legal.retainer-drawdown@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type RetainerApplication, type RetainerBill, type RetainerDrawdown } from "./legal_retainer_drawdown_types.ts";
/** Apply money on account to bills in the order given. */
export function retainerDrawdown(onAccount: Money, bills: readonly RetainerBill[]): RetainerDrawdown {
const currency = onAccount.currency;
if (onAccount.minor < 0) {
throw new RangeError(`onAccount must not be negative, received ${onAccount.minor}`);
}
let left = onAccount.minor;
let applied = 0;
let outstanding = 0;
const lines: RetainerApplication[] = [];
for (const bill of bills) {
if (bill.amount.currency !== currency) {
throw new RangeError(`currency mismatch: ${currency} and ${bill.amount.currency}`);
}
if (bill.amount.minor < 0) {
throw new RangeError(`bill "${bill.reference}" must not be negative, received ${bill.amount.minor}`);
}
const take = Math.min(left, bill.amount.minor);
left -= take;
applied += take;
outstanding += bill.amount.minor - take;
lines.push({ reference: bill.reference, billed: bill.amount, applied: money(take, currency), outstanding: money(bill.amount.minor - take, currency) });
}
return { bills: lines, applied: money(applied, currency), outstanding: money(outstanding, currency), balance: money(left, currency) };
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add legal.retainer-drawdown
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./legal.retainer-drawdown-1.0.1-typescript.fune, or fetch it from a terminal with fune pull legal.retainer-drawdown@1.0.1:typescript.
The whole function, every language, is one file too: legal.retainer-drawdown-1.0.1.fune, 21,835 bytes, sha256 ade1e11d776564d1508d4c2cc41781ad189fcdb156899d63768b15215bb91b9b. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
// fune: before legal.retainer-drawdown
after — your function gets the result and the arguments, and returns the final result.
// fune: after legal.retainer-drawdown
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
// fune: replace money.amount in legal.retainer-drawdown
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show legal.retainer-drawdown --steps.
// fune: step legal.retainer-drawdown after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| 2,000 on account pays the first bill and part of the second | £2,000.00, bills ×2 | → | bills ×2, applied £2,000.00, outstanding £700.00, balance £0.00 |
| more on account than billed leaves a balance | £5,000.00, bills ×2 | → | bills ×2, applied £2,700.00, outstanding £0.00, balance £2,300.00 |
| exactly enough leaves nothing either way | £2,700.00, bills ×2 | → | bills ×2, applied £2,700.00, outstanding £0.00, balance £0.00 |
| the order decides which bill is paid: newest first here | £2,000.00, bills ×2 | → | bills ×2, applied £2,000.00, outstanding £700.00, balance £0.00 |
| nothing on account pays nothing | £0.00, bills ×1 | → | bills ×1, applied £0.00, outstanding £99.99, balance £0.00 |
| no bills keep all the money on account | €750.00, | → | bills , applied €0.00, outstanding €0.00, balance €750.00 |
| a zero bill takes nothing and does not stop later bills being paid | £1.00, bills ×3 | → | bills ×3, applied £1.00, outstanding £0.20, balance £0.00 |
| a single penny short | £999.99, bills ×1 | → | bills ×1, applied £999.99, outstanding £0.01, balance £0.00 |
| a negative amount on account is an error | -£0.01, | → | error: onAccount must not be negative |
| a credit note passed as a bill is an error | £10.00, bills ×1 | → | error: bill "CN-1" must not be negative |
Show the other 1 test
| Case | Arguments | Expected | |
|---|---|---|---|
| a bill in another currency is an error | £10.00, bills ×1 | → | error: currency mismatch |
More from the author
This is arithmetic only. Whether money may be taken at all is a regulatory question. Rule 4.3 of the SRA Accounts Rules (https://www.sra.org.uk/solicitors/standards-regulations/accounts-rules/) requires a bill of costs or other written notification of the costs to be given before client money is transferred to pay them, and the payment to be for the specific sum identified and covered by the money held for that client. Pass only bills that have been delivered, for one client and matter. Whether the last bill may be part-paid from what is left, as this does, or must wait until the whole sum is held, is a question for the firm's compliance officer (COFA); check `outstanding` on each bill and transfer nothing for a part-paid one if your firm reads the rule strictly. All amounts must be in one currency.
## Before you rely on this
**Not professional advice.** This capability calculates legal figures from published rules. It is a software component for developers, not legal advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed above, and have a solicitor review how you use it, before anyone relies on the output. Provided "as is" under its licence, without warranty.
**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified solicitor has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
1.0.1 marks it unreviewed. The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| README.md | 2,760 |
| impl/python.py | 1,501 |
| impl/rust.rs | 2,805 |
| impl/typescript.ts | 1,368 |
| vectors.json | 8,451 |