Files
service-finder/docs/subscription_info_modal_planDisplayName_bug_analysis.md

120 lines
4.3 KiB
Markdown

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