Files
service-finder/docs/admin_garage_subscription_tab_bugfix_spec.md

10 KiB

🔧 Javítási Terv: Admin Garázs Előfizetés Fül — Hibás Csomagadatok

Gitea Issue: #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:

// 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

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

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

.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:

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:

# 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)

# 🆕 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):

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:

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:

asset_count = len(org.assets) if hasattr(org, 'assets') and org.assets else 0

VAGY használj explicit lekérdezést:

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)

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)

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

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

# 🆕 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:

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