dodil/erp-brick-payments

DODIL ERP brick: payments

README.md

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.