316 lines
10 KiB
Markdown
316 lines
10 KiB
Markdown
# 🔧 Javítási Terv: Admin Garázs Előfizetés Fül — Hibás Csomagadatok
|
|
|
|
> **Gitea Issue:** [#301](http://192.168.100.10:3000/kincses/service-finder/issues/301)
|
|
> **Hatáskör:** Backend (admin_organizations.py) + Frontend (index.vue)
|
|
> **Típus:** Bug
|
|
> **Prioritás:** Magas
|
|
|
|
---
|
|
|
|
## 📋 Összefoglaló
|
|
|
|
A frontend admin `frontend_admin/pages/garages/[id]/index.vue` garázs részletek oldal Előfizetés (Subscription) fülén a kiválasztott csomaghoz tartozó adatok (asset_limit, branch_limit, asset_count) nem a valóságot tükrözik. A backend `SubscriptionSummary` Pydantic modellje hiányos, és a limit-ek kinyerése törékeny.
|
|
|
|
---
|
|
|
|
## 🔍 Feltárt Hibák (Root Causes)
|
|
|
|
### 🐛 #1: `branch_limit` mező hiányzik a `SubscriptionSummary`-ból
|
|
|
|
**Hely:** `backend/app/api/v1/endpoints/admin_organizations.py:116-124`
|
|
|
|
A `SubscriptionSummary` Pydantic modell jelenleg nem tartalmaz `branch_limit` mezőt. A frontend template azonban használja:
|
|
|
|
```typescript
|
|
// frontend_admin/pages/garages/[id]/index.vue:526
|
|
{{ garage.branches?.length || 0 }} / {{ garage.subscription?.branch_limit || '∞' }}
|
|
```
|
|
|
|
Mivel `branch_limit` sosem érkezik a backend válaszban, a mező `undefined`, és a frontend mindig `'∞'`-t (végtelen) jelenít meg.
|
|
|
|
### 🐛 #2: `asset_limit` kinyerése logikai hibás
|
|
|
|
**Hely:** `backend/app/api/v1/endpoints/admin_organizations.py:462-465`
|
|
|
|
```python
|
|
asset_limit = (
|
|
rules.get("max_vehicles")
|
|
or active_sub.tier.feature_capabilities.get("max_vehicles")
|
|
or 1
|
|
)
|
|
```
|
|
|
|
**Problémák:**
|
|
1. Python `or` operátor falsy-nak tekinti a `0`-t — ha a tier `max_vehicles: 0`, az `or` miatt `feature_capabilities`-re ugrik tovább.
|
|
2. Ha `feature_capabilities`-ben sincs `max_vehicles`, vagy az is `0`, az `asset_limit = 1` lesz.
|
|
3. Ha a tier más kulcsnevet használ (pl. `vehicle_limit`, `max_assets`), az érték hibásan `1`-re esik vissza.
|
|
|
|
### 🐛 #3: `GarageDetailsResponse` nem tartalmaz `branches` mezőt
|
|
|
|
**Hely:** `backend/app/api/v1/endpoints/admin_organizations.py:127-175`
|
|
|
|
A `GarageDetailsResponse` modellben nincs `branches` lista. A frontend `garage.branches?.length` használ, de a backend nem ad vissza branch adatokat, így a kihasználtsági mutató mindig `0 / ∞`.
|
|
|
|
### 🐛 #4: `asset_count` hardkódolt (mindig 0)
|
|
|
|
**Hely:** `backend/app/api/v1/endpoints/admin_organizations.py:473`
|
|
|
|
```python
|
|
asset_count=0,
|
|
```
|
|
|
|
Az `asset_count` mindig `0`, sosem számolja le a szervezet tényleges járműveit.
|
|
|
|
### 🐛 #5: Lejárt előfizetés is aktívként jelenhet meg
|
|
|
|
**Hely:** `backend/app/api/v1/endpoints/admin_organizations.py:414-425`
|
|
|
|
```python
|
|
.where(
|
|
OrganizationSubscription.org_id == org_id,
|
|
OrganizationSubscription.is_active == True,
|
|
)
|
|
```
|
|
|
|
A lekérdezés nem ellenőrzi a `valid_until >= now()` feltételt. Ha egy subscription `is_active=True` de `valid_until` már lejárt, a backend továbbra is aktívként kezeli.
|
|
|
|
### 🐛 #6: Fallback path hiányos adatokkal
|
|
|
|
**Hely:** `backend/app/api/v1/endpoints/admin_organizations.py:476-488`
|
|
|
|
Ha nincs aktív `OrganizationSubscription`, a kód a `org.subscription_tier` denormalizált kapcsolatra esik vissza. Ez a path nem adja vissza a `valid_from` és `is_active` mezőket, csak a defaultokat használja.
|
|
|
|
---
|
|
|
|
## 🛠️ Javítási Terv
|
|
|
|
### 1. Backend: `SubscriptionSummary` bővítése
|
|
|
|
**Fájl:** `backend/app/api/v1/endpoints/admin_organizations.py`
|
|
|
|
#### 1/a. `branch_limit` mező hozzáadása (sor 116-124 körül)
|
|
|
|
A `SubscriptionSummary` osztály bővítése:
|
|
|
|
```python
|
|
class SubscriptionSummary(BaseModel):
|
|
tier_name: str = "Free/Fallback"
|
|
tier_level: int = 0
|
|
valid_from: Optional[str] = None
|
|
expires_at: Optional[str] = None
|
|
is_active: bool = True
|
|
asset_count: int = 0
|
|
asset_limit: int = 1
|
|
branch_limit: int = 0 # 🆕 HIÁNYZÓ MEZŐ
|
|
```
|
|
|
|
#### 1/b. `asset_limit` kinyerés javítása (sor 462-465)
|
|
|
|
Az `or` helyett használj **explicit `max()` logikát**:
|
|
|
|
```python
|
|
# JAVÍTVA: explicit max() használata az or helyett
|
|
rules = active_sub.tier.rules or {}
|
|
fc = active_sub.tier.feature_capabilities or {}
|
|
|
|
# Kompatibilitás: több kulcsnév támogatása
|
|
vehicle_limit_keys = ["max_vehicles", "vehicle_limit", "max_assets"]
|
|
asset_limit = max(
|
|
(rules.get(k, 0) for k in vehicle_limit_keys),
|
|
default=0
|
|
)
|
|
asset_limit = max(
|
|
asset_limit,
|
|
max((fc.get(k, 0) for k in vehicle_limit_keys), default=0),
|
|
)
|
|
asset_limit = max(asset_limit, 1) # minimum 1
|
|
```
|
|
|
|
**Ugyanezt a javítást kell elvégezni a fallback path-on is** (sor 477-482).
|
|
|
|
#### 1/c. `branch_limit` kitermelése a rules-ből (sor 462-488 között)
|
|
|
|
```python
|
|
# 🆕 HIÁNYZÓ: branch_limit kinyerése
|
|
branch_limit_keys = ["max_branches", "branch_limit", "max_garages"]
|
|
branch_limit = max(
|
|
(rules.get(k, 0) for k in branch_limit_keys),
|
|
default=0
|
|
)
|
|
branch_limit = max(
|
|
branch_limit,
|
|
max((fc.get(k, 0) for k in branch_limit_keys), default=0),
|
|
)
|
|
```
|
|
|
|
Ezt add hozzá mindkét path-hoz (aktív sub + fallback):
|
|
|
|
```python
|
|
subscription_summary = SubscriptionSummary(
|
|
...
|
|
asset_limit=asset_limit,
|
|
branch_limit=branch_limit, # 🆕
|
|
)
|
|
```
|
|
|
|
#### 1/d. `asset_count` dinamikus számolása
|
|
|
|
Az `asset_count`-ot ne hardkódold. Számold meg a szervezet járműveit. Ehhez be kell tölteni az `Organization.assets` relationship-et a SELECT-nél:
|
|
|
|
```python
|
|
org_stmt = (
|
|
select(Organization)
|
|
.options(
|
|
selectinload(Organization.subscription_tier),
|
|
selectinload(Organization.owner).selectinload(User.person),
|
|
selectinload(Organization.assets), # 🆕
|
|
)
|
|
.where(Organization.id == org_id)
|
|
)
|
|
```
|
|
|
|
Majd a subscription summary építésénél:
|
|
|
|
```python
|
|
asset_count = len(org.assets) if hasattr(org, 'assets') and org.assets else 0
|
|
```
|
|
|
|
VAGY használj explicit lekérdezést:
|
|
|
|
```python
|
|
from sqlalchemy import func
|
|
from app.models.asset import AssetAssignment
|
|
|
|
asset_count_stmt = select(func.count(AssetAssignment.id)).where(
|
|
AssetAssignment.organization_id == org_id
|
|
)
|
|
asset_count_result = await db.execute(asset_count_stmt)
|
|
asset_count = asset_count_result.scalar() or 0
|
|
```
|
|
|
|
#### 1/e. Lejárt előfizetés szűrés (sor 414-425)
|
|
|
|
```python
|
|
from sqlalchemy import or_ as sa_or
|
|
|
|
sub_stmt = (
|
|
select(OrganizationSubscription)
|
|
.options(selectinload(OrganizationSubscription.tier))
|
|
.where(
|
|
OrganizationSubscription.org_id == org_id,
|
|
OrganizationSubscription.is_active == True,
|
|
# 🆕 HIÁNYZÓ: lejárati dátum ellenőrzése
|
|
sa_or_(
|
|
OrganizationSubscription.valid_until.is_(None),
|
|
OrganizationSubscription.valid_until >= func.now(),
|
|
),
|
|
)
|
|
.order_by(OrganizationSubscription.id.desc())
|
|
.limit(1)
|
|
)
|
|
```
|
|
|
|
### 2. Backend: `GarageDetailsResponse` bővítése `branches` listával
|
|
|
|
**Fájl:** `backend/app/api/v1/endpoints/admin_organizations.py`
|
|
|
|
#### 2/a. BranchBrief Pydantic modell létrehozása (a SubscriptionSummary után)
|
|
|
|
```python
|
|
class BranchBrief(BaseModel):
|
|
"""Fióktelep rövid adatai."""
|
|
id: int
|
|
name: str
|
|
city: Optional[str] = None
|
|
is_active: bool = True
|
|
```
|
|
|
|
#### 2/b. `GarageDetailsResponse` bővítése `branches`-szel
|
|
|
|
```python
|
|
class GarageDetailsResponse(BaseModel):
|
|
...
|
|
subscription: Optional[SubscriptionSummary] = None
|
|
branches: List[BranchBrief] = [] # 🆕 HIÁNYZÓ MEZŐ
|
|
primary_contact: Optional[ContactPersonInfo] = None
|
|
...
|
|
```
|
|
|
|
#### 2/c. Branch adatok lekérdezése a végpontban
|
|
|
|
```python
|
|
# 🆕 HIÁNYZÓ: branch adatok lekérése
|
|
from app.models.location import Branch
|
|
|
|
branches_stmt = select(Branch).where(Branch.organization_id == org_id)
|
|
branches_result = await db.execute(branches_stmt)
|
|
branches_data = branches_result.scalars().all()
|
|
|
|
branch_list = [
|
|
BranchBrief(
|
|
id=b.id,
|
|
name=b.name,
|
|
city=b.city,
|
|
is_active=b.is_active,
|
|
)
|
|
for b in branches_data
|
|
]
|
|
```
|
|
|
|
### 3. Frontend: Ellenőrző módosítások
|
|
|
|
**Fájl:** `frontend_admin/pages/garages/[id]/index.vue`
|
|
|
|
#### 3/a. `branchQuotaPercent` computed property ellenőrzése
|
|
|
|
Ellenőrizd, hogy a számított property helyesen működik a nem-`undefined` `branch_limit`-nel:
|
|
|
|
```typescript
|
|
const branchQuotaPercent = computed(() => {
|
|
if (!garage.value?.subscription?.branch_limit) return 0
|
|
const branchCount = garage.value?.branches?.length || 0
|
|
return (branchCount / garage.value.subscription.branch_limit) * 100
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ Elfogadási Kritériák (Acceptance Criteria)
|
|
|
|
1. **Branch limit megjelenítése:** A branches utilization sorban a backend által küldött `branch_limit` érték jelenjen meg (ne mindig `∞`).
|
|
2. **Asset limit helyes értéke:** A kiválasztott csomag `rules` JSONB-jében lévő `max_vehicles` (vagy alternatív kulcs) értéke jelenjen meg.
|
|
3. **Asset count valós adat:** A járművek száma ne `0` legyen, hanem a tényleges asset-ek száma.
|
|
4. **Branch count valós adat:** A branch-ek száma ne mindig `0` legyen, hanem a tényleges branch-ek száma a `branches` listából.
|
|
5. **Lejárt előfizetés:** Ne jelenjen meg aktív előfizetésként, ha a `valid_until` már lejárt.
|
|
6. **Fallback:** Ha nincs aktív subscription, a `org.subscription_tier` denormalizált mezőből származó adatok legyenek teljesek (valid_from, is_active).
|
|
|
|
---
|
|
|
|
## 📦 Módosítandó Fájlok
|
|
|
|
| # | Fájl | Változtatás |
|
|
|---|------|-------------|
|
|
| 1 | `backend/app/api/v1/endpoints/admin_organizations.py` | `SubscriptionSummary` bővítése `branch_limit`-lel, `asset_limit` logika javítása, `asset_count` dinamikus számolása, `GarageDetailsResponse` bővítése `branches`-szel + `BranchBrief`, lejárati dátum szűrés |
|
|
| 2 | `frontend_admin/pages/garages/[id]/index.vue` | `branchQuotaPercent` számítás ellenőrzése |
|
|
|
|
---
|
|
|
|
## 🧪 Tesztelési Terv
|
|
|
|
1. **Létező garázs megnyitása:** Ellenőrizd, hogy az Előfizetés fül a tényleges csomag adatait mutatja.
|
|
2. **Csomagváltás:** Válts csomagot, és ellenőrizd, hogy a frissítés után a helyes adatok jelennek meg.
|
|
3. **Lejárt előfizetés:** Állíts be egy lejárt `valid_until` dátumot, és ellenőrizd, hogy a garázs fallback csomag adatait mutatja.
|
|
4. **Több branch-es garázs:** Ellenőrizd, hogy a branch count és limit helyesen jelenik meg.
|
|
5. **API teszt:** `GET /api/v1/admin/organizations/{id}/details` - ellenőrizd, hogy a válasz tartalmazza a `subscription.branch_limit` és `branches` mezőket.
|
|
|
|
---
|
|
|
|
## ⚡ Végrehajtási Sorrend
|
|
|
|
1. **Code mód** váltása
|
|
2. Backend módosítások elvégzése (`admin_organizations.py`)
|
|
3. Sync engine futtatása: `docker compose exec sf_api python3 /app/backend/app/scripts/sync_engine.py`
|
|
4. API konténer újraindítása
|
|
5. Frontend (ha szükséges) módosítása
|
|
6. Manuális tesztelés a böngészőből
|