erp-brick-payments - Payments & collections
Record what customers pay (typed in or straight from the bank statement), match it to the invoices it settles, refund what is owed back, and chase what is late, so "who owes us what" is always one true answer.
A brick on erp-platform (>=0.9.2,<0.10), built from the payments module spec, version 2 (erp-method-l2c,
modules/payments/module.yaml, signed for build 2026-10-22). Brick name pay, table prefix pay_, permission namespace pay.
src/brick_payments/
domain/ the rules, plain Python: Receipt, Refund, allocation planning, settlement (derived), credit hold,
collections, the baseline matcher, bank files (camt.053 / CSV), the Odoo mapping; values, events, errors
application/ the use cases - each declared once, served as an API route AND an MCP tool (process.yaml = the workflows)
+ migration.py: the Odoo slice (PAY-44, PAY-15)
infrastructure/ tables + one repository per aggregate; seams.py (ar, bp, gl); odoo.py (the reader and partner-map ports); ai_steps.py
app.py the ASGI app: routes, /mcp, the sign-in metadata for agents
schema.sql the tables; migrations/00001_init/{up,down}.sql
roles.yaml the default roles (a recommendation; the brick decides by permission only)
tests/ rules per story, domain, both doors, seams, migration, process, schema, scenarios SC-PAY-01..09 + generated probes
Run it.
pip install -e '.[test]'
pytest -q # no bucket needed: SQLite, with invoicing, bp and the ledger standing in
BUCKET=my-bucket erp-migrate --schema src/brick_payments/schema.sql --brick pay=0.2.1
BUCKET=my-bucket AR_BASE_URL=... BP_BASE_URL=... GL_BASE_URL=... uvicorn brick_payments.app:app # API at /docs, agents at /mcp
erp-compose check tests/tier1.compose.yaml --strict --roles src/brick_payments/roles.yaml
What it asks of its neighbours (infrastructure/seams.py): ar for invoices and credit notes
(GET /invoices?bp_id=&ids=, GET /credit-notes?bp_id=, POST /writeoffs/propose, and for the migration
GET /migration/open-balances), business-partner for GET /partners/{bp_id}, the ledger for
POST /journal-entry/journals. It never reads their tables and never writes ar_ tables. ar's spec names no route paths
yet; the ones above are what this brick expects.
What other bricks ask of it: settlement, open_items, credit_status, credit_ok (a readiness gate
{gate, met, reason, fix} sales-order shows on confirm) and, as machine routes, allocate_from_source (invoicing's credit
notes and write-offs), ingest_provider_payment (a card / direct-debit provider), ingest_statement,
collections_tick, run_odoo_migration.
The Odoo migration reads Odoo through infrastructure/odoo.py (a read-only user: ODOO_URL, ODOO_DB, ODOO_USER,
ODOO_PASSWORD) and business-partner's migration map; ar's migration must have run first.