feat(subs): admin can correct a subscription's plan VERSION
A subscription froze its planVersionId at sale (reproducible pricing). There was no way to move a sold sub onto a different VERSION of the SAME plan — needed when an admin publishes v2 with different timeframes (e.g. mujor-naten-cdo-dite v1 "every day" → v2 "weekdays only") and wants an existing subscriber on it, or back on v1. Backend (PUT /api/subscriptions/:id): - accept planVersionId; honored only with the subscription:plan permission (stronger than subscription:update — a plan-management action). Non-privileged caller sending a change → 403, not silently dropped. - validated to belong to the sub's EXISTING planId (a different plan = a different price basis = a re-sale → 400). - price/currency/period/planId stay frozen; only planVersionId moves. The swap is server-logged for audit (the row is mutable master data, not on the ledger). Past signed entry/exit events keep their own windowTariffVersionId, so history reprices identically — only future access uses the new version's windows. Frontend (SubscriptionManager): - pass the session user through the route (like RolesManager). - admin-only "Versioni" picker in the edit modal: lists every version of the sub's plan by effective date + a timeframe summary (days + window, or 24/7), current pre-selected. The plan itself stays read-only. Sends planVersionId only when it changed. - i18n: subs.version/versionHint/versionCurrent/versionOnlyOne/everyDay/allDay in both sq + en. Verified on a writable DB copy: version changed, price + planId frozen, cross-plan version rejected. Live DB untouched. build+lint 14/14. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -57,6 +57,18 @@ mutates an old one — past sales keep their recorded `planVersionId` and repric
|
||||
sale, plus `planId` + `planVersionId` (which version priced it — reproducible, like a payment's
|
||||
`tariffVersionId`). An **update never re-sells** (price/plan frozen); a new price = a new sale.
|
||||
|
||||
> **Version correction (admin, built 2026-06-21).** The one update that may move `planVersionId`:
|
||||
> an admin can re-point a sub to a **different VERSION of its SAME plan** — e.g. v2 changed the
|
||||
> timeframes (`days [0–6]` → weekdays-only) and an existing subscriber should be on it, or back on v1.
|
||||
> `PUT /api/subscriptions/:id` accepts `planVersionId`, **gated on `subscription:plan`** (plan-mgmt,
|
||||
> stronger than `subscription:update`; a non-privileged caller is 403'd, not silently ignored). It is
|
||||
> validated to belong to the sub's existing `planId` (a different plan = a different price basis = a
|
||||
> re-sale, refused with 400). **Price/currency/period stay frozen** — only the access rules change,
|
||||
> and only going forward (past signed `vehicle_entry`/`exit` events keep their own frozen
|
||||
> `windowTariffVersionId`, so history reprices identically). The swap is server-logged for audit (the
|
||||
> `subscriptions` row is mutable master data, not on the signed ledger). UI: an admin-only "Version"
|
||||
> picker in the edit modal, listing every version of that plan by effective date + timeframe summary.
|
||||
|
||||
> **Superseded — per-row typed price (built 2026-06-18).** Originally each subscription stored its own
|
||||
> `priceMinor` + `period:"monthly"`, typed by the operator and pre-filled from
|
||||
> `site_config.subscription_monthly_price_minor`. That column is **kept only to seed a "Monthly" plan**
|
||||
|
||||
+11
@@ -1245,3 +1245,14 @@ typographic chars (— ⚠ … ' ") transliterate to ASCII instead of "?". `wind
|
||||
kept as deprecated read-only in `LedgerPayload` for historic events. Verified live model on a DB copy
|
||||
(13:21→14:30 = 200 ALL; 19:55-grace→23:00 = 0; 19:00→21:30-cross = 100 ALL). build+lint 14/14, shared
|
||||
87/87. Existing signed occurrences left untouched (immutable). See [[subscription]] tariff-bridge-history.
|
||||
|
||||
## [2026-06-21] feat | Subscription plan-version correction (admin)
|
||||
Added an admin-only path to move an existing [[subscription]] to a different VERSION of its SAME plan
|
||||
(e.g. v1 "every day" → v2 "weekdays only" of mujor-naten-cdo-dite). PUT /api/subscriptions/:id now
|
||||
accepts planVersionId, gated on subscription:plan (403 for non-privileged), validated to share the
|
||||
sub's existing planId (cross-plan = 400 — that'd be a re-sale). Price/currency/period stay frozen;
|
||||
only the access rules change going forward (past signed events keep their own windowTariffVersionId).
|
||||
Server-logged for audit. UI: admin-only "Versioni" picker in the edit modal, listing every version by
|
||||
effective date + timeframe summary, current pre-selected. Verified on a writable DB copy: version
|
||||
changed, price + planId frozen, cross-plan rejected. build+lint 14/14, i18n parity (sq+en). Live DB
|
||||
untouched. See [[subscription]] "Version correction".
|
||||
|
||||
Reference in New Issue
Block a user