Files
service-finder/docs/subscription_info_modal_planDisplayName_bug_analysis.md

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:

  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:

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

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