Files
service-finder/docs/p0_ui_switcher_roles_subscription_audit_report.md
2026-06-24 11:29:45 +00:00

15 KiB

P0 Audit Report: UI, Switcher, Roles & Subscription Visibility

Date: 2026-06-23
Author: Fast Coder (Core Developer)
Status: Read-Only Audit Complete — No Code Modified


1. AUDIT: Garage Switcher (HeaderCompanySwitcher.vue)

File: frontend/src/components/header/HeaderCompanySwitcher.vue

Current Behavior

The switcher fetches organizations via authStore.fetchMyOrganizations() which calls GET /api/v1/organizations/my.

Filtering logic (line 144-151):

const companyOrganizations = computed(() => {
  return authStore.myOrganizations.filter(
    (org) => org.org_type &&
           org.org_type !== 'individual' &&
           org.org_type !== 'service_provider' &&
           org.org_type !== 'service'
  )
})

Root Cause of Missing Private Garages:

  • The org_type field in the backend response (GET /organizations/my) returns the actual OrgType enum value.
  • The filter excludes individual type organizations — but a user's private/personal garage IS of type individual.
  • The switcher only shows the "Private Garage" button (line 40-58) as a hardcoded navigation to /dashboard, but does NOT list individual-type organizations in the dropdown.
  • If a user has multiple private/individual garages (e.g., "Saját garázs" + "Családi garázs"), only the first one is accessible via the hardcoded button. The others are invisible.

Why company garages appear correctly:

  • Business/fleet_owner type organizations pass the filter and are listed dynamically.

Required Fix

  1. Remove the org_type !== 'individual' filter — or change the approach to show ALL organizations.
  2. Add visual separation in the dropdown: group by org_type (e.g., "Saját garázsok" vs "Céges garázsok").
  3. Ensure org_type is always populated in the backend response (currently it's optional in the OrganizationItem type).

2. AUDIT: Subscription & Limit Visibility

2a. Backend: GET /api/v1/organizations/my

File: backend/app/api/v1/endpoints/organizations.py

Current response includes:

  • subscription_plan (string, e.g. "test_privát")
  • subscription_expires_at (ISO datetime)
  • max_vehicles (P0: from subscription tier JSONB rules)
  • max_garages (P0: from subscription tier JSONB rules)
  • user_role (OWNER/ADMIN/etc.)

Verdict: Backend already returns all necessary subscription data per organization.

2b. Backend: GET /auth/me (User Profile)

File: backend/app/api/v1/endpoints/users.py

Current response includes:

  • subscription_plan (user-level)
  • max_vehicles, max_garages (from subscription tier)
  • system_capabilities, org_capabilities

Verdict: Backend already returns user-level subscription data.

2c. Frontend: SubscriptionStatusWidget.vue

File: frontend/src/components/dashboard/SubscriptionStatusWidget.vue

Current display:

  • Shows plan name (subscriptionPlan)
  • Shows days remaining
  • Shows a progress bar (but only for time remaining, NOT for vehicle count)

Missing:

  • No vehicle count display (e.g., "16/100 jármű")
  • No max_vehicles limit progress bar
  • The widget only shows the time-based progress, not the usage-based progress.

2d. Frontend: SubscriptionInfoModal.vue

File: frontend/src/components/subscription/SubscriptionInfoModal.vue

Current display:

  • Shows plan name
  • Shows expiry date + days remaining
  • Shows vehicle limit progress bar (vehicleCount / maxVehicles)
  • Shows garage limit progress bar (garageCount / maxGarages)

Verdict: The modal already has full subscription visibility. The issue is that this modal is only accessible via a button click, not visible on the dashboard by default.

2e. Frontend: DashboardView.vue

File: frontend/src/views/DashboardView.vue

Current display:

  • SubscriptionStatusWidget is rendered in the dashboard (line 81)
  • But it only shows plan name + days remaining, NOT vehicle usage

Required Fixes

  1. Enhance SubscriptionStatusWidget.vue to also show:
    • Vehicle count vs limit (e.g., "🚗 16/100")
    • A secondary progress bar for vehicle usage
    • The plan name should be more prominent
  2. Alternatively, replace the widget with a richer component that includes both time and usage data.

3. AUDIT: Role Logic & Ownership Transfer

3a. Backend: Role-Based Access Control

File: backend/app/api/v1/endpoints/organizations.py

_check_org_admin_access() function:

  • Allows both OWNER and ADMIN roles to access admin endpoints
  • Used by: GET /{org_id}/members, POST /{org_id}/invitations, PATCH /{org_id}/members/{member_id}, DELETE /{org_id}/members/{member_id}
  • ADMINs already have full operational rights (member management, invitations)

Subscription management endpoint:

  • PUT /{org_id}/subscription (line 775) requires RequireSystemCapability(Capability.CAN_MANAGE_SUBSCRIPTIONS) — this is a system-level capability, not org-level.
  • ADMINs cannot change subscription via this endpoint unless they also have the system capability (which typically only SUPERADMIN has).
  • The Architect's requirement says ADMINs should manage billing/subscription. This needs a separate org-level permission check.

3b. Backend: Ownership Transfer

File: backend/app/models/marketplace/organization.py

  • is_ownership_transferable field exists on the Organization model (boolean, default true)
  • can_transfer_ownership capability exists in seed_org_roles.py (line 35: OWNER=true, ADMIN=false)
  • No dedicated backend endpoint for "Transfer Ownership" exists yet
  • The PATCH /{org_id}/members/{member_id} endpoint (line 486) can change a member's role to OWNER, but there's no complete transfer flow (e.g., reassign owner_id on the Organization record, log the transfer, notify parties)

3c. Frontend: Members/Team Page

  • No frontend page found for managing team members (search for "members", "team", "munkatárs", "tagok" returned 0 results in .vue files)
  • The backend has all the APIs (GET /{org_id}/members, POST /{org_id}/invitations, PATCH /{org_id}/members/{member_id}, DELETE /{org_id}/members/{member_id}), but there's no UI to call them

Required Fixes

  1. Create a Team Members page (frontend) that uses the existing backend APIs
  2. Add org-level subscription management for ADMINs (separate permission check from system-level)
  3. Create a Transfer Ownership endpoint + UI flow

4. AUDIT: Mobile Menu Alignment (Hamburger Menu)

File: frontend/src/layouts/PrivateLayout.vue

Current "Költségek" button styling:

<button
  @click="navigateToCard('costs')"
  class="flex w-full items-center gap-3 px-4 py-2.5 text-sm text-white/80 transition-all duration-150 hover:bg-white/5 hover:text-white"
>
  <svg class="w-4 h-4 text-white/40" ...>
  {{ t('menu.finance') }}
</button>

Analysis:

  • The button uses flex items-center gap-3 — this is a flex row with the icon and text.
  • The text is not wrapped in any alignment utility — it uses default flex alignment.
  • The icon + text are left-aligned within the flex container (default justify-start).
  • The text-sm class applies to the text, which is fine.
  • There is NO text-center class on this button or its text. The Architect's report mentions text-center causing misalignment, but the current code uses flex items-center gap-3 which is correct for left-aligned menu items.

However, looking at the OrganizationLayout.vue hamburger menu (line 40-72), the same pattern is used — all buttons are flex w-full items-center gap-3. The text alignment is consistent.

Possible issue: The problem might be in a different component or a different version of the file. The current code appears correctly aligned. If there's a visual bug, it could be:

  1. A CSS conflict from a parent component
  2. An i18n translation string that's too long causing text wrapping
  3. A different mobile menu component not found in this search

Required Fix

  • If the issue is confirmed, ensure all hamburger menu buttons use flex items-center gap-3 (already the case)
  • Check if any parent CSS overrides the alignment
  • Verify the menu.finance translation key produces the expected text

5. ARCHITECT REPORT & ACTION PLAN

Summary of Findings

# Area Status Priority
1 Garage Switcher: individual orgs filtered out Bug P0
2 SubscriptionStatusWidget: missing vehicle usage Missing P0
3 Team Members UI page Missing P1
4 Ownership Transfer endpoint Missing P1
5 ADMIN subscription management ⚠️ Partial P1
6 Mobile menu alignment OK (current code) P2

Detailed Action Plan

🔴 P0 — Must Fix

Task 1: Fix Garage Switcher to show ALL organizations

File Change
frontend/src/components/header/HeaderCompanySwitcher.vue Remove the org_type !== 'individual' filter. Instead, show ALL organizations grouped by type (individual vs business). Add a section header "Saját garázsok" for individual orgs.
frontend/src/types/organization.ts Make org_type required (remove ?) since the backend now sends it reliably
backend/app/api/v1/endpoints/organizations.py Verify org_type is always populated in the response (currently uses o.org_type.value if hasattr(...) — ensure it never returns null)

Task 2: Enhance SubscriptionStatusWidget with vehicle usage

File Change
frontend/src/components/dashboard/SubscriptionStatusWidget.vue Add vehicle count vs limit display (e.g., "🚗 16/100"), add secondary progress bar for vehicle usage, make plan name more prominent
frontend/src/components/dashboard/SubscriptionStatusWidget.vue Add maxVehicles and vehicleCount computed properties (already available from authStore.user?.max_vehicles and vehicleStore.vehicles.length)

🟡 P1 — Should Fix

Task 3: Create Team Members UI page

File Change
frontend/src/views/organization/TeamMembersView.vue NEW FILE — Create a full team management page with: member list table, role change dropdown (OWNER/ADMIN/ACCOUNTANT/DRIVER/VIEWER), invitation form, remove member button
frontend/src/router/index.ts Add route /organization/:id/team pointing to the new view
frontend/src/layouts/OrganizationLayout.vue Add "Csapat" menu item to the hamburger menu

Task 4: Create Ownership Transfer endpoint + UI

File Change
backend/app/api/v1/endpoints/organizations.py NEW ENDPOINT POST /{org_id}/transfer-ownership — requires OWNER role, accepts new_owner_user_id, updates owner_id on Organization, creates audit log, notifies both parties
backend/app/api/v1/endpoints/organizations.py The existing PATCH /{org_id}/members/{member_id} already supports changing role to OWNER, but doesn't update Organization.owner_id. Add that logic.
Frontend team page Add "Tulajdonjog átruházása" button (visible only to OWNERs)

Task 5: ADMIN subscription management

File Change
backend/app/api/v1/endpoints/organizations.py Add an alternative permission check for PUT /{org_id}/subscription that allows org ADMINs (not just system-level) to manage subscriptions. Use _check_org_admin_access() instead of RequireSystemCapability.

🟢 P2 — Nice to Fix

Task 6: Mobile menu alignment verification

File Change
frontend/src/layouts/PrivateLayout.vue Verify the "Költségek" button alignment. If the issue persists, add text-left explicitly to the button or wrap the text in a <span class="text-left">.
frontend/src/layouts/OrganizationLayout.vue Same verification for the company hamburger menu

Data Flow Diagram (Current vs Proposed)

Current:
  GET /organizations/my
    → Returns all orgs including individual
    → HeaderCompanySwitcher FILTERS OUT individual
    → User sees only company orgs + hardcoded "Private Garage" button
    → Multiple private garages = invisible

Proposed:
  GET /organizations/my
    → Returns all orgs including individual
    → HeaderCompanySwitcher shows ALL orgs grouped:
      ┌─────────────────────────────┐
      │  👤 Saját garázsok          │
      │  ✓ Első garázs              │
      │    Második garázs           │
      │─────────────────────────────│
      │  🏢 Céges garázsok          │
      │    Alpha Kft.               │
      │    Beta Kft.                │
      └─────────────────────────────┘

Subscription Visibility Enhancement

Current SubscriptionStatusWidget:
  ┌─────────────────────────────┐
  │  test_privát     16 nap     │
  │  ████████░░░░░░             │  ← only time progress
  └─────────────────────────────┘

Proposed SubscriptionStatusWidget:
  ┌─────────────────────────────┐
  │  💎 test_privát    Aktív    │
  │  Lejárat: 2026.07.09       │
  │  ████████░░░░░░  16 nap    │  ← time progress
  │  🚗 Járművek: 16/100       │
  │  ██████████░░░░  16%       │  ← usage progress
  └─────────────────────────────┘

End of Audit Report. Ready for Architect review and approval before implementation.