# 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`](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):** ```typescript 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`](backend/app/api/v1/endpoints/organizations.py:180) **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`](backend/app/api/v1/endpoints/users.py:48) **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`](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`](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`](frontend/src/views/DashboardView.vue:78-94) **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`](backend/app/api/v1/endpoints/organizations.py:329-357) **`_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`](backend/app/models/marketplace/organization.py:196) - `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`](frontend/src/layouts/PrivateLayout.vue:66-75) **Current "Költségek" button styling:** ```html ``` **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`](frontend/src/components/header/HeaderCompanySwitcher.vue:144) | 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`](frontend/src/types/organization.ts:24) | Make `org_type` required (remove `?`) since the backend now sends it reliably | | [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:239-269) | 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`](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`](frontend/src/components/dashboard/SubscriptionStatusWidget.vue:72-81) | 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`](frontend/src/layouts/OrganizationLayout.vue:52-60) | Add "Csapat" menu item to the hamburger menu | **Task 4: Create Ownership Transfer endpoint + UI** | File | Change | |------|--------| | [`backend/app/api/v1/endpoints/organizations.py`](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`](backend/app/api/v1/endpoints/organizations.py:486-569) | 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`](backend/app/api/v1/endpoints/organizations.py:775-822) | 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`](frontend/src/layouts/PrivateLayout.vue:66-75) | Verify the "Költségek" button alignment. If the issue persists, add `text-left` explicitly to the button or wrap the text in a ``. | | [`frontend/src/layouts/OrganizationLayout.vue`](frontend/src/layouts/OrganizationLayout.vue:40-72) | 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.