feat: add obligation primitive — unified per-entity AR/AP financial model #14

Merged
john merged 1 commit from feat/obligation-primitive into dev 2026-08-03 07:21:46 +00:00
Owner

Summary

Introduces an obligation primitive that unifies AR (invoices) and AP (timesheets) under a single per-entity financial model.

New models

  • Obligation — atomic financial record: entity_id, direction (receivable/payable), amount/currency, source_type+source_id, status lifecycle (outstanding → partial → settled → void)
  • Settlement — immutable payment record against an obligation

Lifecycle wiring

  • Invoice confirmed → creates receivable obligation
  • Timesheet approved → creates payable obligation
  • Settlement recomputes obligation status; settling a receivable auto-sets invoice.status = paid
  • Reversing settlements walk invoice back from paid to sent/confirmed

Entity reference normalization

  • client_id → entity_id on Invoice (49 occurrences including 2 raw SQL)
  • contractor_entity_id → entity_id on Timesheet
  • client_id dropped from BillingProfile
  • Seed script updated with kernel entity UUIDs for all 6 billing profiles

Removed

  • Payable model (redundant with payable-direction Obligation)
  • Payment model (replaced by Settlement)
  • 8 endpoints removed (pay, payments, all payables)

New endpoints

  • GET/POST /obligations, GET /obligations/{id}
  • POST /obligations/{id}/settle, POST /obligations/{id}/void
  • GET /entities/{entity_id}/position

Migration

Single migration 003 (a3f7c2d91b45): creates obligation+settlement tables, renames entity columns, adds SoftDeleteMixin to timesheet+vendor_payment_account, drops payment+payable tables.

Judgment calls

  • confirm() rejects invoices with no entity_id (400) — obligation requires it
  • P&L moved to ObligationService (obligation-derived, avoids duplication)
  • source_type is str not enum in schemas (extensible)
  • vendor_payment_account_id on Obligation carries a real FK

Deploy notes

  • Run seed script after deploy to backfill billing_profile.entity_id
  • Statement of accounts generation depends on entity_id being populated
## Summary Introduces an **obligation** primitive that unifies AR (invoices) and AP (timesheets) under a single per-entity financial model. ### New models - **Obligation** — atomic financial record: entity_id, direction (receivable/payable), amount/currency, source_type+source_id, status lifecycle (outstanding → partial → settled → void) - **Settlement** — immutable payment record against an obligation ### Lifecycle wiring - Invoice confirmed → creates receivable obligation - Timesheet approved → creates payable obligation - Settlement recomputes obligation status; settling a receivable auto-sets invoice.status = paid - Reversing settlements walk invoice back from paid to sent/confirmed ### Entity reference normalization - client_id → entity_id on Invoice (49 occurrences including 2 raw SQL) - contractor_entity_id → entity_id on Timesheet - client_id dropped from BillingProfile - Seed script updated with kernel entity UUIDs for all 6 billing profiles ### Removed - Payable model (redundant with payable-direction Obligation) - Payment model (replaced by Settlement) - 8 endpoints removed (pay, payments, all payables) ### New endpoints - GET/POST /obligations, GET /obligations/{id} - POST /obligations/{id}/settle, POST /obligations/{id}/void - GET /entities/{entity_id}/position ### Migration Single migration 003 (a3f7c2d91b45): creates obligation+settlement tables, renames entity columns, adds SoftDeleteMixin to timesheet+vendor_payment_account, drops payment+payable tables. ### Judgment calls - confirm() rejects invoices with no entity_id (400) — obligation requires it - P&L moved to ObligationService (obligation-derived, avoids duplication) - source_type is str not enum in schemas (extensible) - vendor_payment_account_id on Obligation carries a real FK ### Deploy notes - Run seed script after deploy to backfill billing_profile.entity_id - Statement of accounts generation depends on entity_id being populated
feat: add obligation primitive — unified per-entity AR/AP financial model
All checks were successful
CI / Backend (pull_request) Successful in 6s
CI / Docker Build (pull_request) Successful in 17s
CI / Deploy (pull_request) Has been skipped
CI / Detect Changes (pull_request) Successful in 9s
a8ea0b518c
- Add Obligation model (receivable/payable direction, status lifecycle)
- Add Settlement model (replaces Payment, immutable financial records)
- Normalize entity references (client_id, contractor_entity_id -> entity_id)
- Wire invoice confirm -> receivable obligation creation
- Wire timesheet approve -> payable obligation creation
- Add per-entity financial position endpoint
- Remove Payable model (replaced by payable-direction Obligation)
- Remove Payment model (replaced by Settlement)
- Add SoftDeleteMixin to Timesheet, VendorPaymentAccount
- Migration 003: schema changes + new tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CodKV6RRdtrSsNcNaXtYao
john merged commit ee8433f1f6 into dev 2026-08-03 07:21:46 +00:00
john deleted branch feat/obligation-primitive 2026-08-03 07:21:46 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
haskytech/haskos-finance!14
No description provided.