# 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.