brick-expenses — Expenses
Let people claim what they spent for work in a minute: photograph the receipt, add mileage or a daily allowance by the country's rules, see the company-card transactions waiting for a receipt, and send the claim to their manager. Finance checks it against the policy and the VAT rules, the books get the cost and the reclaimable VAT, and the employee is paid back with the next payment run.
A brick on erp-platform, built from the expenses module spec (v1, signed 2026-10-25), version 0.2.0. Slice A: the expense report workflow end to end
(draft, submit with the gates named, the approval chain from hr fixed at submission, approve level by level with no self-approval under either
login, return as revision n+1, reject, post one journal to gl, ask payments to pay, paid when payments confirms) and hr's leaver intake.
Slice B: every gate of submission (confirmed fields, receipts, policy, card transactions, stale lines), a manager's delegate, company cards (the feed,
matching, personal spend netted from the payout), foreign-currency lines at gl's rate, VAT reclaim through tax's answer and content check, the receipt store,
payout cancel and reissue, mileage and daily allowance by dated country rates, categories/rates/policy/settings as dated rows with the owner's controls, the
receipt reader and its consent, the daily chase, re-billing through ar, and the Odoo slice. See AGENTS.md for the rules, the spec gaps and what is left.
src/brick_expenses/
domain/ the rules, plain Python: report.py (state machine, chain, journal, leaver window), policy.py, cards.py, vat.py, rates.py, receipts.py, ai.py,
reminders.py, rebill.py, odoo.py, values.py, money.py, errors.py
application/ the use cases - each declared once, served as an API route AND an MCP tool; migration.py (the Odoo slice); process.yaml (the signed workflows)
infrastructure/ tables.py (tables + repositories), seams.py (hr, gl, payments, tax, ar, mail: the only place they are reached), objects.py (the receipt
store port), readers.py (the receipt reader port), odoo.py (the Odoo reader port)
app.py the ASGI app: routes, /mcp, the sign-in metadata for agents
roles.yaml the default roles (permissions only) and which are service clients
schema.sql the tables (kept equal to tables.py by tests/test_schema.py)
migrations/ 00001_init .. 00011_migration - {up,down}.sql, journal `exp_schema_migrations`
tests/ rules, one test per story, invariants, both doors on SQLite, seam contract checks, budget, process pin, schema
Run it.
pip install -e '.[test]' # needs erp-platform>=0.9.13,<0.10 (e.g. -e ../erp-platform)
pytest -q # no bucket needed; needs the erp-method checkout next to this repo (or ERP_METHOD_DIR)
BUCKET=my-bucket erp-migrate --schema src/brick_expenses/schema.sql --brick expenses=0.2.0
BUCKET=my-bucket HR_BASE_URL=... GL_BASE_URL=... PAYMENTS_BASE_URL=... TAX_BASE_URL=... AR_BASE_URL=... \
OBJECT_ACCESS_KEY=... OBJECT_SECRET_KEY=... uvicorn brick_expenses.app:app # API at /docs, agents at /mcp
The worker: call POST /outbox/drain and POST /reminders/run (permission exp:journal:post_auto, a service client) on a schedule - the first completes any
stuck approval and sends every pending journal, payout, re-bill and reminder; the second is the daily chase of card receipts and waiting claims. There is no
server-side scheduler. tax, ar, the mail capability and the Odoo reader are optional: each is a reply when absent, never an error.