-
Mahmoud Aglan authored
A compound charges a resident 2,200 and a non-resident 2,800, and the second child of a non-resident 2,400 — while a resident's second child stays at 2,200, because the resident price is already the discounted one. A sibling_order rule could say "second child" but not "and only for non-residents", and the arithmetic has no shortcut: a flat 400 off takes the resident to 1,800, and a percentage landing on 2,400 from 2,800 lands on 1,886 from 2,200. The config-only alternative was a second stackable rule holding fixed_price 220000 to push residents back up. That works arithmetically and is the wrong answer: it stores the tier's base price in a second table. The day fees rise and only base_prices is edited — the obvious place — that rule silently forces every resident back to the old number, with no error and no missing-price failure to notice. It also prints a +200 EGP "discount" line on a resident sibling's invoice, because appliedRules is stored verbatim on it. So sibling_order and family_size gain an optional membership_type list in their conditions, evaluated as an AND alongside the existing range. conditions is jsonb with no CHECK constraint, so no migration is needed. The key is absent from every rule authored before now, isset() is false, and evaluation is bit-identical for every existing rule on every tenant — additive in the same sense a nullable column is. It is accepted only where the schema declares it; normalize() strips it from an age or loyalty rule rather than storing a condition the engine will never consult. describe() appends the tier to the existing clause so the picker's rejection reason stays a sentence. Pinned by 13 tests, the first of which is the one that matters to the other tenants: absent key, unchanged behaviour. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
c1c1e9bc