• Mahmoud Aglan's avatar
    feat(pricing): let a sibling discount apply to one membership tier only · c1c1e9bc
    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: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    c1c1e9bc
SiblingDiscountByMembershipTierTest.php 7.51 KB