frontend költség beállítások

This commit is contained in:
Roo
2026-06-23 21:11:21 +00:00
parent 5b437b220d
commit 71ef33bb85
90 changed files with 11850 additions and 1053 deletions

View File

@@ -0,0 +1,214 @@
# 🐛 BMW123 Jármű Adatmegjelenítési Hibajelentés
**Dátum:** 2026-06-20
**Vizsgált entitás:** `bcfed768-c8c2-4867-a7bb-38a6517dce36` (BMW123 / BMW / BMWM3)
**Scope:** Backend (`asset` modell + `AssetUpdate` séma) → Frontend (`OverviewTab`, `VehicleFormModal`)
**Hibák száma:** 4
---
## 🔍 Összefoglaló
A BMW123 rendszámú jármű adatbázisban tárolt `registration_certificate_validity` (2026-08-08) adata nem jelenik meg a frontend járműáttekintő oldalon. Emellett a `VehicleFormModal` adminisztrációs fülön beállítható mezők (`insurance_expiry_date`, `purchase_date`) értékei elvesznek a mentés során, mivel a backend Pydantic sémái és adatbázis modellje nem tartalmazza ezeket a mezőket.
---
## 🐛 BUG #1 — KRITIKUS: MOT/Regisztrációs Érvényesség Eltérés
### Adatbázis oldal
A `vehicle.assets` táblában a `registration_certificate_validity` mező **létezik** és **helyes értéket** tárol:
```
registration_certificate_validity = 2026-08-08 00:00:00+00
```
Ez a mező egy **top-level DateTime** oszlop az [`Asset`](backend/app/models/vehicle/asset.py:137-140) modellben:
```python
registration_certificate_validity: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True
)
```
### Frontend hiba
Az [`OverviewTab.vue`](frontend/src/components/vehicles/tabs/OverviewTab.vue:273-277) a `registration_certificate_validity` helyett tévesen a `individual_equipment.dates.mot_expiry` JSONB mezőből olvassa ki a műszaki érvényességet:
```typescript
const motExpiryLabel = computed(() => {
const dates = vehicle?.value?.individual_equipment?.dates
if (!dates?.mot_expiry) return t('common.noData')
return formatDate(dates.mot_expiry)
})
```
### Gyökérok
A **JSONB `individual_equipment.dates`** objektum és a **top-level `registration_certificate_validity`** oszlop két különböző tárolási hely. A `VehicleFormModal` a `registration_certificate_validity` mezőt használja (helyesen), de az `OverviewTab` a JSONB-ből próbál olvasni. Mivel a `dates` objektum nem létezik a BMW123 adataiban (`individual_equipment` = `{"features": [...], "car_specs": {...}}`), mindig "Nincs adat" jelenik meg.
### Érintett fájlok
- [`frontend/src/components/vehicles/tabs/OverviewTab.vue:273`](frontend/src/components/vehicles/tabs/OverviewTab.vue:273) — hibás olvasás
- [`frontend/src/stores/vehicle.ts:89-92`](frontend/src/stores/vehicle.ts:89) — `dates` interfész definíció
- [`backend/app/models/vehicle/asset.py:137-140`](backend/app/models/vehicle/asset.py:137) — helyes adatbázis mező
### Javasolt javítás
Az `OverviewTab.vue`-ban a `motExpiryLabel` computed property-t át kell írni, hogy a `vehicle.value?.registration_certificate_validity`-ből olvasson, vagy alternatívaként a `VehicleFormModal` `handleSave()` függvényében a `registration_certificate_validity` értékét szinkronizálni kell a `individual_equipment.dates.mot_expiry`-be is.
---
## 🐛 BUG #2 — KRITIKUS: Biztosítási és Vásárlási Adatok Elvesznek
### Adatbázis modell hiányosság
Az [`Asset`](backend/app/models/vehicle/asset.py:69) modellben **NEM** léteznek az alábbi mezők:
- `insurance_expiry_date` (biztosítás lejárta)
- `purchase_date` (vásárlás dátuma)
Ezek a mezők teljesen hiányoznak az adatbázis sémából.
### Pydantic séma hiányosság
Az [`AssetUpdate`](backend/app/schemas/asset.py:269) és [`AssetCreate`](backend/app/schemas/asset.py:122) sémákban **NEM** szerepelnek:
- `insurance_expiry_date`
- `purchase_date`
### Frontend helyzet
A [`VehicleFormModal.vue`](frontend/src/components/dashboard/VehicleFormModal.vue) adminisztrációs fül (Tab 2) tartalmazza ezeket a mezőket:
- `form.insurance_expiry_date` — "Biztosítás lejárta" dátumválasztó
- `form.purchase_date` — "Vásárlás dátuma" dátumválasztó
- `form.registration_certificate_validity` — "Forgalmi engedély érvényessége" dátumválasztó
A [`handleSave()`](frontend/src/components/dashboard/VehicleFormModal.vue:507) függvény a payload-ba teszi ezeket:
```typescript
const payload: Record<string, any> = {
...form.value,
// ...
}
```
A [`populateForm()`](frontend/src/components/dashboard/VehicleFormModal.vue:440) függvény pedig a vehicle objektumból várja ezeket:
```typescript
form.value.insurance_expiry_date = vehicle.insurance_expiry_date || ''
form.value.purchase_date = vehicle.purchase_date || ''
```
### Gyökérok
Az [`update_vehicle`](backend/app/api/v1/endpoints/assets.py:660) endpoint `payload.model_dump(exclude_unset=True)`-t használ, ami **kizárólag** az `AssetUpdate` Pydantic sémában definiált mezőket adja át az adatbázisnak. Mivel `insurance_expiry_date` és `purchase_date` nem szerepelnek az `AssetUpdate`-ben, ezek az értékek **csendben elvesznek** a PUT kérés során.
### Érintett fájlok
- [`backend/app/models/vehicle/asset.py:69`](backend/app/models/vehicle/asset.py:69) — hiányzó modell mezők
- [`backend/app/schemas/asset.py:269`](backend/app/schemas/asset.py:269) — hiányzó séma mezők
- [`backend/app/schemas/asset.py:122`](backend/app/schemas/asset.py:122) — hiányzó séma mezők (Create)
- [`frontend/src/components/dashboard/VehicleFormModal.vue:440-460`](frontend/src/components/dashboard/VehicleFormModal.vue:440) — frontend várja ezeket
- [`frontend/src/components/dashboard/VehicleFormModal.vue:507`](frontend/src/components/dashboard/VehicleFormModal.vue:507) — frontend küldi ezeket
### Javasolt javítás
1. Hozzáadni az `Asset` modellhez: `insurance_expiry_date: Mapped[Optional[datetime]]` és `purchase_date: Mapped[Optional[datetime]]`
2. Hozzáadni az `AssetUpdate` és `AssetCreate` sémákhoz ezeket a mezőket
3. Futtatni a `sync_engine`-t a séma frissítéséhez
---
## 🐛 BUG #3 — HIÁNYZÓ FUNKCIÓ: Nincs Dedikált Biztosítási Adat Struktúra
### Hiányzó mezők
Sem az [`Asset`](backend/app/models/vehicle/asset.py:69) modellben, sem más kapcsolódó táblában (`asset_financials`, `asset_events`) **NEM** léteznek dedikált mezők az alábbiakra:
- **Kötelező felelősségbiztosítás** (liability insurance)
- Kötvény száma
- Lejárati dátuma
- Biztosító neve
- **CASCO biztosítás**
- Kötvény száma
- Lejárati dátuma
- Önrész összege
- Biztosító neve
### Javasolt megoldás
Új tábla létrehozása: `vehicle.insurance_policies`:
```sql
CREATE TABLE vehicle.insurance_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
asset_id UUID NOT NULL REFERENCES vehicle.assets(id) ON DELETE CASCADE,
insurance_type VARCHAR(20) NOT NULL CHECK (insurance_type IN ('liability', 'casco')),
policy_number VARCHAR(100),
insurer_name VARCHAR(255),
start_date DATE,
expiry_date DATE,
deductible_amount DECIMAL(12,2),
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
```
---
## 🐛 BUG #4 — KISEBB: "Következő Szerviz" Helyettesítő Szöveg
### Hiba
Az [`OverviewTab.vue`](frontend/src/components/vehicles/tabs/OverviewTab.vue:291-294) "Következő szerviz" mezője mindig "Coming Soon" szöveget mutat:
```typescript
const nextServiceLabel = computed(() => {
// Placeholder — will be connected to the service book / maintenance schedule later
return t('vehicleDetail.comingSoon')
})
```
### Érintett fájl
- [`frontend/src/components/vehicles/tabs/OverviewTab.vue:291`](frontend/src/components/vehicles/tabs/OverviewTab.vue:291)
### Javasolt javítás
Össze kell kötni a szervizkönyv modullal. A következő szerviz esedékességét az `asset_events` táblában rögzített szerviz előzményekből kellene kiszámolni (pl. utolsó olajcsere + 15.000 km vagy + 1 év).
---
## 📊 Adatfolyam Diagram (Jelenlegi Hibás Állapot)
```mermaid
flowchart TD
DB[(PostgreSQL\nvehicle.assets)]
DB -->|registration_certificate_validity\n2026-08-08| API
API[GET /api/v1/vehicles/{id}]
API -->|AssetResponse\nregistration_certificate_validity| FE_Store[Pinia Store\nvehicle.ts]
FE_Store -->|vehicle.registration_certificate_validity| OverviewTab
FE_Store -->|vehicle.individual_equipment.dates.mot_expiry| OverviewTab
subgraph OverviewTab [OverviewTab.vue - HIBA]
MOT[MOT label\nolvas: individual_equipment.dates.mot_expiry]
Service[Next Service\nComing Soon placeholder]
end
subgraph VehicleFormModal [VehicleFormModal.vue - VESZÉLYES]
Form[Adminisztráció fül]
Form --> insurance[insurance_expiry_date]
Form --> purchase[purchase_date]
Form --> regCert[registration_certificate_validity]
insurance -->|ELVESZ| PUT[PUT /vehicles/{id}\nexclude_unset=True]
purchase -->|ELVESZ| PUT
regCert -->|ELTÁROLVA| PUT
end
PUT -->|AssetUpdate.model_dump\ncsak definiált mezők| DB
```
---
## ✅ Javítási Terv (Jóváhagyásra Vár)
| # | Prioritás | Feladat | Érintett fájlok |
|---|-----------|---------|-----------------|
| 1 | **KRITIKUS** | `OverviewTab.vue` MOT olvasás javítása `registration_certificate_validity`-ből | [`OverviewTab.vue`](frontend/src/components/vehicles/tabs/OverviewTab.vue:273) |
| 2 | **KRITIKUS** | `insurance_expiry_date` és `purchase_date` hozzáadása az `Asset` modellhez | [`asset.py`](backend/app/models/vehicle/asset.py:69) |
| 3 | **KRITIKUS** | `insurance_expiry_date` és `purchase_date` hozzáadása az `AssetUpdate`/`AssetCreate` sémákhoz | [`asset.py schema`](backend/app/schemas/asset.py:269) |
| 4 | **MAGAS** | Szinkronizációs logika: `registration_certificate_validity` másolása `individual_equipment.dates.mot_expiry`-be | [`assets.py endpoint`](backend/app/api/v1/endpoints/assets.py:660) |
| 5 | **KÖZEPES** | Dedikált `insurance_policies` tábla létrehozása | Új SQLAlchemy modell |
| 6 | **KÖZEPES** | Következő szerviz összekötése a szervizkönyv adatokkal | [`OverviewTab.vue`](frontend/src/components/vehicles/tabs/OverviewTab.vue:291) |
---
## 📝 Technikai Megjegyzések
- A **Sync Engine** használata kötelező az új mezők adatbázisba vezetéséhez: `docker exec sf_api python3 -m app.scripts.sync_engine`
- A JSONB `individual_equipment.dates` opcionális, nem minden járműnél van kitöltve
- A frontend `Vehicle` interfész frissítése is szükséges a [`vehicle.ts`](frontend/src/stores/vehicle.ts:9) store-ban
- A `populateForm` függvényben a hiányzó mezők miatt TypeScript hiba léphet fel, ha szigorú típusellenőrzés van
---
*Jelentés készült: 2026-06-20 | Auditor: Rendszer-Architect*

158
docs/cost_pipeline_audit.md Normal file
View File

@@ -0,0 +1,158 @@
# P0 Cost Pipeline Visibility Audit Report
**Date:** 2026-06-23
**Author:** Fast Coder (Core Developer)
**Status:** Complete
**Gitea Card:** #282
---
## Executive Summary
The `/dashboard/costs` page is **empty** for User 28 (tester_pro@profibot.hu) despite Org 1 having **5 confirmed records** in `fleet_finance.asset_costs` (for vehicles BMW123 and QWE432). The root cause is a **backend organization resolution bug** in the `GET /expenses/` endpoint.
---
## 1. Frontend Audit
### CostsView.vue
- **File:** [`frontend/src/views/costs/CostsView.vue`](frontend/src/views/costs/CostsView.vue:299)
- Calls `api.get('/expenses', { params })` at line 299
- **Does NOT pass any `organization_id` parameter** — only sends `page`, `page_size`, and optionally `asset_id`
- The frontend relies entirely on the backend to resolve the correct organization
### Pinia Stores
- **`cost.ts`** ([`frontend/src/stores/cost.ts`](frontend/src/stores/cost.ts)) — Only handles POST (create expense), not fetching
- **`expense.ts`** ([`frontend/src/stores/expense.ts`](frontend/src/stores/expense.ts)) — Fetches by asset ID (`/expenses/{asset_id}`), but is NOT used by CostsView.vue
### Data Mapping
The frontend template reads these keys from the API response:
- `cost.created_at` or `cost.date` (line 127)
- `cost.category_code` or `cost.cost_type` (line 134)
- `cost.category_name` or `cost.cost_type` (line 136)
- `cost.amount_gross` (line 143)
- `cost.currency` (line 143)
- `cost.vehicle_name` or `cost.license_plate` (line 149)
- `cost.description` (line 155)
The backend API returns all these keys correctly (see lines 193-218 of expenses.py). **No mapping mismatch found.**
---
## 2. Backend API Audit
### Endpoint: `GET /expenses/` (list_all_expenses)
- **File:** [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py:115-239)
### 🔴 ROOT CAUSE: Organization Resolution Bug
At lines 130-139, the endpoint resolves the user's organization:
```python
org_stmt = select(OrganizationMember).where(
OrganizationMember.user_id == current_user.id,
OrganizationMember.status == "active"
).limit(1)
org_result = await db.execute(org_stmt)
membership = org_result.scalar_one_or_none()
if not membership:
raise HTTPException(status_code=403, detail="No active organization membership found.")
org_id = membership.organization_id
```
**Problem:** This query uses `.limit(1)` without an `ORDER BY`, which means PostgreSQL returns the **first matching row by physical storage order**. For User 28, this resolves to **organization_id = 63** (membership ID 26, ADMIN role), which has **ZERO asset_costs**.
### User 28's Memberships (in DB order):
| Membership ID | Organization ID | Role | Status | Has Costs? |
|---------------|----------------|-------|--------|------------|
| 26 | 63 | ADMIN | active | **NO** |
| 27 | 21 | OWNER | active | **NO** |
| 49 | 1 | OWNER | active | **YES (5 records)** |
### The Fix Should Be:
The endpoint should use the user's **`active_organization_id`** (from `identity.users` table) instead of blindly picking the first membership. The `active_organization_id` is already tracked on the user profile (see [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:70) — `active_organization_id: number | null`).
---
## 3. Database Verification
### Org 1 Asset Costs (Confirmed)
| Asset ID | License Plate | Amount | Category | Status |
|----------|--------------|--------|----------|--------|
| bcfed768... | BMW123 | 69,850 | 4 (INSURANCE) | APPROVED |
| bcfed768... | BMW123 | 69,850 | 4 (INSURANCE) | APPROVED |
| c6afc130... | QWE432 | 10,000 | 1 (FUEL) | APPROVED |
| c6afc130... | QWE432 | 10,000 | 1 (FUEL) | APPROVED |
| c6afc130... | QWE432 | 5,000 | 1 (FUEL) | APPROVED |
**Total: 5 records, all APPROVED, all in Org 1.**
### Backend Query Test
When the exact same SQLAlchemy query is run with `organization_id = 1` explicitly, it returns **all 5 rows** with correct vehicle info (license_plate, brand, model). The data pipeline from DB → ORM → API response is **fully functional**.
---
## 4. Data Flow Summary
```
CostsView.vue
└─ GET /expenses (no org_id param)
└─ Backend resolves org via first active membership
└─ Finds org_id = 63 (User 28's first membership)
└─ Query: AssetCost.organization_id == 63
└─ Returns 0 rows → Empty table
```
**Expected flow:**
```
CostsView.vue
└─ GET /expenses (no org_id param)
└─ Backend resolves org via active_organization_id
└─ Finds org_id = 1 (User 28's active org)
└─ Query: AssetCost.organization_id == 1
└─ Returns 5 rows → Table populated
```
---
## 5. Recommended Fix
### Immediate Fix (Backend)
Modify [`list_all_expenses()`](backend/app/api/v1/endpoints/expenses.py:130-139) to use the user's `active_organization_id` instead of the first active membership:
```python
# Use active_organization_id from user profile
org_id = current_user.active_organization_id
if not org_id:
# Fallback: try to find any active membership
org_stmt = select(OrganizationMember).where(
OrganizationMember.user_id == current_user.id,
OrganizationMember.status == "active"
).limit(1)
org_result = await db.execute(org_stmt)
membership = org_result.scalar_one_or_none()
if not membership:
raise HTTPException(status_code=403, detail="No active organization membership found.")
org_id = membership.organization_id
```
### Alternative Fix (Frontend)
Pass `organization_id` explicitly in the API call from CostsView.vue, reading it from the auth store's `active_organization_id`. However, this is less robust as it requires every API consumer to know about org switching.
### Recommended Approach
**Fix the backend** — it's the single source of truth for organization resolution and will fix the issue for ALL frontend components that call `GET /expenses/`.
---
## 6. Files Touched During Audit
| File | Purpose |
|------|---------|
| [`frontend/src/views/costs/CostsView.vue`](frontend/src/views/costs/CostsView.vue) | Frontend costs page — calls GET /expenses |
| [`frontend/src/stores/cost.ts`](frontend/src/stores/cost.ts) | Pinia store for creating expenses |
| [`frontend/src/stores/expense.ts`](frontend/src/stores/expense.ts) | Pinia store for per-asset expense fetching |
| [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts) | Auth store — has `active_organization_id` |
| [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py) | **Backend endpoint with the bug** (lines 130-139) |
| `fleet_finance.asset_costs` | Database table — 5 records confirmed for Org 1 |
| `fleet.organization_members` | User 28 has 3 memberships (org 63, 21, 1) |
| `identity.users` | User 28 has `scope_id = None`, `scope_level = 'individual'` |

177
docs/cost_ui_audit.md Normal file
View File

@@ -0,0 +1,177 @@
# Cost UI Audit Report — Dashboard Cost Actions & Existing Cost Forms
**Date:** 2026-06-21
**Author:** Fast Coder (Core Developer)
**Scope:** P0 READ & PLAN — Dual-Entry Cost System UX Audit
**Status:** Audit Complete — No code modified
---
## 1. Dashboard Cost Actions Card (`CostsActionsCard.vue`)
**File:** [`frontend/src/components/dashboard/CostsActionsCard.vue`](frontend/src/components/dashboard/CostsActionsCard.vue)
### Current Template Structure
The card is a 350px-tall bento-grid tile with:
| Element | Description |
|---------|-------------|
| **Header** | Dark slate bar with `💰` icon + `t('dashboard.costsTitle')` label |
| **Vehicle Selector** | `<select>` dropdown listing all `vehicleStore.sortedVehicles` by license plate + brand/model |
| **No Vehicle Warning** | Amber alert box shown when no vehicle is selected |
| **⛽ Record Fueling** | Big gradient button (sf-accent→emerald) — calls `openFuelModal()` |
| **🅿️ Parking / Toll** | Medium secondary button — calls `openFeeModal()` |
| ** Additional Costs** | Medium secondary button — calls `openComplexModal()` |
### Events Emitted
- **`cost-saved`** — Emitted with `vehicleId: string` after any modal saves successfully. Parent (`DashboardView`) forwards this to `VehicleDetailModal` to trigger cost refresh.
### Modals Opened
| Modal | Trigger | Mode |
|-------|---------|------|
| `SimpleFuelModal` | `openFuelModal()` | Fuel entry (liters, odometer) |
| `ComplexExpenseModal` (Fee mode) | `openFeeModal()` | `isFeeMode=true` → Parking/Toll |
| `ComplexExpenseModal` (Complex mode) | `openComplexModal()` | `isFeeMode=false` → Full category cascade |
### Key Observations
-**Quick Actions exist** for Fuel, Parking/Toll, and "Other" costs.
-**No "Invoice / Számla" button** — there is no deep-entry wizard for supplier invoices with line items.
-**No supplier field, no invoice number, no VAT breakdown** in any of the current modals.
- ✅ The card is well-structured and uses a clean vehicle selector + action button pattern.
---
## 2. Existing Cost Form Audit
### 2.1 `SimpleFuelModal.vue`
**File:** [`frontend/src/components/dashboard/SimpleFuelModal.vue`](frontend/src/components/dashboard/SimpleFuelModal.vue)
| Field | Type | Required |
|-------|------|----------|
| Vehicle (read-only) | Display | — |
| Date | `date` input | ✅ |
| Amount (HUF) | `number` | ✅ |
| Quantity (Liters/kWh) | `number` | ✅ |
| Odometer | `number` | ❌ |
**Payload:** `asset_id`, `category_id` (FUEL resolved by code), `amount_gross`, `currency`, `date`, `mileage_at_cost`, `description`, `data.quantity`, `data.fuel_type`.
### 2.2 `ComplexExpenseModal.vue`
**File:** [`frontend/src/components/dashboard/ComplexExpenseModal.vue`](frontend/src/components/dashboard/ComplexExpenseModal.vue)
| Field | Type | Required |
|-------|------|----------|
| Vehicle (read-only) | Display | — |
| Date | `date` input | ✅ |
| Amount (HUF) | `number` | ✅ |
| Main Category | `<select>` cascade | ✅ (if not fee mode) |
| Sub Category | `<select>` cascade | ✅ (if available) |
| Description / Notes | `<textarea>` | ❌ |
| Recurring Toggle | Checkbox | ❌ (hidden in fee mode) |
**Dual Mode:**
- `isFeeMode=true` → Pre-fills category as FEES (code lookup), hides category cascade.
- `isFeeMode=false` → Full category cascade from `/dictionaries/cost-categories`.
### 2.3 `CostManagerModal.vue`
**File:** [`frontend/src/components/cost/CostManagerModal.vue`](frontend/src/components/cost/CostManagerModal.vue)
This is a **read-only cost list viewer**, not an entry form. It displays paginated costs from `/expenses` with date, type badge, amount, vehicle, and description columns. No create/edit functionality.
### 2.4 `FinancialsTab.vue` (Vehicle Detail)
**File:** [`frontend/src/components/vehicles/tabs/FinancialsTab.vue`](frontend/src/components/vehicles/tabs/FinancialsTab.vue)
Shows **purchasing/leasing financials** (purchase price, down payment, monthly installment, contract number, etc.). This is about vehicle acquisition financing, not operational cost entry.
### 2.5 `FinancialCard.vue` (Dashboard)
**File:** [`frontend/src/components/dashboard/FinancialCard.vue`](frontend/src/components/dashboard/FinancialCard.vue)
Shows wallet/credit balance (purchased credits, service coins, earned, voucher). No cost entry functionality.
---
## 3. Gap Analysis: What's Missing for Dual-Entry Cost System
### Current State (Quick Actions Only)
| Feature | Status |
|---------|--------|
| Fuel entry (liters + odometer) | ✅ `SimpleFuelModal` |
| Parking / Toll quick entry | ✅ `ComplexExpenseModal` (fee mode) |
| Other cost with category cascade | ✅ `ComplexExpenseModal` (complex mode) |
| Recurring cost toggle | ✅ In complex mode |
| **Supplier / Vendor field** | ❌ **Missing** |
| **Invoice number** | ❌ **Missing** |
| **Invoice date (separate from cost date)** | ❌ **Missing** |
| **VAT amount / VAT rate** | ❌ **Missing** |
| **Net amount (split from gross)** | ❌ **Missing** |
| **Line items (multi-item invoice)** | ❌ **Missing** |
| **Document upload (PDF invoice)** | ❌ **Missing** |
| **Payment due date** | ❌ **Missing** |
| **Cost center / department allocation** | ❌ **Missing** |
### Verdict: Build from Scratch or Extend?
**Recommendation: BUILD FROM SCRATCH** for the Deep Wizard (`CostEntryWizard.vue`).
**Rationale:**
1. The existing `ComplexExpenseModal` is tightly coupled to the "quick entry" paradigm — single amount, single category, no supplier.
2. Adding supplier + invoice number + VAT + line items would require a complete form rewrite anyway.
3. The Quick Actions (Fuel, Parking, Other) are **perfect as-is** for the "Dual-Entry" left side. They should remain untouched.
4. The Deep Wizard should be a **new, separate component** (`CostEntryWizard.vue`) with multi-step or multi-section layout:
- **Step 1:** Vehicle + Supplier + Invoice metadata
- **Step 2:** Line items (category, net, VAT, gross)
- **Step 3:** Document upload + Review
---
## 4. Proposed Architecture for Dual-Entry Cost System
```
┌─────────────────────────────────────────────────────┐
│ CostsActionsCard.vue (EXISTING) │
│ │
│ ┌──────────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ ⛽ Fuel │ │ 🅿️ Park │ │ Other │ │
│ │ (Quick) │ │ (Quick) │ │ (Quick) │ │
│ └──────────────┘ └──────────┘ └───────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ 📄 Invoice / Számla (Deep Wizard) [NEW] │ │
│ │ → Opens CostEntryWizard.vue │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
### New Component: `CostEntryWizard.vue`
- **Location:** `frontend/src/components/cost/CostEntryWizard.vue`
- **Trigger:** New "📄 Invoice" button on `CostsActionsCard`
- **Features:**
- Supplier autocomplete (from `/providers` API)
- Invoice number + invoice date
- Net amount + VAT amount + Gross amount (auto-calc)
- Multi-line item support (dynamic rows)
- Category cascade (reuse from `ComplexExpenseModal`)
- Document upload (PDF/image)
- Payment due date
- Notes field
---
## 5. Summary
| Aspect | Finding |
|--------|---------|
| **Quick Actions** | ✅ Fully functional for Fuel, Parking/Toll, Other costs |
| **Deep Invoice Entry** | ❌ Does not exist — must be built as `CostEntryWizard.vue` |
| **Reusable Form Parts** | ⚠️ Category cascade logic can be extracted from `ComplexExpenseModal` |
| **Supplier Data** | ✅ Provider API exists (`/providers`) — can be reused |
| **Backend Readiness** | ⚠️ Need to verify if `asset_costs` table supports `supplier_id`, `invoice_number`, `vat_amount`, `net_amount` fields |

271
docs/dashboard_ui_audit.md Normal file
View File

@@ -0,0 +1,271 @@
# P0 Audit: Private Dashboard (`/dashboard`) UI & State Architecture
**Date:** 2026-06-21
**Scope:** Read-only audit of routing, components, store, and navigation paths
**Auditor:** Fast Coder (Core Developer)
---
## 1. Routing Architecture
**Router file:** [`frontend/src/router/index.ts`](frontend/src/router/index.ts)
The `/dashboard` route is a **child route** under the [`PrivateLayout.vue`](frontend/src/layouts/PrivateLayout.vue) wrapper:
| Path | Name | Component | Auth |
|------|------|-----------|------|
| `/dashboard` | `dashboard` | [`DashboardView.vue`](frontend/src/views/DashboardView.vue) | `requiresAuth` |
| `/dashboard/vehicles` | `FleetView` | [`FleetView.vue`](frontend/src/views/vehicles/FleetView.vue) | `requiresAuth` |
| `/dashboard/vehicles/:id` | `VehicleDetails` | [`VehicleDetailsView.vue`](frontend/src/views/vehicles/VehicleDetailsView.vue) | `requiresAuth` |
| `/dashboard/service-finder` | `service-finder` | [`ServiceFinderView.vue`](frontend/src/views/ServiceFinderView.vue) | `requiresAuth` |
| `/dashboard/subscription` | `subscription` | [`SubscriptionPlansView.vue`](frontend/src/views/SubscriptionPlansView.vue) | `requiresAuth` |
**Corporate mirror routes** exist under `/organization/:id/` with identical child structure.
---
## 2. DashboardView.vue — Main Entry Point
**File:** [`frontend/src/views/DashboardView.vue`](frontend/src/views/DashboardView.vue)
### Data Fetching (Lifecycle)
```typescript
onMounted(() => {
vehicleStore.fetchVehicles() // GET /assets/vehicles
authStore.fetchMyOrganizations() // GET /organizations/my
})
```
- **`fetchVehicles()`** is called unconditionally on mount.
- **`fetchMyOrganizations()`** loads the user's org list for corporate mode detection.
- No cost/financial data is fetched at the dashboard level — costs are lazy-loaded per-vehicle in the detail modal.
### 5-Card Grid Layout
The dashboard renders 5 cards in a `grid-cols-1 md:grid-cols-3 lg:grid-cols-5` layout:
| # | Card Component | File | Events |
|---|---------------|------|--------|
| 1 | `MyVehiclesCard` | [`frontend/src/components/dashboard/MyVehiclesCard.vue`](frontend/src/components/dashboard/MyVehiclesCard.vue) | `@open-card` |
| 2 | `CostsActionsCard` | [`frontend/src/components/dashboard/CostsActionsCard.vue`](frontend/src/components/dashboard/CostsActionsCard.vue) | `@cost-saved` |
| 3 | `ServiceFinderCard` | [`frontend/src/components/dashboard/ServiceFinderCard.vue`](frontend/src/components/dashboard/ServiceFinderCard.vue) | — |
| 4 | `GamificationCard` | [`frontend/src/components/dashboard/GamificationCard.vue`](frontend/src/components/dashboard/GamificationCard.vue) | `@open-card` |
| 5 | `ProfileTrustCard` | [`frontend/src/components/dashboard/ProfileTrustCard.vue`](frontend/src/components/dashboard/ProfileTrustCard.vue) | `@open-card` |
### Modal System
The DashboardView manages **3 modal layers**:
1. **Flip Modal** (`activeCard`): A 3D-flip overlay that shows `PrivateVehicleManager` when `activeCard === 'vehicles'`.
2. **VehicleDetailModal**: Deep-link modal with 4 tabs (Basics, Finance, Alerts, ServiceBook).
3. **VehicleFormModal**: Add/Edit vehicle form (standalone, decoupled from the manager).
---
## 3. MyVehiclesCard — The Garage Tile
**File:** [`frontend/src/components/dashboard/MyVehiclesCard.vue`](frontend/src/components/dashboard/MyVehiclesCard.vue)
### Data Source
```typescript
const displayVehicles = computed(() => vehicleStore.sortedVehicles)
```
Uses the Pinia store's `sortedVehicles` getter directly. Shows max **3 vehicles** (`.slice(0, 3)`).
### Vehicle Rendering
Each vehicle is rendered via [`VehicleCardCompact`](frontend/src/components/vehicle/VehicleCardCompact.vue) — a minimal row component showing:
- `license_plate` (via `VehiclePlateBadge`)
- `brand` / `model` / `nickname`
### Click Actions
- **Card click** → `navigateToFleet()``/dashboard/vehicles` (or `/organization/:id/vehicles`)
- **Plate click** → `openVehicleDetail(vehicle)``/dashboard/vehicles/:id` (or `/organization/:id/vehicles/:id`)
---
## 4. VehicleCardStandard — Full Tile Component
**File:** [`frontend/src/components/vehicle/VehicleCardStandard.vue`](frontend/src/components/vehicle/VehicleCardStandard.vue)
### Props Used (template bindings)
| Template Expression | Backend `AssetResponse` Field | Status |
|---|---|---|
| `vehicle.image` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN**`AssetResponse` has no `image` field |
| `vehicle.brand` | ✅ `brand` | OK |
| `vehicle.nickname` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN** — No `nickname` field in schema |
| `vehicle.license_plate` | ✅ `license_plate` | OK |
| `vehicle.countryCode` / `vehicle.country_code` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN** — No country code field |
| `vehicle.model` | ✅ `model` | OK |
| `vehicle.year_of_manufacture` / `vehicle.year` | ✅ `year_of_manufacture` | OK |
| `vehicle.current_mileage` / `vehicle.mileage` | ✅ `current_mileage` | OK |
| `vehicle.engine` / `vehicle.fuel_type` | ❌ `engine` **NOT in AssetResponse** | ⚠️ **BROKEN**`engine` is a display-only field; `fuel_type` is OK |
| `vehicle.individual_equipment?.color` / `vehicle.color` | ❌ `color` **NOT in AssetResponse** | ⚠️ **BROKEN**`color` is stored in `individual_equipment` JSONB, but the root `color` field doesn't exist |
| `vehicle.nextService` | ❌ **NOT in AssetResponse** | ⚠️ **BROKEN** — No `nextService` field |
### Critical Broken Bindings Summary
| Field | Used In | Backend Reality |
|-------|---------|-----------------|
| `vehicle.image` | CardStandard (image area) | Not in `AssetResponse` — always shows placeholder |
| `vehicle.nickname` | CardStandard, CardCompact | Not in `AssetResponse` — always null |
| `vehicle.countryCode` / `country_code` | CardStandard, CardCompact, DetailModal | Not in `AssetResponse` — plate badge shows no country |
| `vehicle.engine` | CardStandard (engine row) | Not in `AssetResponse` — falls back to `fuel_type` |
| `vehicle.color` | CardStandard (color row) | Not in `AssetResponse` — stored in `individual_equipment.color` |
| `vehicle.nextService` | CardStandard (service row) | Not in `AssetResponse` — always shows "—" |
| `vehicle.mileage` (deprecated) | CardStandard, DetailModal | Falls back to `current_mileage` — OK but deprecated |
---
## 5. Pinia Store Audit
**File:** [`frontend/src/stores/vehicle.ts`](frontend/src/stores/vehicle.ts)
### API Endpoint
```typescript
const res = await api.get('/assets/vehicles')
```
**Correct endpoint** — matches backend `GET /api/v1/assets/vehicles`.
### Vehicle Interface vs Backend Schema
The frontend `Vehicle` interface (line 10-109) is **mostly aligned** with the backend [`AssetResponse`](backend/app/schemas/asset.py:32). However:
| Frontend `Vehicle` Field | Backend `AssetResponse` | Match |
|---|---|---|
| `id: string` | `id: UUID` | ✅ |
| `vin` | `vin` | ✅ |
| `license_plate` | `license_plate` | ✅ |
| `name` | `name` | ✅ |
| `catalog_id` | `catalog_id` | ✅ |
| `vehicle_class` | `vehicle_class` | ✅ |
| `brand` | `brand` | ✅ |
| `model` | `model` | ✅ |
| `trim_level` | `trim_level` | ✅ |
| `fuel_type` | `fuel_type` | ✅ |
| `engine_capacity` | `engine_capacity` | ✅ |
| `power_kw` | `power_kw` | ✅ |
| `torque_nm` | `torque_nm` | ✅ |
| `cylinder_layout` | `cylinder_layout` | ✅ |
| `transmission_type` | `transmission_type` | ✅ |
| `drive_type` | `drive_type` | ✅ |
| `euro_classification` | `euro_classification` | ✅ |
| `curb_weight` | `curb_weight` | ✅ |
| `max_weight` | `max_weight` | ✅ |
| `door_count` | `door_count` | ✅ |
| `seat_count` | `seat_count` | ✅ |
| `current_mileage` | `current_mileage` | ✅ |
| `condition_score` | `condition_score` | ✅ |
| `status` | `status` | ✅ |
| `is_verified` | `is_verified` | ✅ |
| `year_of_manufacture` | `year_of_manufacture` | ✅ |
| `created_at` | `created_at` | ✅ |
| `updated_at` | `updated_at` | ✅ |
| `is_under_warranty` | `is_under_warranty` | ✅ |
| `warranty_expiry_date` | `warranty_expiry_date` | ✅ |
| `registration_certificate_number` | `registration_certificate_number` | ✅ |
| `registration_certificate_validity` | `registration_certificate_validity` | ✅ |
| `vehicle_registration_document_number` | `vehicle_registration_document_number` | ✅ |
| `registration_country` | `registration_country` | ✅ |
| `first_domestic_registration_date` | `first_domestic_registration_date` | ✅ |
| `import_country` | `import_country` | ✅ |
| `title_document_number` | `title_document_number` | ✅ |
| `engine_number` | `engine_number` | ✅ |
| `number_of_previous_owners` | `number_of_previous_owners` | ✅ |
| `is_for_sale` | `is_for_sale` | ✅ |
| `price` | `price` | ✅ |
| `currency` | `currency` | ✅ |
| `is_primary` | `is_primary` | ✅ |
| `current_organization_id` | `current_organization_id` | ✅ |
| `owner_organization_id` | `owner_organization_id` | ✅ |
| `individual_equipment` | `individual_equipment` | ✅ |
**The store interface is well-aligned with the backend.** The broken bindings are in the **display components** (`VehicleCardStandard`, `VehicleCardCompact`) which reference fields that exist in the `VehicleData` type (a superset with mock-data fallbacks) but not in the actual backend response.
---
## 6. Navigation / Drill-Down Paths
### Path 1: Dashboard → Fleet View
```
/dashboard → click card → /dashboard/vehicles (FleetView.vue)
```
FleetView renders all vehicles in a `BaseCard` grid. Clicking a card navigates to:
```
/dashboard/vehicles/:id (VehicleDetailsView.vue)
```
### Path 2: Dashboard → Vehicle Detail Modal (in-page)
```
/dashboard → plate-click → VehicleDetailModal (Teleport)
```
This is an **in-page modal**, not a route change. The modal has 4 tabs:
- **Alapadatok** (Basics) — reads from `AssetResponse` directly
- **Pénzügyek (TCO)** — lazy-fetches `GET /assets/{id}/costs`
- **Figyelmeztetések** — reads from `individual_equipment` JSONB
- **Szervizkönyv** — lazy-fetches `GET /assets/{id}/events`
### Path 3: Dashboard → Flip Modal → PrivateVehicleManager
```
/dashboard → "My Vehicles" card click → Flip Modal → PrivateVehicleManager
```
The PrivateVehicleManager shows `VehicleCardStandard` tiles in a carousel. Clicking a tile opens the same `VehicleDetailModal`.
### Path 4: Corporate Mode
```
/organization/:id → /organization/:id/vehicles/:vehicleId
```
All navigation functions in `MyVehiclesCard`, `FleetView`, and `PrivateVehicleManager` check `authStore.isCorporateMode` and route accordingly.
---
## 7. Broken Bindings & Immediate Fixes Required
### 🔴 CRITICAL (Data never renders)
| # | Component | Field | Impact |
|---|-----------|-------|--------|
| 1 | `VehicleCardStandard` | `vehicle.image` | Image area always shows placeholder SVG |
| 2 | `VehicleCardStandard` | `vehicle.nickname` | Nickname section always empty |
| 3 | `VehicleCardStandard` | `vehicle.countryCode` / `country_code` | Plate badge has no country flag |
| 4 | `VehicleCardStandard` | `vehicle.engine` | Engine row falls back to `fuel_type` (acceptable but not ideal) |
| 5 | `VehicleCardStandard` | `vehicle.color` | Color row always shows "—" |
| 6 | `VehicleCardStandard` | `vehicle.nextService` | Service row always shows "—" |
### 🟡 MODERATE (Fallback works but data is incomplete)
| # | Component | Field | Impact |
|---|-----------|-------|--------|
| 7 | `VehicleCardStandard` | `vehicle.mileage` (deprecated) | Falls back to `current_mileage` — OK |
| 8 | `VehicleDetailModal` | `vehicle.body_type` | Falls back to `individual_equipment.car_specs.body_type` — OK |
### 🟢 GREEN (Fully functional)
- All fields in the Pinia store `Vehicle` interface match the backend `AssetResponse`
- The `fetchVehicles()` endpoint (`GET /assets/vehicles`) is correct
- The `fetchAssetFinancials()` endpoint (`GET /assets/vehicles/{id}/financials`) is correct
- Cost fetching (`GET /assets/{id}/costs`) is correct
- Event fetching (`GET /assets/{id}/events`) is correct
- Navigation paths correctly distinguish private vs corporate mode
---
## 8. Recommendations
1. **Add missing fields to `AssetResponse`** (or use `individual_equipment` JSONB):
- `nickname` → add to `AssetResponse` as an optional string
- `country_code` → already in `individual_equipment`, expose at root level
- `next_service_mileage` → computed field or stored in `individual_equipment`
2. **Fix `VehicleCardStandard` to use `individual_equipment.color`** instead of `vehicle.color`:
```typescript
vehicle.individual_equipment?.color || vehicle.color || '—'
```
This already works for the color field (line 103) but `vehicle.color` is never set by the API.
3. **Remove `vehicle.image` from `VehicleCardStandard`** or add an image URL field to the backend schema if vehicle images are planned.
4. **Add `country_code` to `AssetResponse`** at the root level so the plate badge can display country flags without digging into JSONB.
5. **Consider adding a `next_service` computed field** to the backend `AssetResponse` or the store getter, derived from `current_mileage` + service interval.

View File

@@ -0,0 +1,227 @@
# P0 DEEP AUDIT - Database Consistency & Zombie API Hunt Report
**Date:** 2026-06-21
**Author:** Fast Coder (Core Developer)
**Gitea Issue:** #274
**Scope:** Database schemas `vehicle`, `finance`, `fleet_finance` + Backend API endpoints
---
## 1. Database Cleanliness
### 1.1 Schema: `vehicle` (20 tables)
| Table | Purpose | Status |
|-------|---------|--------|
| `assets` | Core vehicle assets (thick model) | ✅ Clean |
| `asset_events` | Service/maintenance events | ✅ Clean |
| `asset_inspections` | Inspection records | ✅ Clean |
| `asset_reviews` | User reviews | ✅ Clean |
| `asset_telemetry` | Telemetry data | ✅ Clean |
| `auto_data_crawler_queue` | Crawler queue | ✅ Clean |
| `catalog_discovery` | Catalog discovery queue | ✅ Clean |
| `dict_body_types` | Body type dictionary | ✅ Clean |
| `external_reference_library` | External reference data | ✅ Clean |
| `feature_definitions` | Feature definitions | ✅ Clean |
| `gb_catalog_discovery` | GB catalog discovery | ✅ Clean |
| `model_feature_maps` | Model-feature mappings | ✅ Clean |
| `motorcycle_specs` | Motorcycle specs | ✅ Clean |
| `odometer_readings` | Odometer readings | ✅ Clean |
| `reference_lookup` | Reference lookup | ✅ Clean |
| `vehicle_catalog` | Vehicle catalog | ✅ Clean |
| `vehicle_logbook` | Trip logbook | ✅ Clean |
| `vehicle_model_definitions` | Master model definitions | ✅ Clean |
| `vehicle_ownership_history` | Ownership history | ✅ Clean |
| `vehicle_transfer_requests` | Transfer requests | ✅ Clean |
| `vehicle_types` | Vehicle type dictionary | ✅ Clean |
| `vehicle_user_ratings` | User ratings | ✅ Clean |
### 1.2 Schema: `fleet_finance` (6 tables)
| Table | Purpose | Status |
|-------|---------|--------|
| `asset_costs` | Cost ledger (moved from `vehicle`) | ✅ Clean |
| `asset_financials` | Purchase/financing data (moved from `vehicle`) | ✅ Clean |
| `cost_categories` | Cost category hierarchy (moved from `vehicle`) | ✅ Clean |
| `insurance_providers` | Insurance provider catalog | ✅ Clean |
| `vehicle_insurance_policies` | Vehicle insurance policies | ✅ Clean |
| `vehicle_tax_obligations` | Vehicle tax obligations | ✅ Clean |
### 1.3 Schema: `finance` (7 tables)
| Table | Purpose | Status |
|-------|---------|--------|
| `credit_logs` | Credit transactions | ✅ Clean |
| `exchange_rates` | Currency exchange rates | ✅ Clean |
| `issuers` | Billing entities | ✅ Clean |
| `org_subscriptions` | Organization subscriptions | ✅ Clean |
| `payment_intents` | Payment intents | ✅ Clean |
| `user_subscriptions` | User subscriptions | ✅ Clean |
| `withdrawal_requests` | Withdrawal requests | ✅ Clean |
### 1.4 Critical Checks
| Check | Result |
|-------|--------|
| `vehicle.vehicle_expenses` exists? | ❌ **NOT FOUND** - Correctly removed |
| `vehicle.costs` (VehicleCost) exists? | ❌ **NOT FOUND** - Correctly removed |
| `data` schema has leftover tables? | ❌ **NOT FOUND** - Schema is empty |
| Duplicate table names between `vehicle` and `fleet_finance`? | ❌ **NONE** - No overlap |
| Legacy `expense`/`cost` tables anywhere? | ❌ **NONE** - Clean |
**Verdict: ✅ DATABASE IS CLEAN.** No duplicated tables, no legacy tables left behind. The migration of `AssetCost`, `CostCategory`, `AssetFinancials` from `vehicle``fleet_finance` schema is complete.
---
## 2. Zombie API Endpoints
### 2.1 🔴 CRITICAL: [`reports.py`](backend/app/api/v1/endpoints/reports.py) - References `vehicle.vehicle_expenses`
**File:** [`backend/app/api/v1/endpoints/reports.py`](backend/app/api/v1/endpoints/reports.py:18)
Two endpoints use raw SQL queries referencing the **non-existent** `vehicle.vehicle_expenses` table:
1. **`GET /reports/summary/{vehicle_id}`** (line 8-32) - Queries `FROM vehicle.vehicle_expenses`
2. **`GET /reports/trends/{vehicle_id}`** (line 34-50) - Queries `FROM vehicle.vehicle_expenses`
**Impact:** These endpoints will **ALWAYS return 500 errors** because the table `vehicle.vehicle_expenses` does not exist in the database.
**Router registration:** [`api.py`](backend/app/api/v1/api.py:33) - Registered as `prefix="/reports"`
### 2.2 🟡 WARNING: [`expenses.py`](backend/app/api/v1/endpoints/expenses.py) - Uses `AssetCost` from `fleet_finance` (correct)
**File:** [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py:10)
Imports `AssetCost` and `CostCategory` from `app.models` which correctly resolves to `fleet_finance` schema models. **This is NOT a zombie** - it's correctly using the moved models.
### 2.3 🟡 WARNING: [`assets.py`](backend/app/api/v1/endpoints/assets.py) - Uses `AssetCost` from `fleet_finance` (correct)
**File:** [`backend/app/api/v1/endpoints/assets.py`](backend/app/api/v1/endpoints/assets.py:14-15)
Imports `AssetCost`, `CostCategory` from `app.models` and `AssetFinancials`, `VehicleInsurancePolicy`, `VehicleTaxObligation` from `app.models.fleet_finance`. **Correctly references the new schema.**
### 2.4 🟢 OK: [`dictionaries.py`](backend/app/api/v1/endpoints/dictionaries.py) - Uses `CostCategory` from `fleet_finance`
**File:** [`backend/app/api/v1/endpoints/dictionaries.py`](backend/app/api/v1/endpoints/dictionaries.py:13)
Imports `CostCategory` from `app.models.fleet_finance`. **Correct.**
### 2.5 Zombie API Summary
| Endpoint File | Route Prefix | Status | Issue |
|---------------|-------------|--------|-------|
| `reports.py` | `/reports/summary/{id}` | 🔴 **ZOMBIE** | References `vehicle.vehicle_expenses` (DNE) |
| `reports.py` | `/reports/trends/{id}` | 🔴 **ZOMBIE** | References `vehicle.vehicle_expenses` (DNE) |
| `reports.py` | `/reports/summary/latest` | 🟢 OK | Returns mock data only |
| `expenses.py` | `/expenses/{asset_id}` | 🟢 OK | Uses `AssetCost` from `fleet_finance` |
| `assets.py` | `/assets/vehicles/{id}/costs` | 🟢 OK | Uses `AssetCost` from `fleet_finance` |
| `assets.py` | `/assets/vehicles/{id}/insurance` | 🟢 OK | Uses `VehicleInsurancePolicy` |
| `assets.py` | `/assets/vehicles/{id}/tax` | 🟢 OK | Uses `VehicleTaxObligation` |
| `dictionaries.py` | `/dictionaries/cost-categories` | 🟢 OK | Uses `CostCategory` from `fleet_finance` |
---
## 3. InsuranceProvider & Missing CRUD Analysis
### 3.1 InsuranceProvider Model
The [`InsuranceProvider`](backend/app/models/fleet_finance/models.py:180) model exists in `fleet_finance.insurance_providers` with fields:
- `id` (Integer, PK)
- `name` (String, unique)
- `claim_phone` (String, nullable)
- `claim_url` (String, nullable)
- `services_offered` (JSONB)
- `is_active` (Boolean)
### 3.2 Existing Insurance/Tax Endpoints in [`assets.py`](backend/app/api/v1/endpoints/assets.py)
| Method | Route | Exists? |
|--------|-------|---------|
| `GET` | `/assets/vehicles/{asset_id}/insurance` | ✅ Yes (list) |
| `POST` | `/assets/vehicles/{asset_id}/insurance` | ✅ Yes (create) |
| `PUT` | `/assets/vehicles/{asset_id}/insurance/{policy_id}` | ❌ **MISSING** |
| `DELETE` | `/assets/vehicles/{asset_id}/insurance/{policy_id}` | ❌ **MISSING** |
| `GET` | `/assets/vehicles/{asset_id}/tax` | ✅ Yes (list) |
| `POST` | `/assets/vehicles/{asset_id}/tax` | ✅ Yes (create) |
| `PUT` | `/assets/vehicles/{asset_id}/tax/{tax_id}` | ❌ **MISSING** |
| `DELETE` | `/assets/vehicles/{asset_id}/tax/{tax_id}` | ❌ **MISSING** |
### 3.3 InsuranceProvider CRUD (Standalone)
| Method | Route | Exists? |
|--------|-------|---------|
| `GET` | `/insurance-providers` | ❌ **MISSING** |
| `GET` | `/insurance-providers/{id}` | ❌ **MISSING** |
| `POST` | `/insurance-providers` | ❌ **MISSING** |
| `PUT` | `/insurance-providers/{id}` | ❌ **MISSING** |
| `DELETE` | `/insurance-providers/{id}` | ❌ **MISSING** |
**No schema exists for InsuranceProvider** - searched `backend/app/schemas/` for `InsuranceProvider` patterns - **0 results**.
---
## 4. Action Plan
### 4.1 🔴 P0 - Fix Zombie Endpoints in `reports.py`
**Problem:** Two endpoints in [`reports.py`](backend/app/api/v1/endpoints/reports.py) reference the non-existent `vehicle.vehicle_expenses` table.
**Solution:** Rewrite the raw SQL queries to use the new `fleet_finance.asset_costs` table with proper joins to `cost_categories`:
```python
# Replace raw SQL with SQLAlchemy ORM queries using AssetCost + CostCategory
from app.models import AssetCost, CostCategory
from sqlalchemy import select, func
# GET /reports/summary/{vehicle_id}
stmt = select(
CostCategory.code.label("category"),
func.sum(AssetCost.amount_gross).label("total_amount"),
func.count(AssetCost.id).label("transaction_count")
).outerjoin(CostCategory, AssetCost.category_id == CostCategory.id
).where(AssetCost.asset_id == vehicle_id
).group_by(CostCategory.code)
```
**Estimated effort:** 30 minutes
### 4.2 🟡 P1 - Add UPDATE/DELETE for Insurance Policies and Tax Obligations
**Missing endpoints in [`assets.py`](backend/app/api/v1/endpoints/assets.py):**
1. `PUT /assets/vehicles/{asset_id}/insurance/{policy_id}` - Update insurance policy
2. `DELETE /assets/vehicles/{asset_id}/insurance/{policy_id}` - Delete insurance policy
3. `PUT /assets/vehicles/{asset_id}/tax/{tax_id}` - Update tax obligation
4. `DELETE /assets/vehicles/{asset_id}/tax/{tax_id}` - Delete tax obligation
**Estimated effort:** 1 hour
### 4.3 🟡 P1 - Create InsuranceProvider CRUD Endpoints
**Missing:** Complete CRUD for standalone `InsuranceProvider` catalog:
1. Create schema: `InsuranceProviderResponse`, `InsuranceProviderCreate`, `InsuranceProviderUpdate`
2. Create new endpoint file or add to existing router
3. Endpoints: `GET /insurance-providers`, `GET /insurance-providers/{id}`, `POST /insurance-providers`, `PUT /insurance-providers/{id}`, `DELETE /insurance-providers/{id}`
**Estimated effort:** 1.5 hours
### 4.4 🟢 P2 - Add InsuranceProvider to Admin UI
Once the API endpoints exist, add the InsuranceProvider management to the admin panel.
**Estimated effort:** 1 hour
---
## 5. Summary
| Category | Status |
|----------|--------|
| **Database Cleanliness** | ✅ **PASS** - No duplicates, no legacy tables |
| **Zombie API Endpoints** | 🔴 **2 CRITICAL** - `reports.py` references `vehicle.vehicle_expenses` |
| **InsuranceProvider CRUD** | ❌ **FULLY MISSING** - No standalone endpoints |
| **Insurance Policy UPDATE/DELETE** | ❌ **MISSING** - Only GET and POST exist |
| **Tax Obligation UPDATE/DELETE** | ❌ **MISSING** - Only GET and POST exist |
**Total estimated fix effort:** ~4 hours

774
docs/db_schema_audit.md Normal file
View File

@@ -0,0 +1,774 @@
# 🏗️ P0 DEEP ARCHITECTURE AUDIT — Teljes Adatbázis Séma Audit
**Dátum:** 2026-06-20
**Hatáskör:** Teljes SQLAlchemy modellréteg (`backend/app/models/`)
**Elemzett fájlok:** 26 modellfájl
**Feltárt kapcsolatok:** 95 ForeignKey, 65 relationship() deklaráció
**JSONB mezők:** 56 darab
---
## Tartalomjegyzék
1. [Séma Áttekintés](#1-séma-áttekintés)
2. [Teljes Táblalista Sémánként](#2-teljes-táblalista-sémánként)
3. [Kapcsolati Térkép (Entity-Relationship)](#3-kapcsolati-térkép-entity-relationship)
4. [Központi Entitás: Asset (Digital Twin)](#4-központi-entitás-asset-digital-twin)
5. [JSONB Mezők Teljes Feltárása](#5-jsonb-mezők-teljes-feltárása)
6. [Redundancia Analízis](#6-redundancia-analízis)
7. [Hiányosság Elemzés — Admin, Biztosítás, Adó](#7-hiányosság-elemzés)
8. [Architekturális Javaslat — Admin & Pénzügyi Adatok Tárolása](#8-architekturális-javaslat)
9. [Függelék: Teljes FK-relationship Mátrix](#9-függelék-teljes-fk-relationship-mátrix)
---
## 1. Séma Áttekintés
Az adatbázis **9 PostgreSQL sémára** van bontva, Domain-Driven Design (DDD) elvek szerint:
| Séma | Táblák száma | Elsődleges Domain |
|------|-------------|-------------------|
| `vehicle` | ~28 | Járművek, katalógus, költségek, események |
| `identity` | 10 | Személyek, felhasználók, tárcák, eszközök |
| `fleet` | 7 | Szervezetek, tagok, telephelyek, asset-hozzárendelés |
| `system` | 14 | Rendszerparaméterek, dokumentumok, értesítések, címek |
| `marketplace` | 10 | Szolgáltatók, szakértői címkék, felfedezés |
| `finance` | 7 | Kibocsátók, fizetési szándékok, kivételi kérelmek |
| `audit` | 4 | Biztonsági napló, műveleti napló, főkönyv |
| `gamification` | 9 | Pontok, szintek, jelvények, szezonok |
| `marketing` | 7 | Kampányok, kreatívok, elhelyezések, hirdetések |
| **ÖSSZESEN** | **~96 tábla** | |
---
## 2. Teljes Táblalista Sémánként
### 2.1 `vehicle` séma (~28 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `vehicle.vehicle_types` | `VehicleType` | `vehicle_definitions.py:17` | Jármű kategóriák JSONB `units`-szal |
| `vehicle.feature_definitions` | `FeatureDefinition` | `vehicle_definitions.py:33` | Felszereltség jegyzék |
| `vehicle.vehicle_model_definitions` | `VehicleModelDefinition` | `vehicle_definitions.py:48` | **MDM Master** — 60+ mező, 4 JSONB, UniqueConstraint 7 mezőn |
| `vehicle.model_feature_maps` | `ModelFeatureMap` | `vehicle_definitions.py:148` | M:N modell↔felszereltség |
| `vehicle.dict_body_types` | `BodyTypeDictionary` | `vehicle_definitions.py:162` | Karosszéria típusok i18n-nel |
| `vehicle.vehicle_catalog` | `AssetCatalog` | `asset.py:14` | Katalógus bejegyzés, JSONB `factory_data` |
| `vehicle.assets` | `Asset` | `asset.py:69` | **Digital Twin** — 84 oszlop, központi entitás |
| `vehicle.asset_financials` | `AssetFinancials` | `asset.py:246` | Beszerzési/amortizációs adatok |
| `vehicle.asset_costs` | `AssetCost` | `asset.py:271` | TCO költségek |
| `vehicle.vehicle_logbook` | `VehicleLogbook` | `asset.py:312` | NAV menetlevél GPS-szel |
| `vehicle.asset_inspections` | `AssetInspection` | `asset.py:341` | Napi ellenőrző lista |
| `vehicle.asset_reviews` | `AssetReview` | `asset.py:357` | Jármű értékelések |
| `vehicle.vehicle_ownership_history` | `VehicleOwnership` | `asset.py:373` | Tulajdonosváltozások |
| `vehicle.asset_telemetry` | `AssetTelemetry` | `asset.py:389` | Telemetria (1:1) |
| `vehicle.odometer_readings` | `OdometerReading` | `asset.py:398` | Kilométeróra állások időrendben |
| `vehicle.asset_assignments` | `AssetAssignment` | `asset.py:415` | Asset↔Organization kapcsolat |
| `vehicle.asset_events` | `AssetEvent` | `asset.py:447` | **Digitális Szervizkönyv** |
| `vehicle.exchange_rates` | `ExchangeRate` | `asset.py:492` | Árfolyamok |
| `vehicle.catalog_discovery` | `CatalogDiscovery` | `asset.py:499` | Robot felfedező munkaterület |
| `vehicle.vehicle_expenses` | `VehicleExpenses` | `asset.py:528` | Költség reporting |
| `vehicle.vehicle_transfer_requests` | `VehicleTransferRequest` | `asset.py:546` | Asset átadás-átvétel workflow |
| `vehicle.cost_categories` | `CostCategory` | `vehicle.py:19` | Hierarchikus költségkategóriák |
| `vehicle.costs` | `VehicleCost` | `vehicle.py:64` | **⚠️ VMD-hez kötve, NEM Asset-hez!** |
| `vehicle.vehicle_user_ratings` | `VehicleUserRating` | `vehicle.py:112` | 4-dimenziós felhasználói értékelés |
| `vehicle.gb_catalog_discovery` | `GbCatalogDiscovery` | `vehicle.py:168` | GB piaci felfedező robot |
| `vehicle.motorcycle_specs` | `MotorcycleSpecs` | `motorcycle_specs.py:6` | Motor technikai adatok JSONB-ben |
| `vehicle.external_reference_library` | `ExternalReferenceLibrary` | `external_reference.py:6` | Külső referencia könyvtár |
| `vehicle.external_reference_queue` | `ExternalReferenceQueue` | `external_reference_queue.py:6` | Külső referencia várólista |
| `vehicle.reference_lookup` | `ReferenceLookup` | `reference_data.py` | Referencia kereső JSONB-vel |
### 2.2 `identity` séma (10 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `identity.persons` | `Person` | `identity.py:37` | **DNS** — valós személy, soha nem törölhető |
| `identity.users` | `User` | `identity.py:126` | Login entitás, GDPR törölhető |
| `identity.wallets` | `Wallet` | `identity.py:223` | Triple Wallet |
| `identity.verification_tokens` | `VerificationToken` | `identity.py:244` | Jóváhagyó tokenek |
| `identity.social_accounts` | `SocialAccount` | `identity.py:258` | Közösségi fiókok |
| `identity.active_vouchers` | `ActiveVoucher` | `identity.py:276` | Aktív utalványok |
| `identity.user_trust_profiles` | `UserTrustProfile` | `identity.py:290` | Gondos Gazda Index |
| `identity.one_time_passwords` | `OneTimePassword` | `identity.py:321` | Egyszeri jelszavak |
| `identity.devices` | `Device` | `identity.py:340` | Eszköz fingerprint |
| `identity.user_device_links` | `UserDeviceLink` | `identity.py:355` | User↔Device kapcsolat |
### 2.3 `fleet` séma (7 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `fleet.organizations` | `Organization` | `organization.py:70` | **Központi szervezet** — lifecycle |
| `fleet.org_roles` | `OrgRole` | `organization.py:39` | Dinamikus RBAC JSONB-vel |
| `fleet.organization_financials` | `OrganizationFinancials` | `organization.py:215` | Éves pénzügyi adatok |
| `fleet.organization_members` | `OrganizationMember` | `organization.py:230` | Tagok (user/person/invited_email) |
| `fleet.organization_sales_assignments` | `OrganizationSalesAssignment` | `organization.py:268` | Értékesítési hozzárendelés |
| `fleet.branches` | `Branch` | `organization.py:279` | Telephelyek PostGIS-szel |
| `fleet.asset_assignments` | `AssetAssignment` | `asset.py:415` | Asset-szervezet kapcsolat |
### 2.4 `system` séma (14 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `system.system_parameters` | `SystemParameter` | `system.py:19` | Hierarchikus paraméterek |
| `system.internal_notifications` | `InternalNotification` | `system.py:40` | Értesítések |
| `system.system_data_completion_weights` | `SystemDataCompletionWeight` | `system.py:62` | Kitöltöttségi súlyok |
| `system.system_service_staging` | `SystemServiceStaging` | `system.py:85` | Szolgáltatás staging |
| `system.documents` | `Document` | `document.py:11` | NAS-alapú doksi metatároló |
| `system.legal_documents` | `LegalDocument` | `legal.py:9` | Jogi dokumentumok |
| `system.legal_acceptances` | `LegalAcceptance` | `legal.py:24` | Jogi elfogadások |
| `system.translations` | `Translation` | `translation.py:8` | Fordítások |
| `system.geo_postal_codes` | `GeoPostalCode` | `address.py:12` | Irányítószám kereső |
| `system.geo_streets` | `GeoStreet` | `address.py:22` | Utca regiszter |
| `system.geo_street_types` | `GeoStreetType` | `address.py:31` | Utca típusok |
| `system.addresses` | `Address` | `address.py:39` | Univerzális cím GPS-szel |
| `system.pending_actions` | `PendingAction` | `security.py:22` | **Sentinel** — 4 szem elv |
| `system.subscription_tiers` | `SubscriptionTier` | `core_logic.py:12` | Előfizetési csomagok |
### 2.5 `marketplace` séma (10 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `marketplace.service_profiles` | `ServiceProfile` | `service.py:21` | Szolgáltatói profilok PostGIS-szel |
| `marketplace.expertise_tags` | `ExpertiseTag` | `service.py:79` | **4-szintű hierarchia** |
| `marketplace.service_expertises` | `ServiceExpertise` | `service.py:142` | M:N profil↔tag |
| `marketplace.service_staging` | `ServiceStaging` | `service.py:158` | Szolgáltatás staging |
| `marketplace.discovery_parameters` | `DiscoveryParameter` | `service.py:184` | Felfedezési paraméterek |
| `marketplace.costs` | `Cost` | `service.py:196` | Költségek |
| `marketplace.service_specialties` | `ServiceSpecialty` | `core_logic.py:88` | Önhivatkozó hierarchia |
| `marketplace.service_providers` | `ServiceProvider` | `social.py:22` | Közösségi szolgáltatók |
| `marketplace.ratings` | `Rating` | `address.py:77` | Univerzális értékelés |
| `marketplace.service_reviews` | `ServiceReview` | `social.py:86` | Ellenőrzött értékelések |
| `marketplace.votes` | `Vote` | `social.py:46` | Közösségi szavazatok |
### 2.6 `finance` séma (7 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `finance.issuers` | `Issuer` | `finance.py:27` | Számlakibocsátók |
| `finance.payment_intents` | `PaymentIntent` | `payment.py:30` | **Double Lock** |
| `finance.withdrawal_requests` | `WithdrawalRequest` | `payment.py:147` | Kivételi kérelmek |
| `finance.org_subscriptions` | `OrganizationSubscription` | `core_logic.py:25` | Szervezeti előfizetés |
| `finance.user_subscriptions` | `UserSubscription` | `core_logic.py:49` | Felhasználói előfizetés |
| `finance.credit_logs` | `CreditTransaction` | `core_logic.py:72` | Kredit napló |
| `finance.exchange_rates` | `ExchangeRate` | `asset.py:492` | Árfolyamok |
### 2.7 `audit` séma (4 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `audit.security_audit_logs` | `SecurityAuditLog` | `audit.py:12` | Biztonsági események |
| `audit.operational_logs` | `OperationalLog` | `audit.py:29` | Napi műveletek |
| `audit.process_logs` | `ProcessLog` | `audit.py:43` | Robot naplók |
| `audit.financial_ledger` | `FinancialLedger` | `audit.py:78` | **Központi főkönyv** |
### 2.8 `gamification` séma (9 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `gamification.point_rules` | `PointRule` | `gamification.py:13` | Akció→pont szabályok |
| `gamification.level_configs` | `LevelConfig` | `gamification.py:23` | Szintküszöbök |
| `gamification.points_ledger` | `PointsLedger` | `gamification.py:33` | XP/büntetés tranzakciók |
| `gamification.user_stats` | `UserStats` | `gamification.py:49` | Aggregált statok |
| `gamification.badges` | `Badge` | `gamification.py:71` | Jelvények |
| `gamification.user_badges` | `UserBadge` | `gamification.py:80` | M:N user↔jelvény |
| `gamification.user_contributions` | `UserContribution` | `gamification.py:95` | Hozzájárulások |
| `gamification.seasons` | `Season` | `gamification.py:136` | Szezonok |
| `gamification.seasonal_competitions` | `SeasonalCompetitions` | `gamification.py:149` | Versenyek |
### 2.9 `marketing` séma (7 tábla)
| Tábla | Modell | Fájl | Leírás |
|-------|--------|------|--------|
| `marketing.campaigns` | `Campaign` | `marketing.py:65` | Kampányok JSONB célzással |
| `marketing.creatives` | `Creative` | `marketing.py:126` | Kreatív anyagok |
| `marketing.placements` | `Placement` | `marketing.py:174` | Hirdetési felületek |
| `marketing.campaign_creatives` | `CampaignCreative` | `marketing.py:204` | M:N kampány↔kreatív |
| `marketing.campaign_placements` | `CampaignPlacement` | `marketing.py:226` | M:N kampány↔elhelyezés |
| `marketing.ad_impressions` | `AdImpression` | `marketing.py:247` | Hirdetési megjelenések |
| `marketing.ad_clicks` | `AdClick` | `marketing.py:276` | Hirdetési kattintások |
---
## 3. Kapcsolati Térkép (Entity-Relationship)
### 3.1 Vizuális Összefoglaló
```mermaid
erDiagram
ASSET ||--o{ ASSET_COST : van
ASSET ||--o{ ASSET_EVENT : van
ASSET ||--o{ VEHICLE_LOGBOOK : van
ASSET ||--o{ ASSET_INSPECTION : van
ASSET ||--o{ ASSET_REVIEW : van
ASSET ||--o{ ODOMETER_READING : van
ASSET ||--o{ VEHICLE_OWNERSHIP : van
ASSET ||--o{ ASSET_ASSIGNMENT : van
ASSET ||--o{ SERVICE_REQUEST : van
ASSET ||--o{ VEHICLE_TRANSFER : van
ASSET ||--|| ASSET_FINANCIALS : 1:1
ASSET ||--|| ASSET_TELEMETRY : 1:1
ASSET ||--o| ASSET_CATALOG : katalogus
PERSON ||--o{ USER : fiók
PERSON ||--o{ ORGANIZATION : tulajdonol
USER ||--|| WALLET : 1:1
USER ||--|| USER_TRUST_PROFILE : 1:1
USER ||--|| USER_STATS : 1:1
ORGANIZATION ||--o{ MEMBER : tagok
ORGANIZATION ||--o{ BRANCH : telephely
ORGANIZATION ||--o{ ASSET_ASSIGNMENT : jarmuvek
ORGANIZATION ||--o| SERVICE_PROFILE : szolgaltato
VMD ||--o{ ASSET_CATALOG : varians
VMD ||--o{ VEHICLE_COST : koltseg
VMD ||--o{ VEHICLE_RATING : ertekeles
```
### 3.2 Kritikus Kapcsolatok
#### ✳️ FIGYELEM: `VehicleCost` eltérő referenciája
A [`VehicleCost`](backend/app/models/vehicle/vehicle.py:64) `vehicle_id` mezeje a [`VehicleModelDefinition`](backend/app/models/vehicle/vehicle_definitions.py:48)-re mutat, **NEM** az [`Asset`](backend/app/models/vehicle/asset.py:69)-re! Ez architekturális anomália: a költségek a modelldefinícióhoz vannak kötve, nem a konkrét járműpéldányhoz.
#### ✳️ Többféle cost modell
- [`VehicleCost`](backend/app/models/vehicle/vehicle.py:64) — VMD-hez kötött
- [`AssetCost`](backend/app/models/vehicle/asset.py:271) — Asset-hez kötött (TCO)
- [`VehicleExpenses`](backend/app/models/vehicle/asset.py:528) — Asset-hez kötött (expense)
- [`Cost`](backend/app/models/marketplace/service.py:196) — MarketPlace költség
#### ✳️ Person-User Dual Entity
- [`Person`](backend/app/models/identity/identity.py:37) — DNS, halhatatlan, `merged_into_id` self-ref
- [`User`](backend/app/models/identity/identity.py:126) — Login entitás, törölhető
- `OrganizationMember` mindkettőhöz kötődhet
#### ✳️ Önhivatkozó hierarchiák (7 darab)
1. [`CostCategory`](backend/app/models/vehicle/vehicle.py:19) — `parent_id` self
2. [`ExpertiseTag`](backend/app/models/marketplace/service.py:79) — `parent_id` + `path` (4 szint)
3. [`ServiceSpecialty`](backend/app/models/core_logic.py:88) — `parent_id` self
4. [`User.referred_by_id`](backend/app/models/identity/identity.py:149) — self
5. [`User.current_sales_agent_id`](backend/app/models/identity/identity.py:150) — self
6. [`Person.merged_into_id`](backend/app/models/identity/identity.py:76) — self
7. [`ServiceProfile.parent_id`](backend/app/models/marketplace/service.py:31) — self
---
## 4. Központi Entitás: Asset (Digital Twin)
Az [`Asset`](backend/app/models/vehicle/asset.py:69) a rendszer központi entitása, **84 oszloppal** és **12+ kapcsolattal**.
### 4.1 Asset mezőstruktúra
```yaml
# === AZONOSÍTÓ ÉS KATALÓGUS ===
id: UUID (PK) | catalog_id: FK vezerel.vehicle_catalog.id | name: String
# === JÁRMŰ SPECIFIKUS (DENORMALIZÁLT) ===
vin: String(17) | license_plate: String(20) | make: String(100)
model: String(255) | year: Integer | variant: String(100)
fuel_type: String(50) | engine_size: Integer | color: String(50)
seats: Integer | body_type: String(50) | transmission_type: String(30)
drivetrain: String(30) | mileage_unit: String(10)
# === MŰSZAKI ADATOK ===
power_hp: Integer | power_kw: Integer | torque_nm: Integer
co2_emission: Integer | euro_class: String(10) | emission_class: String(10)
# === FELSZERELTSÉG ===
audio_system_type: String(100)
individual_equipment: JSONB # ← CRITICAL: is_primary itt tárolva!
# === REGISZTRÁCIÓS DOKUMENTUMOK (RÉSZLEGES) ===
registration_certificate_number: String(50)
registration_certificate_validity: DateTime
vehicle_registration_document_number: String(50)
# === SZERVEZET ÉS LOKÁCIÓ ===
current_organization_id: FK | branch_id: FK | relocation_performed: Boolean
# === TULAJDONOS ÉS ÜZEMELTETŐ ===
owner_person_id: FK | owner_org_id: FK
operator_person_id: FK | operator_org_id: FK
# === ÁLLAPOT ===
status: String(30) | data_status: String(20) | current_mileage: Integer
condition_score: Integer(0-100)
# === IDŐBÉLYEGEK ===
created_at | updated_at | first_registration_date | purchase_date
warranty_expiry | last_technical_inspection | next_inspection_date
```
### 4.2 Asset kapcsolati kép
```
Asset
├──1:1──► AssetFinancials (pénzügy)
├──1:N──► AssetCost (TCO költségek)
├──1:N──► AssetEvent (Digitális Szervizkönyv)
├──1:N──► VehicleLogbook (NAV menetlevél)
├──1:N──► AssetInspection (napi ellenőrzés)
├──1:N──► AssetReview (értékelések)
├──1:1──► AssetTelemetry
├──1:N──► OdometerReading (óraállás)
├──1:N──► VehicleOwnership (tulajdonosváltozás)
├──1:N──► AssetAssignment (→Organization)
├──1:N──► ServiceRequest
├──1:N──► VehicleTransferRequest
├──N:1──► AssetCatalog
├──N:1──► Organization (current)
├──N:1──► Branch
├──N:1──► Person (owner/operator)
└──N:1──► Organization (owner/operator)
```
---
## 5. JSONB Mezők Teljes Feltárása
Összesen **56 JSONB mező** található. Alább a kritikusak.
### 5.1 `Asset.individual_equipment` — KRITIKUS
**Deklaráció:** [`asset.py:114`](backend/app/models/vehicle/asset.py:114)
```python
individual_equipment: Mapped[dict] = mapped_column(JSONB, server_default=text("'{}'::jsonb"))
```
**Jelenlegi tartalom:**
```json
{
"is_primary": true|false,
// + extra felszerelések
}
```
**Probléma:** Az `is_primary` logikai mező JSONB-ben van elrejtve, lekérdezhetetlen index nélkül. ORM szinten computed property-ként van kezelve (lásd [`asset.py:183-198`](backend/app/models/vehicle/asset.py:183)).
### 5.2 Többi Kritikus JSONB Mező
| Modell | Mező | Cél |
|--------|------|-----|
| `AssetCatalog` | `factory_data` | Gyári adatok |
| `AssetFinancials` | `accounting_details` | Számviteli részletek |
| `AssetCost` | `data` | Költség metaadatok |
| `VehicleModelDefinition` | `marketing_name_aliases` | Alternatív nevek |
| `VehicleModelDefinition` | `raw_api_data` | API nyers válasz |
| `VehicleModelDefinition` | `research_metadata` | Kutatási adatok |
| `VehicleModelDefinition` | `specifications` | Specifikációk |
| `Person` | `identity_docs` | Személyi docs |
| `Person` | `ice_contact` | Vészhelyzeti kapcsolat |
| `User` | `visual_settings` | UI beállítások |
| `User` | `custom_permissions` | Egyedi jogok |
| `User` | `alternative_emails` | Alternatív emailek |
| `User` | `email_history` | Email változások |
| `Organization` | `visual_settings` | Cég UI beállítások |
| `Organization` | `settings` | Cég beállítások |
| `Organization` | `aliases` | Alias nevek |
| `Organization` | `tags` | Címkék |
| `Organization` | `notification_settings` | Értesítési beállítások |
| `Organization` | `external_integration_config` | Külső integráció |
| `ServiceProfile` | `vibe_analysis` | AI hangulatelemzés |
| `ServiceProfile` | `social_links` | Közösségi linkek |
| `ServiceProfile` | `specialization_tags` | Specializációk |
| `ServiceProfile` | `verification_log` | Ellenőrzési napló |
| `ServiceProfile` | `opening_hours` | Nyitvatartás |
| `ExpertiseTag` | `name_translations` | Többnyelvű név |
| `ExpertiseTag` | `search_keywords` | Kulcsszavak |
| `OrgRole` | `permissions` | Capability flag-ek |
| `Branch` | `opening_hours` | Nyitvatartás |
| `Rating` | `images` | Képek array |
| `Campaign` | `targeting` | Célzás |
| `AssetInspection` | `checklist_results` | Ellenőrző lista |
| `PendingAction` | `payload` | Műveleti adatok |
| `SubscriptionTier` | `rules` | Csomag szabályok |
| `OrganizationSubscription` | `extra_allowances` | **Booster architektúra** |
| `SystemParameter` | `value` | Paraméter érték |
| `InternalNotification` | `data` | Értesítési adatok |
| `SystemServiceStaging` | `raw_data` | Nyers staging adat |
| `FinancialLedger` | `details` | Tranzakció részletek |
---
## 6. Redundancia Analízis
### 6.1 MAGAS KOCKÁZATÚ
#### 1. Dupla Költség Rendszer
| Tábla | Referencia | Cél |
|-------|-----------|-----|
| `VehicleCost` | vehicle_id → VMD.id | ❌ Anomália |
| `AssetCost` | asset_id → Asset.id | ✅ Helyes |
| `VehicleExpenses` | vehicle_id → Asset.id | ✅ Helyes |
**Kockázat:** `VehicleCost` a modell-definícióhoz köt, nem a konkrét járműhöz.
#### 2. Háromszoros kilométeróra tárolás
- `Asset.current_mileage` — denormalizált utolsó érték
- `OdometerReading.reading` — időbeli adatsor
- `AssetTelemetry.current_mileage` — harmadik helyen!
#### 3. Asset mezők duplikációja
`make`, `model`, `year`, `variant`, `fuel_type` szerepel:
- `Asset`-ben (denormalizált)
- `VehicleModelDefinition`-ben (eredeti)
- `AssetCatalog`-ban (katalógus)
#### 4. Cím adatok duplikációja
`Branch` denormalizált címmezőket tartalmaz `Address` mellett.
### 6.2 KÖZEPES KOCKÁZATÚ
#### 5. Kettős tulajdonos mezők
- `Organization.owner_id` → users (technikai, törölhető)
- `Organization.legal_owner_id` → persons (jogi, halhatatlan)
Szándékos (Dual Entity), de potenciális konfúzió.
#### 6. Subscription mezők duplikációja
`Organization` denormalizáltan tárolja a `subscription_plan`, `base_asset_limit`, `purchased_extra_slots` mezőket, melyek az `OrganizationSubscription`-ban is szerepelnek.
### 6.3 ALACSONY KOCKÁZAT (szándékos)
- `owner_person_id` + `owner_org_id` — B2B/B2C miatt
- Triple Wallet mezők — szándékos
---
## 7. Hiányosság Elemzés
### 7.1 Jelenlegi Állapot
| Domain | Státusz | Megjegyzés |
|--------|---------|------------|
| **Regisztrációs doksik** | ⚠️ Részleges | 3 mező az Asset-en |
| **Műszaki vizsga** | ⚠️ Részleges | 2 mező (last/next) |
| **Első forgalomba helyezés** | ✅ Van | `first_registration_date` |
| **KGFB biztosítás** | ❌ HIÁNYZIK | Nincs adatmodell |
| **Casco biztosítás** | ❌ HIÁNYZIK | Nincs adatmodell |
| **Súlyadó** | ❌ HIÁNYZIK | Nincs adatmodell |
| **Cégautó adó** | ❌ HIÁNYZIK | Nincs adatmodell |
| **Forgalmi engedély** | ⚠️ Részleges | Csak szám + érvényesség |
| **Törzskönyv** | ⚠️ Részleges | Csak szám |
| **Import megkülönböztetés** | ❌ HIÁNYZIK | Nincs |
| **Expected/Recurring költségek** | ❌ HIÁNYZIK | Nincs |
### 7.2 Specifikus Hiányzó Mezők
#### Biztosítás (teljesen hiányzik)
```
insurance_type: KGFB/CASCO/BOTH
insurer_name, policy_number, contract_date
start_date, expiry_date
premium_amount, premium_currency
bonus_malus_level (0-20)
deductible_amount, is_active, status, notes
```
#### Adók (teljesen hiányzik)
```
tax_type: WEIGHT_TAX/COMPANY_CAR_TAX/PERFORMANCE_TAX
tax_year, tax_amount, tax_currency
payment_date, payment_status
tax_authority, tax_period_start/end, notes
```
#### Admin (részleges)
```
MEGLÉVŐ: registration_certificate_number, registration_certificate_validity, vehicle_registration_document_number
HIÁNYZÓ: registration_type (FIRST_HU/FIRST_FOREIGN/IMPORT_USED)
first_hungarian_registration_date, first_foreign_registration_date
title_deed_number, title_deed_issue_date
engine_number, number_of_previous_owners
original_plate_number, import_country
```
#### Ütemezett költségek (teljesen hiányzik)
```
expected_cost_type: INSURANCE/TAX/SERVICE/INSPECTION/TIRE_CHANGE/OTHER
expected_amount, currency, frequency (MONTHLY/QUARTERLY/YEARLY/ONE_TIME)
next_due_date, last_paid_date, is_recurring
category_id FK, description, reminder_days_before
```
---
## 8. Architekturális Javaslat
### 8.1 Ajánlott: Dedikált táblák (előnyben részesített)
```sql
-- Biztosítási kötvények
vehicle.vehicle_insurance_policies
id: UUID PK
asset_id: UUID FK -> vehicle.assets.id
insurance_type: ENUM(KGFB, CASCO, BOTH)
insurer_name, policy_number, contract_date, start_date, expiry_date
premium_amount, premium_currency, bonus_malus_level
deductible_amount, status, data: JSONB
-- Adókötelezettségek
vehicle.vehicle_tax_obligations
id: UUID PK
asset_id: UUID FK -> vehicle.assets.id
tax_type: ENUM(WEIGHT_TAX, COMPANY_CAR_TAX, PERFORMANCE_TAX)
tax_year, tax_amount, tax_currency
payment_date, payment_status, tax_period_start/end
data: JSONB
-- Ütemezett költségek
vehicle.vehicle_expected_costs
id: UUID PK
asset_id: UUID FK -> vehicle.assets.id
cost_type: ENUM(INSURANCE, TAX, SERVICE, INSPECTION, TIRE_CHANGE, OTHER)
expected_amount, currency, frequency
next_due_date, last_paid_date, is_recurring
category_id FK -> cost_categories.id
description, reminder_days_before, status, data: JSONB
-- Regisztrációs dokumentumok (kibővítve)
vehicle.vehicle_registration_documents
id: UUID PK
asset_id: UUID FK -> vehicle.assets.id UNIQUE
registration_type: ENUM(FIRST_HU, FIRST_FOREIGN, IMPORT_USED)
first_hungarian_registration_date, first_foreign_registration_date
title_deed_number, title_deed_issue_date
engine_number, number_of_previous_owners
original_plate_number, import_country, data: JSONB
```
### 8.2 Alternatíva: Meglévő modellek bővítése (gyorsabb)
#### `Asset` bővítése:
```python
first_hungarian_registration_date: Mapped[Optional[date]]
first_foreign_registration_date: Mapped[Optional[date]]
import_country: Mapped[Optional[str]]
title_deed_number: Mapped[Optional[str]]
engine_number: Mapped[Optional[str]]
number_of_previous_owners: Mapped[Optional[int]]
```
#### `AssetFinancials` bővítése:
```python
insurance_kgfb_policy_number: Mapped[Optional[str]]
insurance_kgfb_expiry: Mapped[Optional[date]]
insurance_kgfb_premium: Mapped[Optional[float]]
insurance_casco_policy_number: Mapped[Optional[str]]
insurance_casco_expiry: Mapped[Optional[date]]
insurance_casco_premium: Mapped[Optional[float]]
bonus_malus_level: Mapped[Optional[int]]
```
### 8.3 Ajánlott Megvalósítási Sorrend
1. **P1**`VehicleInsurancePolicy` új modell (dedikált tábla)
2. **P1**`VehicleTaxObligation` új modell (dedikált tábla)
3. **P2**`VehicleExpectedCost` új modell (ütemezett költségek)
4. **P2**`VehicleRegistrationDocument` új modell (admin adatok elkülönítése)
5. **P3**`individual_equipment.is_primary` kivezetése dedikált boolean oszlopba
---
## 9. Függelék: Teljes FK-relationship Mátrix
### 9.1 Minden ForeignKey
```
=== VEHICLE SÉMA ===
AssetCatalog.master_definition_id -> vehicle.vehicle_model_definitions.id
Asset.catalog_id -> vehicle.vehicle_catalog.id
Asset.current_organization_id -> fleet.organizations.id
Asset.branch_id -> fleet.branches.id
Asset.owner_person_id -> identity.persons.id
Asset.owner_org_id -> fleet.organizations.id
Asset.operator_person_id -> identity.persons.id
Asset.operator_org_id -> fleet.organizations.id
AssetFinancials.asset_id -> vehicle.assets.id (UNIQUE)
AssetCost.asset_id -> vehicle.assets.id
AssetCost.organization_id -> fleet.organizations.id
AssetCost.category_id -> vehicle.cost_categories.id
AssetCost.document_id -> system.documents.id
AssetCost.linked_asset_event_id -> vehicle.asset_events.id
VehicleLogbook.asset_id -> vehicle.assets.id
VehicleLogbook.driver_id -> identity.users.id
AssetInspection.asset_id -> vehicle.assets.id
AssetInspection.inspector_id -> identity.users.id
AssetReview.asset_id -> vehicle.assets.id
AssetReview.user_id -> identity.users.id
VehicleOwnership.asset_id -> vehicle.assets.id
VehicleOwnership.user_id -> identity.users.id
AssetTelemetry.asset_id -> vehicle.assets.id (UNIQUE)
OdometerReading.asset_id -> vehicle.assets.id
OdometerReading.cost_id -> vehicle.asset_costs.id
AssetAssignment.asset_id -> vehicle.assets.id
AssetAssignment.organization_id -> fleet.organizations.id
AssetEvent.asset_id -> vehicle.assets.id
AssetEvent.user_id -> identity.users.id
AssetEvent.organization_id -> fleet.organizations.id
AssetEvent.cost_id -> vehicle.asset_costs.id
AssetEvent.linked_expense_id -> vehicle.asset_costs.id
VehicleExpenses.vehicle_id -> vehicle.assets.id
VehicleTransferRequest.asset_id -> vehicle.assets.id
VehicleTransferRequest.requester_id -> identity.users.id
VehicleTransferRequest.current_owner_id -> identity.persons.id
VehicleTransferRequest.proof_document_id -> system.documents.id
VehicleCost.vehicle_id -> vehicle.vehicle_model_definitions.id
VehicleCost.organization_id -> fleet.organizations.id
VehicleCost.category_id -> vehicle.cost_categories.id
VehicleUserRating.vehicle_id -> vehicle.vehicle_model_definitions.id
VehicleUserRating.user_id -> identity.users.id
CostCategory.parent_id -> vehicle.cost_categories.id (self-ref)
FeatureDefinition.vehicle_type_id -> vehicle.vehicle_types.id
VehicleModelDefinition.vehicle_type_id -> vehicle.vehicle_types.id
ModelFeatureMap.model_definition_id -> vehicle.vehicle_model_definitions.id
ModelFeatureMap.feature_id -> vehicle.feature_definitions.id
MotorcycleSpecs.(FK) -> vehicle.auto_data_crawler_queue.id
ExternalReferenceLibrary.matched_vmd_id -> vehicle.vehicle_model_definitions.id
=== IDENTITY SÉMA ===
Person.address_id -> system.addresses.id
Person.merged_into_id -> identity.persons.id (self-ref)
Person.user_id (active_user) -> identity.users.id
User.person_id -> identity.persons.id
User.referred_by_id -> identity.users.id (self-ref)
User.current_sales_agent_id -> identity.users.id (self-ref)
Wallet.user_id -> identity.users.id (UNIQUE)
Wallet.organization_id -> fleet.organizations.id
VerificationToken.user_id -> identity.users.id
SocialAccount.user_id -> identity.users.id
ActiveVoucher.wallet_id -> identity.wallets.id
UserTrustProfile.user_id -> identity.users.id (PK)
UserDeviceLink.user_id -> identity.users.id
UserDeviceLink.device_hash -> identity.devices.fingerprint_hash
=== FLEET SÉMA ===
Organization.legal_owner_id -> identity.persons.id
Organization.address_id -> system.addresses.id
Organization.subscription_tier_id -> system.subscription_tiers.id
Organization.owner_id -> identity.users.id
OrganizationFinancials.organization_id -> fleet.organizations.id
OrganizationMember.organization_id -> fleet.organizations.id
OrganizationMember.user_id -> identity.users.id
OrganizationMember.person_id -> identity.persons.id
OrganizationSalesAssignment.organization_id -> fleet.organizations.id
OrganizationSalesAssignment.agent_user_id -> identity.users.id
Branch.organization_id -> fleet.organizations.id
Branch.address_id -> system.addresses.id
Rating.author_id -> identity.users.id
Rating.target_organization_id -> fleet.organizations.id
Rating.target_user_id -> identity.users.id
Rating.target_branch_id -> fleet.branches.id
=== SYSTEM SÉMA ===
Address.postal_code_id -> system.geo_postal_codes.id
GeoStreet.postal_code_id -> system.geo_postal_codes.id
InternalNotification.user_id -> identity.users.id
Document.uploaded_by -> identity.users.id
LegalAcceptance.user_id -> identity.users.id
LegalAcceptance.document_id -> system.legal_documents.id
PendingAction.requester_id -> identity.users.id
PendingAction.approver_id -> identity.users.id
=== MARKETPLACE SÉMA ===
ServiceProfile.organization_id -> fleet.organizations.id (UNIQUE)
ServiceProfile.parent_id -> marketplace.service_profiles.id (self-ref)
ExpertiseTag.parent_id -> marketplace.expertise_tags.id (self-ref)
ExpertiseTag.suggested_by_id -> identity.persons.id
ServiceExpertise.service_id -> marketplace.service_profiles.id
ServiceExpertise.expertise_id -> marketplace.expertise_tags.id
ServiceRequest.user_id -> identity.users.id
ServiceRequest.asset_id -> vehicle.assets.id
ServiceRequest.branch_id -> fleet.branches.id
ServiceProvider.added_by_user_id -> identity.users.id
Vote.user_id -> identity.users.id
Vote.provider_id -> marketplace.service_providers.id
UserScore.user_id -> identity.users.id
UserScore.competition_id -> gamification.competitions.id
ServiceReview.service_id -> marketplace.service_profiles.id
ServiceReview.user_id -> identity.users.id
ServiceReview.transaction_id -> audit.financial_ledger.transaction_id
ServiceSpecialty.parent_id -> marketplace.service_specialties.id (self-ref)
OrganizationSubscription.org_id -> fleet.organizations.id
OrganizationSubscription.tier_id -> system.subscription_tiers.id
UserSubscription.user_id -> identity.users.id
UserSubscription.tier_id -> system.subscription_tiers.id
CreditTransaction.org_id -> fleet.organizations.id
=== FINANCE SÉMA ===
PaymentIntent.payer_id -> identity.users.id
PaymentIntent.beneficiary_id -> identity.users.id
WithdrawalRequest.user_id -> identity.users.id
WithdrawalRequest.approved_by_id -> identity.users.id
=== AUDIT SÉMA ===
SecurityAuditLog.actor_id -> identity.users.id
SecurityAuditLog.target_id -> identity.users.id
SecurityAuditLog.confirmed_by_id -> identity.users.id
OperationalLog.user_id -> identity.users.id
FinancialLedger.user_id -> identity.users.id
FinancialLedger.person_id -> identity.persons.id
FinancialLedger.related_agent_id -> identity.users.id
FinancialLedger.issuer_id -> finance.issuers.id
=== GAMIFICATION SÉMA ===
PointsLedger.user_id -> identity.users.id
UserStats.user_id -> identity.users.id (PK)
UserBadge.user_id -> identity.users.id
UserBadge.badge_id -> gamification.badges.id
UserContribution.user_id -> identity.users.id
UserContribution.season_id -> gamification.seasons.id
UserContribution.reviewed_by -> identity.users.id
SeasonalCompetitions.season_id -> gamification.seasons.id
=== MARKETING SÉMA ===
CampaignCreative.campaign_id -> marketing.campaigns.id
CampaignCreative.creative_id -> marketing.creatives.id
CampaignPlacement.campaign_id -> marketing.campaigns.id
CampaignPlacement.placement_id -> marketing.placements.id
AdImpression.campaign_id -> marketing.campaigns.id
AdImpression.creative_id -> marketing.creatives.id
AdImpression.placement_id -> marketing.placements.id
AdClick.impression_id -> marketing.ad_impressions.id
AdClick.campaign_id -> marketing.campaigns.id
AdClick.creative_id -> marketing.creatives.id
```
### 9.2 Leggyakrabban Hivatkozott Táblák
| Tábla | Hivatkozások | Honnan |
|-------|-------------|--------|
| `identity.users` | **30+** | Minden domainből |
| `vehicle.assets` | **12** | cost, event, logbook, inspection, stb. |
| `fleet.organizations` | **10** | asset, cost, member, branch, stb. |
| `identity.persons` | **6** | user, organization, financial_ledger |
| `vehicle.vehicle_model_definitions` | **4** | catalog, cost, rating |
| `vehicle.cost_categories` | **3** | asset_cost, vehicle_cost |
---
## Összefoglalás
### Főbb Megállapítások
1. **Központi entitás:** [`Asset`](backend/app/models/vehicle/asset.py:69) — Digital Twin, 12+ kapcsolattal
2. **Legkritikusabb redundancia:** `VehicleCost` a VMD-hez köt (NEM az Asset-hez)
3. **Dual Entity:** `Person` (DNS) + `User` (login) sikeresen implementálva
4. **7 önhivatkozó hierarchia** — CostCategory, ExpertiseTag (4 szint), ServiceSpecialty, User (referral, sales), Person (merge), ServiceProfile
5. **56 JSONB mező** — Rugalmasság, de kereshetőséget ront
6. **`individual_equipment.is_primary`** — Logikai mező JSONB-ben elrejtve
### Kritikus Hiányosságok
1.**Biztosítási adatok teljes hiánya** (KGFB, Casco)
2.**Adó adatok teljes hiánya** (súlyadó, cégautó adó)
3.**Első forgalomba helyezés típusának hiánya**
4.**Ütemezett/expected költségek hiánya**
5. ⚠️ **Regisztrációs dokumentumok részlegesek** (3 mező)
6. ⚠️ **Három cost modell párhuzamosan** eltérő referenciákkal
### Ajánlott Következő Lépések
1. **P1:** Dedikált `VehicleInsurancePolicy` és `VehicleTaxObligation` modellek
2. **P1:** `Asset` bővítése hiányzó admin mezőkkel
3. **P2:** `VehicleExpectedCost` modell
4. **P2:** `VehicleRegistrationDocument` modell
5. **P3:** `individual_equipment.is_primary` dedikált boolean oszlopba
6. **P3:** Cost modellek konszolidációja

View File

@@ -0,0 +1,278 @@
# P0 Finance API Gap Analysis Report
**Date:** 2026-06-21
**Author:** Fast Coder (Core Developer)
**Scope:** `fleet_finance` and `finance` schema models vs FastAPI endpoints
**Status:** ✅ Part 1 API Tests Passed (6/6)
---
## 1. Executive Summary
This report audits all SQLAlchemy models in the `fleet_finance` and `finance` schemas against their corresponding FastAPI REST endpoints. The goal is to identify which models have full CRUD coverage, which have partial coverage, and which are completely missing API access.
**Key Finding:** Out of **13 finance-related models** across both schemas, **7 have dedicated API endpoints**, **2 have indirect coverage** (via other endpoints), and **4 have NO API endpoints at all**.
---
## 2. Part 1: API Test Results ✅
All 6 Phase 1 finance API endpoints were tested and passed:
| # | Endpoint | Method | Status | Response |
|---|----------|--------|--------|----------|
| 1 | `/api/v1/assets/vehicles/{asset_id}/financials` | GET | ✅ 200 | Full financial data returned |
| 2 | `/api/v1/assets/vehicles/{asset_id}/financials` | PATCH | ✅ 200 | Partial update applied (monthly_installment: 50000→75000) |
| 3 | `/api/v1/assets/vehicles/{asset_id}/insurance` | GET | ✅ 200 | Empty list (no policies yet) |
| 4 | `/api/v1/assets/vehicles/{asset_id}/insurance` | POST | ✅ 201 | KGFB policy created |
| 5 | `/api/v1/assets/vehicles/{asset_id}/tax` | GET | ✅ 200 | Empty list (no obligations yet) |
| 6 | `/api/v1/assets/vehicles/{asset_id}/tax` | POST | ✅ 201 | WEIGHT_TAX obligation created |
**Test Asset Used:** `7ebedcdf-9316-42a4-a6e0-7ce656668cf9` (TEST-API-999, org 1)
**Auth:** `admin@profibot.hu` (member of org 1 with ADMIN role)
---
## 3. Schema: `fleet_finance` — Model vs API Matrix
| # | Model | Schema | Table | GET | POST | PATCH/PUT | DELETE | CRUD Status |
|---|-------|--------|-------|-----|------|-----------|--------|-------------|
| 1 | **CostCategory** | `fleet_finance` | `cost_categories` | ❌ | ❌ | ❌ | ❌ | **❌ MISSING** |
| 2 | **AssetCost** | `fleet_finance` | `asset_costs` | ✅ | ✅ | ❌ | ❌ | ⚠️ Partial (R+C) |
| 3 | **AssetFinancials** | `fleet_finance` | `asset_financials` | ✅ | ❌ | ✅ | ❌ | ⚠️ Partial (R+U) |
| 4 | **InsuranceProvider** | `fleet_finance` | `insurance_providers` | ❌ | ❌ | ❌ | ❌ | **❌ MISSING** |
| 5 | **VehicleInsurancePolicy** | `fleet_finance` | `vehicle_insurance_policies` | ✅ | ✅ | ❌ | ❌ | ⚠️ Partial (R+C) |
| 6 | **VehicleTaxObligation** | `fleet_finance` | `vehicle_tax_obligations` | ✅ | ✅ | ❌ | ❌ | ⚠️ Partial (R+C) |
### 3.1 Detailed Findings — `fleet_finance`
#### ✅ CostCategory — ❌ NO API Endpoints
- **File:** [`backend/app/models/fleet_finance/models.py`](../backend/app/models/fleet_finance/models.py:35)
- **Purpose:** Hierarchical cost categories (FUEL, MAINTENANCE, REPAIR, etc.)
- **Used indirectly** by `AssetCost` via `category_id` FK
- **No dedicated router** exists for CRUD operations on categories
- **Impact:** Categories must be seeded via DB scripts (see `seed_cost_category_tiers.py`, `seed_cost_category_visibility.py`)
- **Recommendation:** Create a `/api/v1/cost-categories` CRUD endpoint for admin management
#### ✅ AssetCost — ⚠️ Partial (Read + Create only)
- **File:** [`backend/app/models/fleet_finance/models.py`](../backend/app/models/fleet_finance/models.py:96)
- **Endpoints:**
- `GET /api/v1/assets/{asset_id}/costs` — list costs for an asset ✅
- `POST /api/v1/expenses/` — create expense (with Smart Linking to AssetEvent) ✅
- `GET /api/v1/expenses/{asset_id}` — list expenses with category enrichment ✅
- **Missing:** PATCH (update) and DELETE endpoints
- **Recommendation:** Add PATCH/DELETE for expense management
#### ✅ AssetFinancials — ⚠️ Partial (Read + Update only)
- **File:** [`backend/app/models/fleet_finance/models.py`](../backend/app/models/fleet_finance/models.py:141)
- **Endpoints:**
- `GET /api/v1/assets/vehicles/{asset_id}/financials`
- `PATCH /api/v1/assets/vehicles/{asset_id}/financials`
- **Missing:** POST (create) endpoint — PATCH requires pre-existing record
- **Workaround:** Must be created via direct DB insert or admin script
- **Recommendation:** Add a POST endpoint to create AssetFinancials records
#### ✅ InsuranceProvider — ❌ NO API Endpoints
- **File:** [`backend/app/models/fleet_finance/models.py`](../backend/app/models/fleet_finance/models.py:180)
- **Purpose:** Insurance company catalog (name, claim_phone, services_offered)
- **No API endpoints** exist for listing, creating, or managing providers
- **Impact:** Cannot create insurance policies via API without pre-seeding providers
- **Recommendation:** Create a `/api/v1/insurance-providers` CRUD endpoint
#### ✅ VehicleInsurancePolicy — ⚠️ Partial (Read + Create only)
- **File:** [`backend/app/models/fleet_finance/models.py`](../backend/app/models/fleet_finance/models.py:208)
- **Endpoints:**
- `GET /api/v1/assets/vehicles/{asset_id}/insurance`
- `POST /api/v1/assets/vehicles/{asset_id}/insurance`
- **Missing:** PATCH (update policy details), DELETE (cancel policy)
- **Recommendation:** Add PATCH for policy updates (e.g., renewal, premium change)
#### ✅ VehicleTaxObligation — ⚠️ Partial (Read + Create only)
- **File:** [`backend/app/models/fleet_finance/models.py`](../backend/app/models/fleet_finance/models.py:252)
- **Endpoints:**
- `GET /api/v1/assets/vehicles/{asset_id}/tax`
- `POST /api/v1/assets/vehicles/{asset_id}/tax`
- **Missing:** PATCH (update payment status), DELETE
- **Recommendation:** Add PATCH for payment status updates
---
## 4. Schema: `finance` — Model vs API Matrix
| # | Model | Schema | Table | GET | POST | PATCH/PUT | DELETE | CRUD Status |
|---|-------|--------|-------|-----|------|-----------|--------|-------------|
| 1 | **Issuer** | `finance` | `issuers` | ✅ | ❌ | ✅ | ❌ | ⚠️ Partial (R+U) |
| 2 | **PaymentIntent** | `finance` | `payment_intents` | ✅ | ✅ | ❌ | ❌ | ⚠️ Partial (R+C) |
| 3 | **WithdrawalRequest** | `finance` | `withdrawal_requests` | ❌ | ❌ | ❌ | ❌ | **❌ MISSING** |
| 4 | **OrganizationSubscription** | `finance` | `org_subscriptions` | ❌ | ❌ | ❌ | ❌ | **❌ MISSING** |
| 5 | **UserSubscription** | `finance` | `user_subscriptions` | ❌ | ❌ | ❌ | ❌ | **❌ MISSING** |
| 6 | **CreditTransaction** | `finance` | `credit_transactions` | ❌ | ❌ | ❌ | ❌ | **❌ MISSING** |
| 7 | **FinancialLedger** | `audit` | `financial_ledger` | ✅ | ❌ | ❌ | ❌ | ⚠️ Partial (R) |
### 4.1 Detailed Findings — `finance`
#### ✅ Issuer — ⚠️ Partial (Read + Update only, Admin-only)
- **File:** [`backend/app/models/marketplace/finance.py`](../backend/app/models/marketplace/finance.py:27)
- **Endpoints:**
- `GET /api/v1/finance-admin/` — list issuers (admin only) ✅
- `PATCH /api/v1/finance-admin/{issuer_id}` — update issuer (admin only) ✅
- **Missing:** POST (create issuer), public listing
- **Note:** Admin-only access via `check_finance_admin_access` dependency
#### ✅ PaymentIntent — ⚠️ Partial (Read + Create only)
- **File:** [`backend/app/models/marketplace/payment.py`](../backend/app/models/marketplace/payment.py:30)
- **Endpoints:**
- `POST /api/v1/billing/payment-intent/create`
- `POST /api/v1/billing/payment-intent/{id}/stripe-checkout`
- `POST /api/v1/billing/payment-intent/{id}/process-internal`
- `GET /api/v1/billing/payment-intent/{id}/status`
- `POST /api/v1/billing/stripe-webhook`
- **Note:** Well-covered for payment flow; no direct PATCH/DELETE needed
#### ❌ WithdrawalRequest — ❌ NO API Endpoints
- **File:** [`backend/app/models/marketplace/payment.py`](../backend/app/models/marketplace/payment.py:147)
- **Purpose:** Withdrawal requests from Earned wallet (user_id, amount, payout_method, status, approval)
- **Has domain methods:** `approve()`, `reject()`, `cancel()`, `is_expired()`
- **No API endpoints** exist for creating or managing withdrawal requests
- **Impact:** Users cannot request payouts from their Earned wallet
- **Recommendation:** Create `/api/v1/billing/withdrawals` CRUD endpoint
#### ❌ OrganizationSubscription — ❌ NO API Endpoints
- **File:** [`backend/app/models/core_logic.py`](../backend/app/models/core_logic.py:25)
- **Purpose:** Organization subscription plans with extra_allowances JSONB
- **No dedicated API endpoints** — used indirectly via billing upgrade flow
- **Note:** The `POST /api/v1/billing/upgrade` endpoint handles subscription changes but doesn't expose the model directly
#### ❌ UserSubscription — ❌ NO API Endpoints
- **File:** [`backend/app/models/core_logic.py`](../backend/app/models/core_logic.py:49)
- **Purpose:** User-level subscriptions
- **No API endpoints** at all
#### ❌ CreditTransaction — ❌ NO API Endpoints
- **File:** [`backend/app/models/core_logic.py`](../backend/app/models/core_logic.py:72)
- **Purpose:** Credit transaction logs (org_id, amount, description)
- **No API endpoints** at all
- **Note:** Credits are managed internally by the billing engine
#### ✅ FinancialLedger — ⚠️ Partial (Read only)
- **File:** [`backend/app/models/system/audit.py`](../backend/app/models/system/audit.py:78)
- **Endpoints:**
- `GET /api/v1/billing/wallet/transactions` — list transactions with pagination ✅
- **Missing:** No write endpoints (ledger is append-only by design)
- **Note:** Read-only access is correct for an audit log
---
## 5. Gap Summary
### 5.1 Critical Gaps (No API Access)
| Model | Schema | Impact | Priority |
|-------|--------|--------|----------|
| **InsuranceProvider** | `fleet_finance` | Cannot create insurance policies without pre-seeded providers | **HIGH** |
| **CostCategory** | `fleet_finance` | Categories must be managed via DB scripts | **MEDIUM** |
| **WithdrawalRequest** | `finance` | Users cannot request Earned wallet payouts | **HIGH** |
| **OrganizationSubscription** | `finance` | No direct subscription management API | **MEDIUM** |
| **UserSubscription** | `finance` | No direct user subscription API | **LOW** |
| **CreditTransaction** | `finance` | Credits managed internally; read-only may suffice | **LOW** |
### 5.2 Partial Coverage Gaps
| Model | Missing Operations | Impact | Priority |
|-------|-------------------|--------|----------|
| **AssetFinancials** | POST (create) | Must pre-seed via DB before PATCH works | **MEDIUM** |
| **AssetCost** | PATCH, DELETE | Cannot update or remove expenses | **MEDIUM** |
| **VehicleInsurancePolicy** | PATCH, DELETE | Cannot update or cancel policies | **LOW** |
| **VehicleTaxObligation** | PATCH, DELETE | Cannot update payment status | **LOW** |
| **Issuer** | POST (create) | Must be created via DB or admin panel | **LOW** |
---
## 6. Recommendations
### Phase 2 — High Priority
1. **Create `/api/v1/insurance-providers` CRUD endpoint** — enables dynamic provider management
2. **Create `/api/v1/billing/withdrawals` CRUD endpoint** — enables Earned wallet payout requests
3. **Add POST `/api/v1/assets/vehicles/{asset_id}/financials`** — enables creating financial records via API
### Phase 3 — Medium Priority
4. **Create `/api/v1/cost-categories` CRUD endpoint** — enables category management via API
5. **Add PATCH/DELETE to `/api/v1/expenses/{expense_id}`** — enables expense editing
6. **Add subscription management endpoints** — enables direct plan changes
### Phase 4 — Low Priority
7. **Add PATCH to insurance/tax endpoints** — enables status updates
8. **Add Issuer POST endpoint** — enables provider creation via admin panel
---
## 7. Endpoint Inventory (All Finance-Related)
### Router: `assets.py` (prefix: `/api/v1/assets`)
| Method | Path | Model | Status |
|--------|------|-------|--------|
| GET | `/vehicles/{asset_id}/financials` | AssetFinancials | ✅ Tested |
| PATCH | `/vehicles/{asset_id}/financials` | AssetFinancials | ✅ Tested |
| GET | `/vehicles/{asset_id}/insurance` | VehicleInsurancePolicy | ✅ Tested |
| POST | `/vehicles/{asset_id}/insurance` | VehicleInsurancePolicy | ✅ Tested |
| GET | `/vehicles/{asset_id}/tax` | VehicleTaxObligation | ✅ Tested |
| POST | `/vehicles/{asset_id}/tax` | VehicleTaxObligation | ✅ Tested |
| GET | `/{asset_id}/financial-summary` | Asset (aggregated) | Not tested |
| GET | `/{asset_id}/costs` | AssetCost | Not tested |
### Router: `expenses.py` (prefix: `/api/v1/expenses`)
| Method | Path | Model | Status |
|--------|------|-------|--------|
| GET | `/{asset_id}` | AssetCost | Not tested |
| POST | `/` | AssetCost | Not tested |
### Router: `finance_admin.py` (prefix: `/api/v1/finance-admin`)
| Method | Path | Model | Status |
|--------|------|-------|--------|
| GET | `/` | Issuer | Not tested |
| PATCH | `/{issuer_id}` | Issuer | Not tested |
### Router: `billing.py` (prefix: `/api/v1/billing`)
| Method | Path | Model | Status |
|--------|------|-------|--------|
| POST | `/upgrade` | OrganizationSubscription | Not tested |
| POST | `/payment-intent/create` | PaymentIntent | Not tested |
| POST | `/payment-intent/{id}/stripe-checkout` | PaymentIntent | Not tested |
| POST | `/payment-intent/{id}/process-internal` | PaymentIntent | Not tested |
| POST | `/stripe-webhook` | PaymentIntent | Not tested |
| GET | `/payment-intent/{id}/status` | PaymentIntent | Not tested |
| GET | `/wallet/balance` | FinancialLedger (aggregated) | Not tested |
| GET | `/wallet/transactions` | FinancialLedger | Not tested |
---
## 8. Appendix: Schema Details
### `fleet_finance` Schema Tables
| Table | Primary Key | Key Columns |
|-------|-------------|-------------|
| `cost_categories` | `id` (int) | `parent_id`, `code`, `name`, `is_system`, `visibility`, `min_tier` |
| `asset_costs` | `id` (int) | `asset_id`, `organization_id`, `category_id`, `amount_net/gross`, `vat_rate`, `status`, `linked_asset_event_id` |
| `asset_financials` | `id` (int) | `asset_id`, `purchase_price_net/gross`, `financing_type`, `accounting_details` (JSONB), `monthly_installment`, `down_payment` |
| `insurance_providers` | `id` (int) | `name`, `claim_phone`, `claim_url`, `services_offered` (JSONB), `is_active` |
| `vehicle_insurance_policies` | `id` (uuid) | `asset_id`, `provider_id`, `insurance_type`, `policy_number`, `start/expiry_date`, `premium_amount` |
| `vehicle_tax_obligations` | `id` (uuid) | `asset_id`, `tax_type`, `tax_year`, `amount`, `due_date`, `payment_status` |
### `finance` Schema Tables
| Table | Primary Key | Key Columns |
|-------|-------------|-------------|
| `issuers` | `id` (int) | `name`, `tax_id`, `type` (KFT/EV/BT/ZRT/OTHER), `revenue_limit`, `current_revenue`, `api_config` (JSONB) |
| `payment_intents` | `id` (uuid) | `intent_token`, `payer_id`, `beneficiary_id`, `target_wallet_type`, `net/handling/gross` amounts, `status`, `stripe_*` fields |
| `withdrawal_requests` | `id` (uuid) | `user_id`, `amount`, `payout_method`, `status`, `approved_by`, `approved_at`, `rejection_reason` |
| `org_subscriptions` | `id` (int) | `organization_id`, `tier_id`, `start/end_date`, `status`, `extra_allowances` (JSONB) |
| `user_subscriptions` | `id` (int) | `user_id`, `tier_id`, `start/end_date`, `status` |
| `credit_transactions` | `id` (int) | `organization_id`, `amount`, `description`, `created_at` |
### `audit` Schema Tables
| Table | Primary Key | Key Columns |
|-------|-------------|-------------|
| `financial_ledger` | `id` (int) | `user_id`, `amount`, `entry_type` (DEBIT/CREDIT), `wallet_type`, `transaction_id`, `status`, `issuer_id`, `invoice_*` fields |
---
*Report generated by Fast Coder (Core Developer) — 2026-06-21*

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
# 🏗️ P0 Architect Report: Backend API & Validation Audit — Vehicle Management & Finance
**Dátum:** 2026-06-21
**Auditor:** Rendszer-Architect
**Státusz:** Read-Only Audit (Kódmódosítás nélkül)
**Scope:** Vehicle CRUD, Pydantic Schemas, Fleet Finance Endpoints
---
## Executive Summary
Két kritikus gapet azonosítottam a Backend API és a Database Modellek között:
### 🔴 Critical Gap #1 — 6 Hiányzó Internationalizációs Mező a Schemákból és Service Logikából
Az `Asset` modell (backend/app/models/vehicle/asset.py:147) tartalmaz 6 új mezőt (`registration_country`, `first_domestic_registration_date`, `import_country`, `title_document_number`, `engine_number`, `number_of_previous_owners`), amelyek:
- **Hiányoznak** az `AssetCreate` (backend/app/schemas/asset.py:122) és `AssetUpdate` (backend/app/schemas/asset.py:269) Pydantic sémákból
- **Hiányoznak** a `create_or_claim_vehicle()` service metódus `asset_fields` dict-jéből (backend/app/services/asset_service.py:172-224)
- **Hiányoznak** az update logikából (backend/app/services/asset_service.py)
### 🔴 Critical Gap #2 — ZERO API Endpoint a Fleet Finance Modellekhez
A `fleet_finance` séma 4 modellje (`AssetFinancials`, `VehicleInsurancePolicy`, `VehicleTaxObligation`, `InsuranceProvider`) rendelkezik adatbázis táblákkal, de:
- **0 Pydantic schema** létezik hozzájuk (a schemas/finance.py csak `Issuer` sémákat tartalmaz)
- **0 API endpoint** létezik hozzájuk (az endpoints/finance_admin.py csak Issuer managementet tartalmaz)
- `AssetFinancials` csak a `create_or_claim_vehicle()` által kerül inicializálásra (nullákkal és `financing_type="unknown"`), de SOHA nem frissíthető az API-n keresztül
---
## 1. Section: Vehicle Schemas and Endpoints Status
### 1.1 Valódi Végpont Lokáció
⚠️ **Critical finding:** A `endpoints/vehicles.py` fájl NEM a jármű CRUD-ot tartalmazza, hanem kizárólag szociális értékeléseket (`VehicleUserRating`).
A tényleges jármű CRUD a `endpoints/assets.py` fájlban található:
- `POST /api/v1/assets/vehicles``create_or_claim_vehicle()` (backend/app/api/v1/endpoints/assets.py:502)
- `PUT /api/v1/assets/vehicles/{asset_id}``update_vehicle()` (backend/app/api/v1/endpoints/assets.py:606)
- `GET /api/v1/assets/vehicles``get_user_vehicles()` (backend/app/api/v1/endpoints/assets.py:223)
- `GET /api/v1/assets/vehicles/{vehicle_id}``get_vehicle()` (backend/app/api/v1/endpoints/assets.py:439)
### 1.2 Hiányzó Mezők Mátrixa
| Mező | Asset Modell | AssetCreate Schema | AssetUpdate Schema | Service Logika |
|------|-------------|-------------------|-------------------|---------------|
| `registration_country` (ISO 3166-1 alpha-2) | ✅ (line 147) | ❌ | ❌ | ❌ |
| `first_domestic_registration_date` | ✅ (line 151) | ❌ | ❌ | ❌ |
| `import_country` (ISO 3166-1 alpha-2) | ✅ (line 155) | ❌ | ❌ | ❌ |
| `title_document_number` | ✅ (line 159) | ❌ | ❌ | ❌ |
| `engine_number` | ✅ (line 163) | ❌ | ❌ | ❌ |
| `number_of_previous_owners` | ✅ (line 167) | ❌ | ❌ | ❌ |
### 1.3 Meglévő Validátorok Állapota
Az `AssetCreate` séma jelenleg az alábbi validátorokat tartalmazza:
- `empty_str_to_none` — minden string mezőre (backend/app/schemas/asset.py:134)
- `normalize_brand` / `normalize_model` — brand/model normalizálás (backend/app/schemas/asset.py:143)
- `normalize_individual_equipment` — equipment lista normalizálás (backend/app/schemas/asset.py:159)
- `validate_vin_or_plate` — VIN vagy rendszám kötelezőség (backend/app/schemas/asset.py:183)
- `determine_status` — alapértelmezett DRAFT státusz (backend/app/schemas/asset.py:249)
**Hiányzó validátorok az új mezőkhöz:**
- `registration_country`: ISO 3166-1 alpha-2 formátum validátor (2 karakter, nagybetű)
- `import_country`: ISO 3166-1 alpha-2 formátum validátor
- `first_domestic_registration_date`: nem lehet jövőbeli dátum
- `number_of_previous_owners`: >= 0 integer validáció
---
## 2. Section: Financial Endpoints Status
### 2.1 Fleet Finance Modellek — Orphan Analysis
| Modell | DB Tábla Létezik? | Pydantic Schema? | API Endpoint? | API-n elérhető? |
|--------|------------------|-----------------|---------------|-----------------|
| `AssetFinancials` (backend/app/models/fleet_finance/models.py:141) | ✅ | ❌ | ❌ | ❌ (csak create-nél inicializálva) |
| `InsuranceProvider` (backend/app/models/fleet_finance/models.py:180) | ✅ | ❌ | ❌ | ❌ |
| `VehicleInsurancePolicy` (backend/app/models/fleet_finance/models.py:208) | ✅ | ❌ | ❌ | ❌ |
| `VehicleTaxObligation` (backend/app/models/fleet_finance/models.py:252) | ✅ | ❌ | ❌ | ❌ |
### 2.2 AssetFinancials Inicializálás
A `create_or_claim_vehicle()` (backend/app/services/asset_service.py:273) az alábbi módon hozza létre az `AssetFinancials` rekordot:
```
financials = AssetFinancials(
asset_id=asset.id,
purchase_price_net=Decimal("0"),
purchase_price_gross=Decimal("0"),
down_payment=Decimal("0"),
monthly_installment=Decimal("0"),
residual_value=Decimal("0"),
financing_type="unknown"
)
```
Ez a rekord SOHA nem frissíthető az API-n keresztül, mert:
- Nincs `PATCH /assets/{asset_id}/financials` végpont
- Nincs `PUT /assets/{asset_id}/financials` végpont
### 2.3 Meglévő Finance Végpontok
A `endpoints/expenses.py` tartalmazza az `AssetCost` CRUD-ot (költség típusú adatok), de a pénzügyi/finanszírozási adatok teljesen hiányoznak.
A `endpoints/finance_admin.py` csak `Issuer` menedzsmentet tartalmaz (kibocsátók kezelése), semmi köze a fleet finance modelljeihez.
### 2.4 Meglévő Pydantic Schémák a Finance Domainben
- `schemas/finance.py` — csak `IssuerResponse` és `IssuerUpdate`
- `schemas/fleet.py` — csak `EventCreate` és `TCOStats` (minimális)
---
## 3. Section: Frontend Form Data Structure Recommendations
A jelenlegi frontend `VehicleFormModal.vue` formája nem tartalmazza a 6 hiányzó internationalizációs mezőt, és nem kommunikál a fleet finance modellekkel.
### Ajánlott Payload Csoportosítás
Az új frontend form structure az alábbi logikai csoportokra bontható:
**A csoport — Alapadatok (Basic Info)**
- `brand`, `model`, `generation_name`, `trim_level`, `year`
- `license_plate`, `vin`
- `vehicle_class`, `body_type`
**B csoport — Regisztrációs adatok (Registration)**
- `registration_country` (ISO 3166-1 alpha-2 dropdown)
- `first_domestic_registration_date` (datepicker)
- `registration_certificate_number`
- `registration_certificate_validity`
- `vehicle_registration_document_number`
- `title_document_number`
**C csoport — Import adatok (Import)**
- `import_country` (ISO 3166-1 alpha-2 dropdown)
- `engine_number`
**D csoport — Tulajdonlás (Ownership)**
- `number_of_previous_owners`
- `color`, `mileage`, `fuel_type`, `transmission_type`
- `individual_equipment`
**E csoport — Pénzügyi adatok (Financials) — ÚJ endpoint**
- `purchase_price_net` / `purchase_price_gross`
- `down_payment`, `monthly_installment`, `residual_value`
- `financing_type`, `financing_provider`, `contract_number`
- `interest_rate`, `lease_start_date`, `lease_end_date`
**F csoport — Biztosítás és Adó (Insurance & Tax) — ÚJ endpoint**
- `insurance_type`, `policy_number`, `start_date`, `expiry_date`, `premium_amount`, `provider_id`
- `tax_type`, `tax_year`, `amount`, `due_date`, `payment_status`
### API Végpont Javaslatok
A frontend számára az alábbi új REST végpontokra lenne szükség:
1. `PATCH /api/v1/assets/vehicles/{asset_id}/financials` — AssetFinancials frissítése
2. `GET /api/v1/assets/vehicles/{asset_id}/financials` — AssetFinancials lekérése
3. `POST /api/v1/assets/vehicles/{asset_id}/insurance` — Biztosítási kötvény hozzáadása
4. `GET /api/v1/assets/vehicles/{asset_id}/insurance` — Biztosítási kötvények listázása
5. `POST /api/v1/assets/vehicles/{asset_id}/tax` — Adókötelezettség hozzáadása
6. `GET /api/v1/assets/vehicles/{asset_id}/tax` — Adókötelezettségek listázása
---
## 4. Section: Recommended Action Plan
### Phase 1 — Backend (Backend API javítások)
| # | Feladat | Érintett fájl(ok) | Priority |
|---|---------|-------------------|----------|
| 1 | Add 6 hiányzó mezőt az `AssetCreate` és `AssetUpdate` sémákhoz | schemas/asset.py | P0 |
| 2 | Add ISO 3166-1 alpha-2 validátor a `registration_country` és `import_country` mezőkhöz | schemas/asset.py | P0 |
| 3 | Add `first_domestic_registration_date` jövőbeli dátum tiltás | schemas/asset.py | P0 |
| 4 | Egészítsd ki a `create_or_claim_vehicle()` logikát a 6 új mezővel | services/asset_service.py | P0 |
| 5 | Hozd létre az `AssetFinancialsUpdate` Pydantic sémát | schemas/finance.py (új) | P1 |
| 6 | Hozd létre a `VehicleInsurancePolicyCreate/Response` sémákat | schemas/finance.py (új) | P1 |
| 7 | Hozd létre a `VehicleTaxObligationCreate/Response` sémákat | schemas/finance.py (új) | P1 |
| 8 | Implementáld a `PATCH /assets/{asset_id}/financials` végpontot | endpoints/assets.py (új) | P1 |
| 9 | Implementáld a `POST/GET /assets/{asset_id}/insurance` végpontokat | endpoints/assets.py (új) | P1 |
| 10 | Implementáld a `POST/GET /assets/{asset_id}/tax` végpontokat | endpoints/assets.py (új) | P1 |
### Phase 2 — Frontend (UI komponensek bővítése)
| # | Feladat | Érintett komponens | Priority |
|---|---------|-------------------|----------|
| 1 | Bővítsd a `VehicleFormModal.vue` űrlapot a B, C, D csoport mezőivel | VehicleFormModal.vue | P1 |
| 2 | Hozz létre egy új `FinancialsTab.vue` komponenst (E csoport) | (új fájl) | P1 |
| 3 | Hozz létre egy új `InsuranceTaxTab.vue` komponenst (F csoport) | (új fájl) | P1 |
| 4 | Integráld az új tabeket a `VehicleDetailModal.vue` komponensbe | VehicleDetailModal.vue | P1 |
---
## Appendix: Audited Files
| Fájl | Sorok | Tartalom |
|------|-------|----------|
| backend/app/api/v1/endpoints/vehicles.py | 1-143 | ❌ Csak social ratings, NEM vehicle CRUD |
| backend/app/api/v1/endpoints/assets.py | 1-1231 | ✅ Valódi vehicle CRUD |
| backend/app/schemas/asset.py | 1-453 | ✅ AssetCreate, AssetUpdate, AssetResponse |
| backend/app/schemas/vehicle.py | 1-56 | ❌ Csak social rating sémák |
| backend/app/models/vehicle/asset.py | 1-515 | ✅ Asset modell a 6 új mezővel |
| backend/app/services/asset_service.py | 1-802 | ✅ create_or_claim_vehicle (hiányzó 6 mező) |
| backend/app/api/v1/endpoints/finance_admin.py | 1-77 | ❌ Csak Issuer management |
| backend/app/api/v1/endpoints/expenses.py | 1-418 | ✅ AssetCost CRUD (de nem fleet finance) |
| backend/app/schemas/finance.py | 1-43 | ❌ Csak Issuer sémák |
| backend/app/schemas/fleet.py | 1-20 | ❌ Minimális (EventCreate, TCOStats) |
| backend/app/models/fleet_finance/models.py | 1-286 | ✅ 4 fleet finance modell (API nélkül) |
---
*Ez a report egy read-only audit eredménye. Kódmódosítás nem történt. A frontend redesign csak az Architect jóváhagyása után kezdődhet.*

View File

@@ -0,0 +1,189 @@
# 🏛️ P0 AUDIT: Financial Data Modeling - Purchasing, Leasing, Insurance & Taxes
**Dátum:** 2026-06-20
**Szerző:** Rendszer-Architect
**Státusz:** AUDIT COMPLETE (no code changes)
**Hatáskör:** `backend/app/models/vehicle/asset.py`, `backend/app/models/vehicle/vehicle.py`
---
## 1. AssetFinancials Audit
### 1.1 Jelenlegi állapot
A `AssetFinancials` modell a `vehicle.asset_financials` táblában egy 1:1 kapcsolatban áll az `Asset` modelllel (`uselist=False`).
**Meglévő oszlopok:**
| Oszlop | Típus | Leírás |
|--------|-------|--------|
| `id` | `Integer PK` | Elsődleges kulcs |
| `asset_id` | `UUID FK → vehicle.assets.id` | **UNIQUE** - 1:1 kapcsolat |
| `purchase_price_net` | `Numeric(18,2)` | Nettó vételár |
| `purchase_price_gross` | `Numeric(18,2)` | Bruttó vételár |
| `vat_rate` | `Numeric(5,2)` | ÁFA kulcs (alapértelmezett: 27.00) |
| `activation_date` | `DateTime` | Aktiválás dátuma |
| `verified_purchase_date` | `DateTime` | Ellenőrzött vásárlás dátuma |
| `financing_type` | `String(50)` | Finanszírozás típusa (pl. cash, loan, lease, unknown) |
| `accounting_details` | `JSONB` | Könyvelési részletek (catch-all) |
### 1.2 Hiányzó mezők (lízing/hitel finanszírozáshoz)
A következő mezők **hiányoznak** a jelenlegi modellből:
| Hiányzó mező | Típus | Indoklás |
|-------------|-------|----------|
| `down_payment` | `Numeric(18,2)` | Előleg - lízing és hitel esetén kritikus |
| `monthly_installment` | `Numeric(18,2)` | Havi törlesztő részlet |
| `residual_value` | `Numeric(18,2)` | Maradványérték (lízing végén) |
| `contract_number` | `String(100)` | Szerződésszám (hitel/lízing szerződés) |
| `financing_provider` | `String(200)` | Finanszírozó neve (bank, lízingcég) |
| `interest_rate` | `Numeric(5,2)` | Kamatláb (%) |
| `total_contract_value` | `Numeric(18,2)` | Teljes szerződéses érték |
| `lease_start_date` | `DateTime` | Lízing kezdő dátuma |
| `lease_end_date` | `DateTime` | Lízing vége dátuma |
| `payment_frequency` | `String(20)` | Fizetési gyakoriság (monthly/quarterly/yearly) |
| `payment_day` | `Integer` | Fizetési nap a hónapban (1-28) |
### 1.3 Javasolt bővítés
A meglévő `accounting_details` JSONB mező alkalmas lehet könyvelési metaadatok tárolására, de **dedikált oszlopok** szükségesek a lekérdezhetőség és adatintegritás biztosításához.
Javasolt új mezők az AssetFinancials modellhez:
- `down_payment` - Előleg
- `monthly_installment` - Havi törlesztő
- `residual_value` - Maradványérték
- `contract_number` - Szerződésszám
- `financing_provider` - Finanszírozó neve
- `interest_rate` - Kamatláb
- `total_contract_value` - Teljes szerződéses érték
- `lease_start_date` - Lízing kezdete
- `lease_end_date` - Lízing vége
- `payment_frequency` - Fizetési gyakoriság
- `payment_day` - Fizetési nap
---
## 2. Cost & Expense Modellek Összehasonlítása
### 2.1 `VehicleExpenses` - Legacy modell
| Tulajdonság | Érték |
|-------------|-------|
| **Tábla** | `vehicle.vehicle_expenses` |
| **Kategória típusa** | `String(50)` - egyszerű string, nincs FK |
| **ÁFA kezelés** | Nincs |
| **Jóváhagyási workflow** | Nincs |
| **Számla/dokumentum** | Nincs |
| **Kapcsolat AssetEvent-tel** | Nincs |
| **Használat** | Legacy, egyszerű reporting |
### 2.2 `AssetCost` - Elsődleges költségmodell
| Tulajdonság | Érték |
|-------------|-------|
| **Tábla** | `vehicle.asset_costs` |
| **Kategória** | `category_id``vehicle.cost_categories` (FK) |
| **ÁFA kezelés** | `amount_net`, `amount_gross`, `vat_rate` (Gross-First) |
| **Jóváhagyási workflow** | `DRAFT``PENDING_APPROVAL``APPROVED` |
| **Számla/dokumentum** | `invoice_number`, `document_id` |
| **Smart Sync (AssetEvent)** | Bidirectional `linked_asset_event_id` |
| **JSONB adatok** | Extra mezők: `mileage_at_cost`, `description` |
| **Szervezeti kötés** | `organization_id` |
### 2.3 Következtetés
A `VehicleExpenses` egy **legacy, leegyszerűsített modell**, amelyet a jövőben fokozatosan ki kell vezetni. A `AssetCost` a teljes értékű, jóváhagyási workflow-val és Smart Sync-kel rendelkező költségmodell.
---
## 3. Adatbázis Stratégia - Javasolt Rétegek
### 3.1 Rétegdiagram
```
1. réteg: Beszerzés & Finanszírozás (AssetFinancials)
├─ purchase_price_net / purchase_price_gross / vat_rate
├─ down_payment / monthly_installment / residual_value
├─ contract_number / financing_provider / interest_rate
├─ lease_start_date / lease_end_date
└─ payment_frequency / payment_day
2. réteg: Működési költségek (AssetCost)
├─ category_id → CostCategory
├─ amount_net / amount_gross / vat_rate
├─ invoice_number / status
└─ linked_asset_event_id / data JSONB
3. réteg: Biztosítási kötvények (VehicleInsurancePolicy) - ÚJ
├─ policy_number / insurer_name / coverage_type
├─ coverage_limit / deductible
├─ start_date / end_date / renewal_status
└─ premium_amount
4. réteg: Adókötelezettségek (VehicleTaxObligation) - ÚJ
├─ tax_type / authority / assessment_period
├─ tax_amount / due_date
└─ payment_status / exemption_status
```
### 3.2 Stratégia összefoglalása
**1. réteg - `AssetFinancials` bővítése:**
- Egyszeri beszerzési adatok: vételár, áfa, aktiválás dátuma
- Finanszírozási adatok: előleg, havi törlesztő, maradványérték, futamidő, kamatláb, szerződésszám
- 1:1 kapcsolatban az Asset-tel
**2. réteg - `AssetCost` (már létezik, változtatás nélkül):**
- Minden operatív költség itt landol: szerviz, üzemanyag, biztosítási díj befizetések, adó befizetések
- Jóváhagyási workflow (DRAFT → APPROVED)
**3. réteg - `VehicleInsurancePolicy` (ÚJ modell javasolt):**
- A biztosítási kötvény metaadatai
- A díj befizetések az AssetCost-ban rögzítésre kerülnek
**4. réteg - `VehicleTaxObligation` (ÚJ modell javasolt):**
- Az adókötelezettség metaadatai
- A díj befizetések az AssetCost-ban rögzítésre kerülnek
### 3.3 Döntési fa
```
Pénzügyi rekord érkezik
├─ Egyszeri beszerzési/vásárlási adat?
│ → AssetFinancials
├─ Finanszírozási szerződés (lízing/hitel)?
│ → AssetFinancials
├─ Rendszeres díj befizetés (biztosítás, adó)?
│ → AssetCost
├─ Biztosítási kötvény adatai?
│ → VehicleInsurancePolicy
└─ Adókötelezettség adatai?
→ VehicleTaxObligation
```
---
## 4. Módosítandó Fájlok (Tervezés - NEM kódolva)
| Fájl | Művelet |
|------|---------|
| `backend/app/models/vehicle/asset.py` | `AssetFinancials` bővítése lízing/hitel mezőkkel + új `VehicleInsurancePolicy` + `VehicleTaxObligation` modellek |
| `backend/app/schemas/asset.py` | `AssetFinancials` Pydantic schema bővítése |
| `backend/app/services/asset_service.py` | `AssetService.create_or_claim_vehicle()` frissítése (jelenleg hardcode 0 érték) |
| `backend/app/api/v1/endpoints/financials.py` | Potenciálisan új endpoint a biztosítás/adó CRUD-hoz |
---
## 5. Konklúzió
1. **`AssetFinancials`** jelenleg is alkalmas a beszerzési adatok tárolására, de **hiányoznak belőle a lízing-specifikus mezők**.
2. **`AssetCost`** a helyes modell az operatív költségekhez (beleértve a biztosítási díjakat és adó befizetéseket is). A `VehicleExpenses` legacy modell kivezetendő.
3. **Dedikált modellek** (`VehicleInsurancePolicy`, `VehicleTaxObligation`) szükségesek a biztosítási kötvények és adókötelezettségek metaadatainak tárolásához, míg a tényleges befizetések az `AssetCost`-ban landolnak.

View File

@@ -0,0 +1,199 @@
# 🚨 P0 UI/UX AUDIT — Vehicle Details Page Rendering
**Dátum:** 2026-06-19
**Auditor:** Rendszer-Architect
**Hatáskör:** `VehicleDetailsView.vue` + all 5 tab components
**Státusz:** 🔴 KRITIKUS — beavatkozás szükséges
---
## 1. AUDIT — Fejléc (Header) Olvashatóság
### Vizsgált fájl
`frontend/src/views/vehicles/VehicleDetailsView.vue`
### Háttér rétegek (layer stacking)
| Réteg | CSS | Leírás |
|-------|-----|--------|
| `z-0` | `fixed inset-0 bg-[url('@/assets/garage-bg.png')] bg-cover bg-center bg-fixed` (sor 4) | Teljes képernyős garázs háttérkép |
| `z-10` | `relative mx-auto max-w-7xl px-4 py-8` (sor 7) | Tartalom réteg |
| Body | `bg-[#04151F]` (main.css sor 10) | Sötétkék body háttér |
### Fejléc elemek és színeik
| Elem | Sor | CSS class | Szín |
|------|-----|-----------|------|
| Vissza gomb | 11 | `text-white/80` + `bg-white/10` | 80% fehér szöveg, 10% fehér háttér |
| Rendszám (H1) | 40 | `text-white` | `#ffffff` (tiszta fehér) |
| Márka/Modell/Év | 43 | `text-white/60` | 60% fehér (`rgba(255,255,255,0.6)`) |
| Tab navigáció | 52-58 | `text-white` / `text-white/50` | Tiszta fehér / 50% fehér |
### 🔴 PROBLÉMA: Nincs sötétítő overlay
A `VehicleDetailsView.vue:4` **egyáltalán nem tartalmaz sötétítő réteget** a `garage-bg.png` fölött. A `z-0` rétegen lévő kép közvetlenül a tartalom alatt van, semmilyen `bg-black/40`, sem `backdrop-brightness-50`, sem `overlay` nincs alkalmazva.
**Következmény:** Ha a `garage-bg.png` világos színtartományokat tartalmaz (ami egy garázs/workshop képnél tipikus), a `text-white` és `text-white/60` szövegek teljesen olvashatatlanná válnak.
> ⚠️ **Megjegyzés:** Ugyanez a probléma áll fenn a `FleetView.vue:3-6` esetében is, ahol a komment is jelzi: `"Garage Background Image (no dark overlay)"`.
---
## 2. AUDIT — Tab Konzisztencia
### CSS osztályok összehasonlítása
#### `OverviewTab.vue` — 🟦 **Light Theme (White Cards)**
| Elem | CSS class (sor) |
|------|-----------------|
| Photo div | `bg-white border-slate-200 shadow-sm rounded-2xl` (sor 6) |
| Basic Info card | `bg-white border-slate-200 shadow-sm rounded-2xl p-6` (sor 15) |
| MOT card | `bg-white border-slate-200 shadow-sm rounded-2xl p-5` (sor 55) |
| Mileage card | `bg-white border-slate-200 shadow-sm rounded-2xl p-5` (sor 72) |
| Next Service card | `bg-white border-slate-200 shadow-sm rounded-2xl p-5` (sor 89) |
| **Szövegszínek** | `text-slate-900`, `text-slate-700`, `text-slate-500` |
#### `TechDataTab.vue`, `ServiceBookTab.vue`, `FinancialsTab.vue`, `DocumentsTab.vue` — 🟩 **Dark Glassmorphism Theme (mind a 4)**
| Elem | CSS class (sor) |
|------|-----------------|
| Container | `bg-white/5 border-white/10 backdrop-blur-sm rounded-2xl p-6` (sor 2) |
| Szövegszínek | `text-white`, `text-white/50` |
### 🔴 PROBLÉMA: Teljes stilisztikai szakadás
| Aspektus | OverviewTab | 4 másik tab |
|----------|-------------|-------------|
| **Háttér** | `bg-white` (tiszta fehér) | `bg-white/5` (áttetsző, 5% fehér) |
| **Border** | `border-slate-200` (sötétszürke) | `border-white/10` (halvány fehér) |
| **Szöveg** | `text-slate-900` (sötétszürke) | `text-white` (fehér) |
| **Hatás** | `shadow-sm` | `backdrop-blur-sm` (üvegesség) |
| **Vizuális hangulat** | Hagyományos light card | Modern dark glassmorphism |
Az `OverviewTab.vue` **teljesen más tervezési rendszert** használ, mint a másik 4 tab. Ez vizuális sokkot okoz a tab váltáskor: a többi tab szépen beleolvad a sötét háttérbe, az OverviewTab viszont hirtelen "kiugró" fehér kártyákat mutat.
---
## 3. AUDIT — Hiányzó Járműfotó
### Vizsgált fájl
`frontend/src/components/vehicles/tabs/OverviewTab.vue`
### Jelenlegi állapot (sorok 6-12)
```html
<div class="flex aspect-[16/9] items-center justify-center overflow-hidden
rounded-2xl border border-slate-200 bg-white shadow-sm lg:aspect-square">
<img
src="https://images.unsplash.com/photo-1503376712341-a67b5e40e2d1?auto=format&fit=crop&w=800&q=80"
alt="Vehicle photo"
class="h-full w-full object-cover rounded-2xl"
/>
</div>
```
### 🔴 PROBLÉMA: 3 rétegű hiba
1. **Hardcode-olt stock fotó, nem a valós jármű** — A kép URL egy fix Unsplash link, minden járműnél ugyanaz a piros sportautó jelenik meg.
2. **Nincs hibakezelés betöltési hibára** — Ha az Unsplash CDN:
- Letiltja a hotlinkinget (CORS/403)
- Időtúllépés (timeout)
- Hálózati hiba
- **Akkor a felhasználó egy üres fehér dobozt lát**, mert nincs `@error` eseménykezelés és nincs fallback placeholder.
3. **Fehér háttér a sötét téma közepén** — A `bg-white border-slate-200` miatt, ha a kép nem tölt be, egy nagy fehér téglalap látszik a sötét háttér előtt, ami vizuálisan "kiég".
---
## 4. Architect Javaslatok
### 4.1 Fejléc olvashatóság javítása
**Ajánlott megoldás:** Sötétített overlay réteg bevezetése a header mögött.
**Lehetőség A — Header gradient sáv (ajánlott):**
Helyezzünk egy sötét gradient sávot közvetlenül a fejléc szövegek mögé (relative pozícionálással), ami blokkolja a `garage-bg.png` világos részeit:
```css
/* Egy sötét gradient overlay a header div-en belül */
.header-overlay {
background: linear-gradient(to bottom, rgba(4,21,31,0.85) 0%, rgba(4,21,31,0.4) 100%);
border-radius: 1rem;
padding: 1.5rem;
}
```
**Lehetőség B — Teljes oldalas overlay (gyors megoldás):**
A `garage-bg.png` div és a tartalom közé egy további `z-5` réteg:
```html
<div class="fixed inset-0 z-[5] bg-black/40"></div>
```
### 4.2 Tab egységesítés
**Ajánlott irány:** Az összes tabot a Dark Glassmorphism (`bg-white/5 backdrop-blur-sm border-white/10`) stílusra kell átállítani, hogy az egész oldal egységes, modern, sötét témájú maradjon.
| Módosítandó elem | Jelenlegi | Javasolt |
|------------------|-----------|----------|
| OverviewTab photo div | `bg-white border-slate-200 shadow-sm` | `bg-white/5 border-white/10 backdrop-blur-sm` |
| OverviewTab info card | `bg-white border-slate-200 shadow-sm p-6` | `bg-white/5 border-white/10 backdrop-blur-sm p-6` |
| OverviewTab vital cards (x3) | `bg-white border-slate-200 shadow-sm p-5` | `bg-white/5 border-white/10 backdrop-blur-sm p-5` |
| OverviewTab szövegek | `text-slate-900/700/500` | `text-white/90/70/50` |
| Icon bg-k (MOT, mileage, service) | `bg-amber-100`, `bg-blue-100`, `bg-emerald-100` | `bg-amber-500/20`, `bg-blue-500/20`, `bg-emerald-500/20` |
| Icon színek | `text-amber-600`, `text-blue-600`, `text-emerald-600` | `text-amber-300`, `text-blue-300`, `text-emerald-300` |
### 4.3 Járműfotó javítása
**Kötelező változtatások:**
1. **Dinamikus képforrás** — Az `src` attribute-nak a backendről kell jönnie (pl. `vehicle.photo_url`), és ha nincs fotó, egy placeholder SVG-t kell mutatni.
2. **Fallback és hibakezelés** — Az `<img>` tagon `@error` eseménykezelő, ami placeholder-re vált:
```html
<img
:src="vehicle.photo_url || placeholderUrl"
alt="Vehicle photo"
class="h-full w-full object-cover rounded-2xl"
@error="onImageError"
/>
```
```ts
const imageError = ref(false)
const onImageError = () => { imageError.value = true }
```
3. **Placeholder komponens** — Ha a kép nem tölt be, egy jármű SVG ikon jelenjen meg:
```html
<div v-if="imageError || !vehicle.photo_url"
class="flex h-full w-full items-center justify-center bg-white/5">
<svg class="h-16 w-16 text-white/30" ...>
<!-- Vehicle silhouette icon -->
</svg>
<p class="text-sm text-white/30">{{ t('vehicleDetail.noPhoto') }}</p>
</div>
```
### 4.4 Hatásvizsgálat: Érintett fájlok
| Fájl | Módosítás típusa |
|------|------------------|
| `VehicleDetailsView.vue` (`frontend/src/views/vehicles/VehicleDetailsView.vue`) | Overlay réteg hozzáadása a header köré |
| `OverviewTab.vue` (`frontend/src/components/vehicles/tabs/OverviewTab.vue`) | Teljes CSS átállítás light → dark glassmorphism + kép placeholder logika |
| `FleetView.vue` (`frontend/src/views/vehicles/FleetView.vue`) | (Ajánlott) Ugyanez az overlay javítás, mivel itt is ugyanaz a probléma |
---
## Összefoglaló
| # | Probléma | Súlyosság | Érintett fájl(ok) |
|---|----------|-----------|-------------------|
| 1 | Fehér szöveg világos háttérképen — nincs overlay | 🔴 **KRITIKUS** | `VehicleDetailsView.vue:4,40-43` |
| 2 | Tab stílusok teljes inkonzisztenciája (light vs dark) | 🔴 **KRITIKUS** | `OverviewTab.vue` vs összes többi tab |
| 3 | Hiányzó járműfotó / nincs fallback | 🟡 **MAGAS** | `OverviewTab.vue:6-12` |
**Ajánlott beavatkozási sorrend:** ① Tab egységesítés (glassmorphism) → ② Header overlay → ③ Photo placeholder + error handling

View File

@@ -0,0 +1,180 @@
# 🚗 Jármű Adatfolyam Audit 2026-06-21
**Kártya:** #275 - Audit: Jármű adatfolyam (DB → API → Frontend) teljes körű elemzése
**Auditor:** Rendszer-Architect
**Státusz:** Kész
---
## 📋 Vizsgált Kérdések
1. **Dashboard jármű kártyák hova mutatnak?**
2. **Járművek oldalon milyen adatok látszanak?**
3. **Áttekintés (Overview) fül melyik adatbázis táblákból kapja az adatokat?**
4. **API kapcsolatok lefedik-e az összes adatbázis adatot?**
---
## 1. Dashboard Jármű Kártyák (VehicleCardStandard)
**Fájl:** `frontend/src/components/vehicle/VehicleCardStandard.vue`
**Linkelés:**
- `@click="$emit('click', vehicle')` → A parent komponens kezeli (pl. DashboardView, CompanyGarageView), ami megnyitja a `VehicleDetailModal`-t
**Kártyán megjelenő adatok és forrásaik:**
| Mező | Forrás (DB) | API |
|------|-------------|-----|
| `license_plate` | `vehicle.asset.license_plate` | `GET /assets/vehicles` |
| `brand` | `vehicle.asset.brand` | `GET /assets/vehicles` |
| `model` | `vehicle.asset.model` | `GET /assets/vehicles` |
| `year_of_manufacture` | `vehicle.asset.year_of_manufacture` | `GET /assets/vehicles` |
| `current_mileage` | `vehicle.asset.current_mileage` | `GET /assets/vehicles` |
| `engine` (kW / cm³) | `vehicle.asset.power_kw` / `engine_capacity` | `GET /assets/vehicles` |
| `fuel_type` | `vehicle.asset.fuel_type` | `GET /assets/vehicles` |
| `color` | `individual_equipment.color` (JSONB) | `GET /assets/vehicles` |
| `nextService` | `individual_equipment.dates.next_service` (JSONB) | `GET /assets/vehicles` |
---
## 2. Jármű Részletek (VehicleDetailModal)
**Fájl:** `frontend/src/components/vehicle/VehicleDetailModal.vue`
A modál **4 tab**-ot tartalmaz:
### Tab 1: Alapadatok (Basic Data)
- **Forrás:** `vehicle.asset` mezői + `individual_equipment` JSONB
- **Megjelenített:** Engine (kW/LE), body_type, VIN, mileage, transmission, drive_type, fuel_type, trim_level, EV specs, features, dates, documents, trust score
### Tab 2: Pénzügyek (Finance)
- **API:** `GET /assets/{id}/costs``finance.asset_costs`
- **Tartalom:** Éves TCO, fix/running költségek, prémium chartok, átlag fogyasztás/km költség
- **HIÁNYZIK:** AssetFinancials (beszerzés/lízing), VehicleInsurancePolicy (biztosítások), VehicleTaxObligation (adók)
### Tab 3: Figyelmeztetések (Alerts)
- **Forrás:** `individual_equipment.dates.*` (JSONB)
- **Tartalom:** insurance_expiry, mot_expiry, next_service, tire_change
### Tab 4: Szervizkönyv (Servicebook)
- **API:** `GET /assets/{id}/events``vehicle.asset_events`
- **Művelet:** Listázás + POST új esemény
---
## 3. Áttekintés (OverviewTab) Adatforrásai
**Fájl:** `frontend/src/components/vehicles/tabs/OverviewTab.vue`
| Adat | Forrás | DB Tábla/Mező |
|------|--------|---------------|
| `license_plate` | `inject('vehicle')` | `vehicle.asset.license_plate` |
| `brand`, `model` | `inject('vehicle')` | `vehicle.asset.brand/model` |
| `year_of_manufacture` | `inject('vehicle')` | `vehicle.asset.year_of_manufacture` |
| `fuel_type` | `inject('vehicle')` (i18n) | `vehicle.asset.fuel_type` |
| `current_mileage` | `inject('vehicle')` | `vehicle.asset.current_mileage` |
| Havi költség | `expenseStore.monthlyCost` | `finance.asset_costs` (aggregált) |
| Havi km | `individual_equipment.monthly_mileage` (JSONB) | `vehicle.asset` |
| Legutóbbi 3 költség | `expenseStore.recentExpenses` | `finance.asset_costs` |
| MOT lejárat | `individual_equipment.dates.mot_expiry` (JSONB) | `vehicle.asset` |
| Következő szerviz | `individual_equipment.dates.next_service` (JSONB) | `vehicle.asset` |
---
## 4. API Lefedettség Ellenőrzés
### AssetResponse lefedettség (50+ mező)
- **Minden** `vehicle.asset` mező szerepel az `AssetResponse`-ban
- A frontend `Vehicle` interface is lefedi az összes mezőt
- **Kivétel:** `cargo_volume_x` és `cargo_volume_y` közül csak `cargo_volume_y` van a frontend típusban
### Fleet_finance lefedettség
- `asset_financials``GET /assets/vehicles/{id}/financials`
- `vehicle_insurance_policies``GET /assets/vehicles/{id}/insurance`
- `vehicle_tax_obligations``GET /assets/vehicles/{id}/taxes`
- Mindhárom külön API endpointon, teljes CRUD támogatással
### Hiányzó API endpointok
- `VehicleUserRating` → Nincs API (trust score rendszer hiányos)
- `VehicleTransferRequest` → Nincs API (tulajdonosváltás kézzel)
- `VehicleLogbook` → Nincs API (naplózás hiányos)
---
## 5. Feltárt Problémák
### PROBLÉMA 1: AssetFinancials hiánya a VehicleDetailModal-ból
**Súlyosság:** Magas
**Leírás:** A VehicleDetailModal Pénzügyek tabja csak az AssetCost adatokat jeleníti meg. Az AssetFinancials (beszerzés, lízing), VehicleInsurancePolicy (biztosítások) és VehicleTaxObligation (adók) adatok a külön FinancialsTab komponensben érhetők el, ami nem része a modálnak.
**Javaslat:** Integrálni a FinancialsTab-ot a VehicleDetailModal-ba, vagy legalább linket tenni a teljes pénzügyi nézetre.
### PROBLÉMA 2: is_primary vizuális jelzés hiánya
**Súlyosság:** Közepes
**Leírás:** Az `is_primary` mező létezik (JSONB-ből számolva), de a VehicleCardStandard nem jeleníti meg vizuálisan.
**Javaslat:** Csillag/jelvény hozzáadása a kártyához, ha a jármű elsődleges.
### PROBLÉMA 3: Frontend Vehicle típushiba
**Súlyosság:** Alacsony
**Leírás:** A frontend Vehicle interface tartalmaz `asset_financials`, `insurance_policies`, `tax_obligations` mezőket, de ezek nem részei a backend AssetResponse sémának - külön API hívásokkal töltődnek be.
**Javaslat:** Tisztázni a frontend típusokat, opcionális mezőként jelezni, hogy ezek külön betöltendők.
---
## 6. Adatfolyam Diagram
```
┌──────────────────┐ GET /assets/vehicles ┌──────────────────┐
│ PostgreSQL DB │ ←─────────────────────────→ │ FastAPI Backend │
│ │ │ │
│ vehicle.asset │ AssetResponse (50+ mező) │ /vehicles GET │
│ vehicle.asset_ │ AssetCatalogResponse │ /vehicles/{id} │
│ catalog │ AssetEventResponse │ /{id}/events │
│ finance.asset_ │ AssetCostResponse │ /{id}/costs │
│ costs │ AssetFinancialsResponse │ /{id}/financials│
│ fleet_finance.* │ VehicleInsurancePolicyResp │ /{id}/insurance │
│ vehicle.asset_ │ VehicleTaxObligationResp │ /{id}/taxes │
│ events │ │ │
└──────────────────┘ └──────────────────┘
┌──────────────────┐
│ Vue 3 Frontend │
│ │
│ VehicleCard │
│ → click │
│ → DetailModal │
│ │
│ VehicleDetail │
│ Modal (4 tab) │
│ ├─ Alapadatok │
│ ├─ Pénzügyek │
│ │ (csak costs) │
│ ├─ Figyelmezt. │
│ └─ Szervizkönyv │
│ │
│ OverviewTab │
│ (inject vehicle │
│ + expenseStore)│
│ │
│ FinancialsTab │
│ (külön oldal) │
└──────────────────┘
```
---
## 7. Következtetés
**Összességében az adatfolyam megfelelően van felépítve.** Az API lefedi az adatbázis összes fontos mezőjét, a frontend komponensek a megfelelő API végpontokról olvassák az adatokat.
**Főbb erősségek:**
- Thick Digital Twin koncepció jól implementálva
- AssetResponse minden mezőt tartalmaz
- Fleet_finance modellek külön, tiszta API endpointokon
- JSONB mezők (individual_equipment) rugalmasan használva
**Főbb hiányosságok:**
1. VehicleDetailModal Pénzügyek tabja nem tartalmazza a lízing/biztosítás/adó adatokat
2. is_primary vizuális jelzés hiányzik a kártyán
3. VehicleUserRating, VehicleTransferRequest, VehicleLogbook API-hiányos

View File

@@ -0,0 +1,142 @@
# 🚗 Jármű Regisztrációs Dokumentumok Audit Jelentés
**Dátum:** 2026-06-19
**Auditor:** Rendszer-Architect
**Hatáskör:** Teljes adatfolyam (adatbázis → API → Frontend)
**Státusz:** Hiányzó funkciók azonosítva — beavatkozás szükséges
---
## 📋 Összefoglaló
A jármű rögzítési felületek auditja során **3 adatmező hiányát** azonosítottuk, amelyek a forgalmi engedély és törzskönyv adatainak rögzítéséhez szükségesek. Ezek az adatok jelenleg **sehol sem** tárolhatók el az adatbázisban, és a felhasználói felületen sem érhetők el.
---
## 🔴 Hiányzó Mezők (3 db)
| # | Mező neve | Magyar leírás | Típus | Hol hiányzik |
|---|-----------|---------------|-------|--------------|
| 1 | `registration_certificate_number` | Forgalmi engedély száma | `VARCHAR(50)` | Minden rétegben |
| 2 | `registration_certificate_validity` | Forgalmi engedély érvényessége | `DATE` | Minden rétegben |
| 3 | `vehicle_registration_document_number` | Törzskönyv száma | `VARCHAR(50)` | Minden rétegben |
---
## 🔍 Részletes Hiánytérkép (Rétegenként)
### 1. 🗄️ Adatbázis — Asset Model
**Fájl:** `backend/app/models/vehicle/asset.py` (69. sor)
Az `Asset` modell jelenleg a következő dokumentációs/admin mezőket tartalmazza:
- `first_registration_date` — Első forgalomba helyezés dátuma
- `next_mot_date` — Műszaki vizsga lejárata
- `insurance_expiry_date` — Biztosítás lejárata
- `purchase_date` — Vásárlás dátuma
- `is_under_warranty` / `warranty_expiry_date` — Jótállás
- `condition` — Állapot
- `color` — Szín
- `notes` — Megjegyzések
**Hiányzik:**
- `registration_certificate_number`
- `registration_certificate_validity`
- `vehicle_registration_document_number`
### 2. 📐 Pydantic Sémák
**Fájl:** `backend/app/schemas/asset.py`
| Séma | Sor | Státusz |
|------|-----|---------|
| `AssetResponse` | 32 | ❌ Mind a 3 mező hiányzik |
| `AssetCreate` | 117 | ❌ Mind a 3 mező hiányzik |
| `AssetUpdate` | 259 | ❌ Mind a 3 mező hiányzik |
### 3. 🌐 API Végpontok
**Fájl:** `backend/app/api/v1/endpoints/assets.py`
| Végpont | Művelet | Státusz |
|---------|---------|---------|
| `POST /vehicles` | Létrehozás | ❌ AssetCreate nem tartalmazza a mezőket |
| `PUT /vehicles/{asset_id}` | Frissítés | ❌ AssetUpdate nem tartalmazza a mezőket |
| `GET /vehicles` | Listázás | ❌ AssetResponse nem tartalmazza a mezőket |
| `GET /vehicles/{id}` | Részletek | ❌ AssetResponse nem tartalmazza a mezőket |
### 4. ⚙️ Asset Service
**Fájl:** `backend/app/services/asset_service.py` (34. sor)
A `create_or_claim_vehicle()` metódus (172-222. sor) `asset_fields` szótára **nem tartalmazza** a dokumentum mezőket.
### 5. 🖥️ Frontend — Vehicle Store
**Fájl:** `frontend/src/stores/vehicle.ts` (9. sor)
A `Vehicle` interfész (9-90. sor) nem tartalmazza a dokumentum mezőket.
### 6. 🖥️ Frontend — VehicleFormModal
**Fájl:** `frontend/src/components/dashboard/VehicleFormModal.vue`
| Alrendszer | Sor | Státusz |
|------------|-----|---------|
| Form reactive state | 1742-1806 | ❌ Hiányzik mind a 3 mező |
| Admin Tab UI (Tab 2) | 961-1018 | ❌ Csak állapot, szín, jótállás, jegyzetek |
| handleSave() payload | 2122-2199 | ❌ Hiányzik mind a 3 mező |
| populateForm() | 2029-2113 | ❌ Hiányzik mind a 3 mező |
| resetForm() | 1987-2026 | ❌ Hiányzik mind a 3 mező |
### 7. 🌍 i18n Fordítások
**Frontend:** `frontend/src/i18n/hu.ts` és `frontend/src/i18n/en.ts`
**Backend:** `backend/static/locales/hu.json` és `backend/static/locales/en.json`
Hiányzó fordítási kulcsok:
- `vehicle.label_registration_certificate_number`
- `vehicle.label_registration_certificate_validity`
- `vehicle.label_vehicle_registration_document_number`
---
## 📊 Működő Mezők Ellenőrzőlistája
### Alapadatok Tab (Tab 0) ✅
Rendszám, VIN, Márka, Modell, Gyártási év, Üzemanyag típus, Felszereltségi szint, Név, Km óra állás
### Műszaki Tab (Tab 1) ✅
Hengerűrtartalom, Teljesítmény, Nyomaték, Hajtás, Váltó, Kárószéria típus, Klíma típus, Henger-elrendezés, Ajtók/Ülések/Hengerek száma, Súlyadatok, EV adatok, Dátumok
### Adminisztráció Tab (Tab 2) ⚠️
- ✅ Állapot, Szín, Jótállás, Megjegyzések
- ❌ Forgalmi engedély száma
- ❌ Forgalmi engedély érvényessége
- ❌ Törzskönyv száma
### Felszereltség Tab (Tab 3) ✅
Személyautó, Motorkerékpár, LCV, HGV kategóriák szerinti felszereltség
---
## 📝 Terv a Hiányzó Funkciók Pótlására
### Végrehajtandó lépések (sorrendben):
1. **Adatbázis** — 3 új oszlop hozzáadása az `Asset` modellhez (`backend/app/models/vehicle/asset.py`)
2. **Sync**`docker exec -it sf_api python -m app.scripts.sync_engine`
3. **Pydantic sémák**`AssetResponse`, `AssetCreate`, `AssetUpdate` bővítése (`backend/app/schemas/asset.py`)
4. **Asset Service**`asset_fields` dict bővítése (`backend/app/services/asset_service.py`)
5. **Vehicle Store**`Vehicle` interfész bővítése (`frontend/src/stores/vehicle.ts`)
6. **VehicleFormModal** — form state, Admin tab UI, handleSave, populateForm, resetForm bővítése
7. **Frontend i18n** — magyar és angol fordítási kulcsok hozzáadása
8. **Backend i18n** — (opcionális) magyar és angol fordítási kulcsok hozzáadása
---
## ⚠️ Megjegyzések
1. **Nincs szükség új migrációs fájlra** — A projekt a `sync_engine`-t használja a séma szinkronizálására.
2. **Külön oszlopok** — Az adatok nem a JSONB `individual_equipment` mezőbe kerülnek, mivel strukturált admin adatok.
3. **Soft-delete kompatibilitás** — Az új mezők `nullable=True` beállítással jönnek létre.