4.3 KiB
4.3 KiB
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:
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:
- Queries the
SubscriptionTierassigned to the user/org - Extracts
display_namefromtier.rulesJSONB - Returns it as
subscription_display_namein/auth/meand/users/me
The UserProfile TypeScript interface in auth.ts:68 already declares:
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:
authStore.user?.subscription_display_name(backend-resolved human name)- Org-level
subscription_display_name(corporate mode) - Static slug-to-human mapping (fallback)
- 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
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<string, string> = {
'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
})