# SubscriptionInfoModal planDisplayName Bug Analysis **Date:** 2026-07-28 **Issue:** #433 - P0: Avatar Menu Subscription Modal - planDisplayName bugfix & DB audit --- ## 1. Database Investigation: admin@profibot.hu ### User Profile | Field | Value | |-------|-------| | `id` | 2 | | `email` | admin@profibot.hu | | `role` | ADMIN | | `subscription_plan` | private_vip_v1 | | `subscription_expires_at` | 2026-08-27 16:36:24+00 | | `is_vip` | true | | `scope_level` | system | | `scope_id` | 67 | | `ui_mode` | personal | ### Organization Memberships (3 orgs) | org_id | role | name | type | status | base_asset_limit | |--------|------|------|------|--------|-------------------| | 1 | ADMIN | Profibot Tester - Privat Garazs (#28) | individual | active | 100 | | 67 | OWNER | User Admin - Privat Garazs (#2) | individual | active | 100 | | 57 | OWNER | Teszt Autoszerviz Kft. | service_provider | pending_verification | 3 | ### Vehicle Distribution (vehicle.assets via current_organization_id) | org_id | Count | Sample plates | |--------|-------|---------------| | 1 | 19 | TEST-API-999, TEST-123, DRAFT-456, TEST-999, DRAFT-999, TEST-888, DRAFT-888, TEST-777 | | 67 | 3 | TEST-API-01 (archived), ABB112 (archived), ABC-123 (archived) | | 57 | 0 | No vehicles | ### Key Finding: No Duplication, No Data Corruption Vehicles are NOT duplicated across organizations. Each org has distinct vehicles. The perception of "azonos adatok" (same data) likely arises because: - Org 1 has 19 test vehicles (TEST-XXX, DRAFT-XXX pattern) making it appear bloated - Org 67 has only 3 archived vehicles - The KEP 3 showing "private_pro_v1" is the UI bug, not a data issue (admin's actual plan is private_vip_v1) --- ## 2. Root Cause: planDisplayName in SubscriptionInfoModal.vue ### Bug Location `SubscriptionInfoModal.vue:175-179` — the `planDisplayName` computed property: ```typescript const planDisplayName = computed(() => { const plan = authStore.user?.subscription_plan if (!plan || plan === 'FREE') return t('subscription.free') return plan // BUG: returns raw slug like "private_vip_v1" }) ``` ### Why It's Broken The backend `_resolve_subscription_data()` in `users.py:45-191` already: 1. Queries the `SubscriptionTier` assigned to the user/org 2. Extracts `display_name` from `tier.rules` JSONB 3. Returns it as `subscription_display_name` in `/auth/me` and `/users/me` The `UserProfile` TypeScript interface in `auth.ts:68` already declares: ```typescript subscription_display_name?: string | null ``` The `SubscriptionInfoModal` ignores this field completely. ### Reference Fix (Already Implemented in SubscriptionStatusWidget) `SubscriptionStatusWidget.vue:124-150` has the correct 3-level fallback: 1. `authStore.user?.subscription_display_name` (backend-resolved human name) 2. Org-level `subscription_display_name` (corporate mode) 3. Static slug-to-human mapping (fallback) 4. Raw plan name (ultimate fallback) ### Additional Data Binding Verification | Computed | Current Code | Status | |----------|-------------|--------| | maxVehicles | `authStore.user?.max_vehicles ?? 1` | CORRECT | | maxGarages | `authStore.user?.max_garages ?? 1` | CORRECT | | vehicleCount | `vehicleStore.vehicles.length` | CORRECT | | garageCount | `authStore.myOrganizations?.length ?? 0` | Context-dependent | | vehiclePercent/garagePercent | `count / max * 100` | CORRECT | --- ## 3. Fix Specification ### Required Change Replace `planDisplayName` computed in `SubscriptionInfoModal.vue:175-179` with the same 3-level fallback logic used by `SubscriptionStatusWidget.vue:124-150`. ### New Code ```typescript const planDisplayName = computed(() => { // Priority 1: Backend-supplied human-readable name const displayName = authStore.user?.subscription_display_name if (displayName) return displayName // Priority 2: Slug-to-human mapping fallback const plan = authStore.user?.subscription_plan if (!plan || plan === 'FREE') return t('subscription.free') const slugToHuman: Record = { 'private_basic_v1': 'Privat Alap', 'private_pro_v1': 'Privat Pro', 'private_vip_v1': 'Privat VIP', 'corp_free_v1': 'Ceges Ingyenes', 'corp_basic_v1': 'Ceges Alap', 'corp_pro_v1': 'Ceges Pro', } if (slugToHuman[plan]) return slugToHuman[plan] // Priority 3: Raw plan name as ultimate fallback return plan }) ```