-
Mahmoud Aglan authored
Where a collection went in the ledger was a `match` statement: five itemable classes hardcoded to five revenue codes, identical for every client, and no way to express tax at all. An accountant who said "no, five pounds of that is a registration fee and the rest is training, and the kit carries 14% VAT" had no answer but a deploy. Revenue routing makes that answer data. Every kind of money the ERP can take is a source with one rule: a tax treatment, and destination lines that take a flat amount, a percentage, or the remainder. A rule can be narrowed to a single product, programme or event, which beats the academy default for that item alone. financial.revenue-routing edits them, with a preview that runs the unsaved rule through the real allocation engine rather than a second implementation that agrees with it until it doesn't. Three things it will not do: - Book tax as revenue. VAT collected is owed to the Authority, so it comes out first and credits a liability; the service refuses a tax account that is not one. Egypt's 14% is a per-source setting because some services are 10% and some are exempt. - Write an entry that does not balance. Integer piasters throughout, floors everywhere, one line closes the rounding, and assertBalanced() throws before a single row is written — a transaction is immutable, so a wrong one can only be reversed, never corrected. - Change anything on the day it ships. The migration seeds each academy the rule that reproduces its current behaviour exactly, so nothing moves until someone asks it to. The platform fee ships switched off for the same reason. Refunds now reverse the collection's own ledger rows in proportion rather than re-running today's rules — a rule edited last week would otherwise unwind money into accounts it never touched. LedgerAccountResolver::splitRevenue() and revenueCodeForItemable() are deleted rather than deprecated: a second implementation of "where does this money go" is one a caller would eventually reach for, and it would bypass every rule the academy wrote. Verified on a restored oc_sport tenant copy and on a database built from zero: 521 tests, 0 failures on both the Postgres and SQLite suites. PaymentLedgerTest builds a schema without the routing tables, which pins the other half of the safety property — an installation that has not received the migration keeps taking money on the built-in behaviour instead of failing at the till. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
e0b69a71
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| Attendance | ||
| Audit/Models | ||
| Compliance | ||
| Document | ||
| Event | ||
| Facility | ||
| Financial | ||
| HR | ||
| Identity | ||
| Inventory | ||
| Notification | ||
| POS | ||
| Participant | ||
| Portal/Services | ||
| Pricing | ||
| Scheduling | ||
| Shared | ||
| Training | ||
| Website | ||