P0 HOTFIX: Subscription asset_limit calculation fix

Root cause: Old code used rules.get('max_vehicles') which only searched
JSONB root level. Corporate tiers store limits under rules['allowances'].

Changes:
1. Added _extract_limit() helper (3-level search: direct → allowances → limits)
2. Replaced single-sub .limit(1) with multi-sub .all() for true aggregation
3. Fixed fallback path to use _extract_limit() instead of rules.get()
4. Added clamping: asset_limit = max(asset_limit, 1) right before
   SubscriptionSummary creation on BOTH code paths
5. Added 'type' column to SubscriptionTier model (base vs addon)
This commit is contained in:
Roo
2026-06-28 12:09:56 +00:00
parent 1e6f79ca22
commit f2935cbd64
4 changed files with 755 additions and 40 deletions

View File

@@ -823,3 +823,130 @@ Detailed fix spec written to `docs/admin_garage_subscription_tab_bugfix_spec.md`
- `sync_engine.py`: 1278 elements OK — system perfectly in sync
- `test_subscription_feature_flags.py`: All 6 steps passed (login, feature flags, check, public tiers)
- Python syntax check: ✅ Syntax OK
## 2026-06-27 - P0 CORE ARCHITECTURE: Multi-Subscription Aggregation (Base + Add-ons)
### 🎯 Cél
"1 Base Tier + N Add-on Tiers" modell implementálása. A rendszer most már több aktív előfizetést kezel szervezetenként, és aggregálja a korlátokat (asset_limit, branch_limit, user_limit) a Base Tier Rules + Base Extra Allowances + SUM(Add-on Tier Rules) forrásokból.
### 🔧 Módosított fájlok
**1. `backend/app/models/core_logic.py`**`SubscriptionTier.type` oszlop hozzáadása
- Új `type: Mapped[Optional[str]]` mező (`String(20)`, nullable, default NULL)
- 'base' = core tier, 'addon' = add-on tier, NULL = backward compat (base)
**2. `backend/app/api/v1/endpoints/admin_organizations.py`** — Multi-subscription aggregation
- `SubscriptionSummary.active_addons: List[str]` mező hozzáadása
- `get_organization_details`: `.limit(1)` eltávolítva, minden aktív sub lekérése
- Base vs Add-on szeparáció: `sub.tier.type` és `rules.get("type")` alapján
- Aggregált limit számítás: `asset_limit = base_vehicles + extra_vehicles + addon_vehicles`
- `max(asset_limit, 1)` fallback csak a végösszegre alkalmazva
- `list_organizations`: bulk query-ben is base/addon szeparáció
### ✅ Verifikáció
- Sync Engine: ✅ 1279 OK, 0 Fixed, 0 Extra
- Python syntax: ✅ Mindkét fájl hibátlanul compile-ol
- Subscription feature flags test: ✅ ALL TESTS COMPLETED
## 2026-06-27 - P0 HOTFIX: Subscription PATCH 422 Error & Add-on Only Update
### 🎯 Cél
Két bug javítása a Subscription PATCH flow-ban:
1. **Backend 422 Error**: `tier_id` kötelező volt a `OrgSubscriptionUpdate` sémában, így add-on-only mentéskor Pydantic validation hibát dobott.
2. **Add-on-only mód**: Ha `tier_id` nincs megadva, a meglévő aktív előfizetés `extra_allowances` és `valid_until` mezői frissüljenek, ne pedig új sor jöjjön létre.
### 🔧 Módosított fájlok
- `backend/app/api/v1/endpoints/admin_organizations.py`
- **Schema** (47-68. sor): `tier_id: int = Field(...)``tier_id: Optional[int] = Field(default=None, ...)`
- **Endpoint** (1067-1240. sor): Két üzemmódra bontás:
- **A) tier_id megadva**: Teljes csomagváltás (meglévő logika)
- **B) tier_id = None**: Aktív előfizetés `extra_allowances` és `valid_until` frissítése `sa_update`-dal
- `frontend_admin/pages/garages/[id]/index.vue` — Nem volt szükség módosításra, a frontend payload konstrukciója már helyes volt.
### ✅ Verifikáció
- Python syntax: ✅ Sikeres compile
- Sync Engine: ✅ 1279 OK, 0 Fixed, 0 Extra — teljes szinkronban
## 2026-06-27 13:16 UTC — P0 HOTFIX: 422 Tier ID & Missing Expiration Payload
**Gitea Card:** #302
**Problem:** PATCH /admin/organizations/{org_id}/subscription returned 422 when tier_id was omitted (add-on-only update). Frontend omitted expires_at when no base tier selected.
**Root Cause (BACKEND):** `OrgSubscriptionUpdate.tier_id` was `int = Field(...)` (required). Changed to `Optional[int] = Field(default=None)`. Endpoint split into Mode A (tier_id present → full switch) and Mode B (tier_id=None → add-on/expiry update).
**Root Cause (FRONTEND):** Button disabled on `!selectedTierId` only. Payload construction didn't handle tierless updates. Fixed: linear payload, `addonsChanged` computed, always send extra_allowances.
**CRITICAL Deployment Fix:** Code changes were on disk but containers were NOT restarted:
- sf_api (23h uptime, uvicorn without --reload)
- sf_admin_frontend (24h uptime)
Both restarted at 13:13 UTC to apply fixes.
**Verification:** All 4 Pydantic schema validation tests PASS.
## 2026-06-27 - P0 Hotfix: 500 Internal Server Error on GET /admin/organizations/{id}/details
### 🎯 Cél
A garázs részletek betöltésekor (GET /api/v1/admin/organizations/43/details) 500-as hiba jelentkezett. A korábbi hotfix (422 Tier ID fix) során hozzáadott lazy importok hibás elérési útvonalakat tartalmaztak.
### 🔧 Javítások (admin_organizations.py)
1. **Line 490:** `from app.models.fleet.vehicle import Vehicle``from app.models import Vehicle` (Asset class a `app.models.vehicle.asset`-ben, `Vehicle` néven exportálva a `app.models`-ből)
2. **Line 492:** `Vehicle.organization_id``Vehicle.current_organization_id` (Asset modellben nincs `organization_id`, helyette `current_organization_id`)
3. **Line 493:** `Vehicle.is_deleted == False` törölve (Asset modellben nincs `is_deleted` mező)
4. **Line 499:** `from app.models.fleet.organization import Branch``from app.models.marketplace.organization import Branch` (Branch a marketplace sémában van)
5. **Line 155 (BranchBrief.id):** `int``str` (Branch elsődleges kulcsa UUID, nem integer)
6. **Line 507:** `b.id``str(b.id)` (UUID konvertálása stringgé)
7. **Line 510:** `b.is_active``not b.is_deleted` (Branch modellben nincs `is_active`, helyette `is_deleted` boolean)
### ✅ Verifikáció
- `GET /api/v1/admin/organizations/43/details`**200 OK** (34 mezővel, köztük branches, members, subscription)
- Minden import sikeresen betöltődik
## 2026-06-27 - P0 JSONB NESTING HOTFIX: Allowances Object & i18n Keys
### 🎯 Cél
Fix the `_extract_limit` helper to also search inside a nested `"allowances"` dict within the tier rules JSONB, and add missing i18n keys for the add-on input sub-labels.
### 🔧 Változtatások
**1. Backend — Nested JSONB lookup**
- Fájl: `backend/app/api/v1/endpoints/admin_organizations.py` (line 601)
- Módosítás: `_extract_limit()` helper now checks `rules.get("allowances", {})` after checking root-level keys.
- Hatás: Tier limits stored as `{"allowances": {"max_vehicles": 20}}` are now correctly extracted.
**2. Frontend — Missing i18n keys**
- Fájlok: `frontend_admin/i18n/locales/en.json`, `frontend_admin/i18n/locales/hu.json`
- Hozzáadva: `garages.details.branches` ("branches"/"telephely") és `garages.details.employees` ("employees"/"dolgozó")
- Hatás: A `+db {{ $t('garages.details.branches') }}` és `+db {{ $t('garages.details.employees') }}` feliratok már nem jelennek meg nyers kulcsként.
### ✅ Verifikáció
- Python syntax check: ✅ OK
- Sync engine: 1279 OK, 0 Fixed, 0 Shadow — teljes szinkronban
## 2026-06-27 - P0 ARCHITECTURE UNIFICATION: Eradicate Path A & Fix Limit(1)
### 🎯 Cél
A backend `admin_organizations.py` fájlban két külön kódút (Path A és Path B) számolta a subscription limiteket. Path A (a `get_organization_details` végpontban) még a régi `rules.get("max_vehicles")` logikát használta ahelyett, hogy az `_extract_limit()` helpert hívta volna, ami a JSONB `allowances` nested objektumba is belenéz. Emellett a subscription query `.limit(1)`-et használt, ami megtörte a multi-subscription (Base + Add-on) aggregációt.
### 🔧 Módosított fájlok
- `backend/app/api/v1/endpoints/admin_organizations.py` — Három kritikus változtatás:
**1. `_extract_limit()` helper létrehozása** (lines 397-447)
- Új, robusztus függvény a limit értékek kinyerésére a `SubscriptionTier.rules` JSONB-ból.
- Három szinten keres: (1) közvetlenül a `rules` objektumban, (2) `rules["allowances"]` nested objektumban, (3) `rules["limits"]` nested objektumban.
- Több kulcsot is támogat (pl. `["max_vehicles", "max_assets"]`), az első találatot adja vissza.
**2. `.limit(1)` eltávolítása a subscription query-ből** (lines 499-514)
- A subscription lekérdezés most `.scalars().all()`-t használ `.limit(1)` helyett.
- Minden aktív subscription sort visszaad (1 Base + N Add-on).
**3. Path A és Path B egyesítése** (lines 574-691)
- A régi Path A (`if active_sub and active_sub.tier:`) és Path B (`elif org.subscription_tier:`) logika helyett egységes, `_extract_limit()`-t használó kód.
- Base subscription-ok és Add-on subscription-ok szétválasztása a `tier.type` mező alapján.
- Add-on tier-ek base limitjeinek aggregálása a primary subscription limitjeihez.
- `extra_allowances` aggregálása az összes aktív subscription-ből.
### ✅ Verifikáció
- Python syntax check: ✅ OK (`py_compile` sikeres)
- Sync engine: 1279 OK, 0 Fixed, 0 Shadow — teljes szinkronban
- A `SubscriptionSummary` most helyesen kapja meg a Base limit értékeket az `_extract_limit()`-en keresztül, mielőtt az Extra allowances hozzáadódnak.