-
Mahmoud Aglan authored
The first pass put programmes, products, prices, promotions, warehouses and receipt templates in the *shared* bucket, where `branch_id IS NULL` means "every branch uses this row". The reasoning was that a missing base price is a hard failure that stops a sale, so hard-filtering the catalogue risked leaving a branch unable to sell anything. That bought safety with the wrong currency. A branch is meant to read as its own installation — its own programmes at its own prices, its own products, its own stores — and a programme offered at one branch turning up in another branch's dropdown is the same bug as a group doing it. The shared bucket just hid it behind a plausible-sounding rule. The data said the caution was unnecessary. Across the live tenant no group points at a programme in another branch, no base price prices a programme in another branch, and exactly three catalogue rows had no branch at all. The catalogue was already per-branch in practice and merely unlabelled. - Programmes, base prices, pricing rules, promotions, products, product categories, kits, warehouses, receipt templates and wallets move to the strict bucket. `kits` and `product_categories` gain the column; the rest only needed their nulls resolved, from actual usage where a link existed and from the main branch otherwise. - Events go the other way and lose the trait entirely, with their registrations. An event is an academy-wide occasion and is genuinely not per branch. The column stays — dropping it would be destructive — but nothing reads it. - Only people and the academy calendar stay shared: employees, trainers, guardians, holidays. A coach who works two pitches needs one record visible from both, not two that drift. Even there the nulls are narrowed — anyone who demonstrably belongs to one branch is pinned to it, which in this tenant leaves none shared at all. - Pricing rules are the one model whose branch is genuinely many-valued: the wizard targets a list through `pricing_rule_branches` and deliberately leaves the legacy column null. A column scope would have hidden every rule it has ever created, so PricingRule supplies its own scope reading the pivot — and BelongsToBranch now lets a model do that. - Forms that let a user save a catalogue row with no branch now require one, and the "كل الفروع" option is gone from those pickers: on a strictly scoped table that choice does not mean every branch, it means none. The setup wizard's seeded prices are filed against their programme's branch rather than null, so a new academy does not finish setup unable to sell. - Comments throughout said "SHARED — branch_id NULL means every branch uses this row". They now say what is true. tests/Feature/PricingSurvivesBranchScopeTest.php is the guard on the original worry: it prices every live enrolment inside its own branch on each run. It passes, and reports the one programme that has no active base price at all — a pre-existing gap, unrelated to scoping. Branch suites: 14 tests / 23,060 assertions against a restored tenant. Standard suite: 224 tests, no failures. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
d3cbd5f1