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_typefield in the backend response (GET /organizations/my) returns the actualOrgTypeenum value. - The filter excludes
individualtype organizations — but a user's private/personal garage IS of typeindividual. - 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
- Remove the
org_type !== 'individual'filter — or change the approach to show ALL organizations. - Add visual separation in the dropdown: group by
org_type(e.g., "Saját garázsok" vs "Céges garázsok"). - Ensure
org_typeis always populated in the backend response (currently it's optional in theOrganizationItemtype).
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:
- ✅
SubscriptionStatusWidgetis rendered in the dashboard (line 81) - ❌ But it only shows plan name + days remaining, NOT vehicle usage
Required Fixes
- Enhance
SubscriptionStatusWidget.vueto also show:- Vehicle count vs limit (e.g., "🚗 16/100")
- A secondary progress bar for vehicle usage
- The plan name should be more prominent
- 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
OWNERandADMINroles 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) requiresRequireSystemCapability(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_transferablefield exists on theOrganizationmodel (boolean, defaulttrue)can_transfer_ownershipcapability exists inseed_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., reassignowner_idon 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
.vuefiles) - 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
- Create a Team Members page (frontend) that uses the existing backend APIs
- Add org-level subscription management for ADMINs (separate permission check from system-level)
- 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-smclass applies to the text, which is fine. - There is NO
text-centerclass on this button or its text. The Architect's report mentionstext-centercausing misalignment, but the current code usesflex items-center gap-3which 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:
- A CSS conflict from a parent component
- An i18n translation string that's too long causing text wrapping
- 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.financetranslation 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.