334 lines
16 KiB
Markdown
334 lines
16 KiB
Markdown
### ✅ Verifikáció
|
||
1. **Build ellenőrzés** → `npx vite build` sikeres (7.08s, 0 error)
|
||
2. **Chunk generálva**: `CostEntryWizard-C_-R4OzC.js` (29.95 kB) - refaktorált komponens helyesen fordul
|
||
3. **ProviderAutocomplete** újrafelhasználva mindhárom komponensben - nincs duplikáció
|
||
|
||
---
|
||
|
||
## P0 EPIC - UI/UX Overhaul for Quick Fuel Logging (Gitea #404)
|
||
|
||
**Dátum:** 2026-07-09
|
||
**Scope:** Backend + Frontend (SimpleFuelModal, ProviderAutocomplete, ProviderQuickAddModal)
|
||
|
||
### 🔧 Módosított fájlok
|
||
|
||
#### 1. Backend: [`providers.py`](backend/app/api/v1/endpoints/providers.py:200)
|
||
- Search query `min_length` changed from `2` → `3`
|
||
- Hibaüzenet frissítve: "min. 3 karakter"
|
||
|
||
#### 2. Backend: [`provider_service.py`](backend/app/services/provider_service.py:288)
|
||
- City search changed from `StartsWith` (`{city}%`) to `ILIKE` partial (`%{city}%`) in all 3 data sources:
|
||
- **Organization** (GeoPostalCode.city)
|
||
- **ServiceStaging** (ServiceStaging.city)
|
||
- **ServiceProvider** (ServiceProvider.address)
|
||
- Zip code search also changed to partial ILIKE
|
||
|
||
#### 3. Frontend: [`ProviderAutocomplete.vue`](frontend_app/src/components/provider/ProviderAutocomplete.vue:127)
|
||
- Minimum search length: `2` → `3` characters
|
||
- Empty state condition updated: `searchQuery.length >= 3`
|
||
|
||
#### 4. Frontend: [`ProviderQuickAddModal.vue`](frontend_app/src/components/provider/ProviderQuickAddModal.vue:539)
|
||
- New `prefillName?: string` prop added
|
||
- Watch on `isOpen` now sets `form.name = props.prefillName` when provided
|
||
|
||
#### 5. Frontend: [`SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue) (complete rewrite)
|
||
|
||
**Magic Triangle (Auto-calculation):**
|
||
- 3 fields: `amountGross`, `unitPrice`, `quantity`
|
||
- Tracks `lastEditedField` to determine which field to auto-calculate
|
||
- Formula: `amount_gross = unit_price × quantity`
|
||
- 2-decimal precision via `roundTo2Decimals()`
|
||
|
||
**Odometer Hint:**
|
||
- On modal open, fetches last expense via `GET /expenses/{asset_id}?limit=1&sort=date_desc`
|
||
- Shows last known `mileage_at_cost` as a hint below the odometer input
|
||
|
||
**Delayed New Provider Flow:**
|
||
- If user types a raw vendor name without selecting a provider:
|
||
1. Expense is saved with `external_vendor_name` set
|
||
2. `unit_price` and `quantity` are sent in `data` JSONB payload
|
||
3. After save, `ProviderQuickAddModal` opens automatically with `prefillName` prop
|
||
4. User can create the provider with the name pre-filled
|
||
|
||
### ✅ Verifikáció
|
||
1. **Sync Engine** → `docker exec sf_api python -m app.scripts.sync_engine` → 1287/1287 elements OK, 0 error
|
||
2. **Python syntax** → All 3 backend files compile successfully (0 syntax errors)
|
||
3. **Vue TypeScript** → `vue-tsc --noEmit` → Only pre-existing errors (Logo.vue.js, logo1.vue.js), no new errors
|
||
4. **Backend search** → 3-char minimum enforced, partial ILIKE city search across all 3 sources
|
||
5. **Magic Triangle** → Auto-calculates based on last edited field, 2-decimal precision
|
||
6. **Delayed New Provider** → Expense saved first, then ProviderQuickAddModal opens with prefillName
|
||
|
||
---
|
||
|
||
## P0 BUGFIX: SimpleFuelModal 500 POST crash + 5 frontend issues (Gitea #405)
|
||
|
||
**Dátum:** 2026-07-10
|
||
**Scope:** Backend (expenses.py, provider_service.py) + Frontend (SimpleFuelModal.vue)
|
||
|
||
### 🔍 Root Cause Analysis: 500 POST /expenses/ crash
|
||
|
||
**Hiba:** `ForeignKeyViolationError` on `fk_asset_costs_vendor_org`
|
||
|
||
**Kiváltó ok:** A frontend [`SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue) a `vendor_organization_id` mezőbe mindig a `selectedProvider.value!.id` értéket küldte, függetlenül attól, hogy a provider milyen forrásból származott. A [`ProviderAutocomplete`](frontend_app/src/components/provider/ProviderAutocomplete.vue) három forrásból ad vissza találatokat:
|
||
|
||
| Source | Tábla | ID típus |
|
||
|--------|-------|----------|
|
||
| `verified_org` | `fleet.organizations` | Organization.id ✅ |
|
||
| `staged_data` | `marketplace.service_staging` | ServiceStaging.id ❌ |
|
||
| `crowd_added` | `marketplace.service_providers` | ServiceProvider.id ❌ |
|
||
|
||
Az FK constraint `fk_asset_costs_vendor_org` a [`fleet_finance.asset_costs.vendor_organization_id`](backend/app/models/fleet_finance/models.py:130) oszlopon van, ami a `fleet.organizations.id`-ra hivatkozik. Amikor a felhasználó kiválasztott egy "Shell"-t, ami a `crowd_added` forrásból jött (ServiceProvider.id=6), a frontend `vendor_organization_id=6`-ot küldött, ami nem létezik a `fleet.organizations` táblában → **500-as hiba**.
|
||
|
||
### 🔧 FIX 1: Backend FK-safe validation (expenses.py)
|
||
|
||
**Fájl:** [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py:618)
|
||
|
||
A `create_expense` végpontban, mielőtt létrehoznánk az `AssetCost` rekordot, egy FK-ellenőrző blokk fut le:
|
||
|
||
```python
|
||
# ── P0 BUGFIX (2026-07-10): FK-safe vendor_organization_id validation ──
|
||
resolved_vendor_org_id = expense.vendor_organization_id
|
||
if resolved_vendor_org_id is not None:
|
||
org_check_stmt = select(Organization.id).where(
|
||
Organization.id == resolved_vendor_org_id,
|
||
Organization.is_deleted == False,
|
||
)
|
||
org_check_result = await db.execute(org_check_stmt)
|
||
org_exists = org_check_result.scalar_one_or_none()
|
||
if org_exists is None:
|
||
logger.warning(
|
||
f"vendor_organization_id={resolved_vendor_org_id} does not exist "
|
||
f"in fleet.organizations. Setting to None and using service_provider_id."
|
||
)
|
||
resolved_vendor_org_id = None
|
||
if resolved_provider_id is None:
|
||
resolved_provider_id = expense.vendor_organization_id
|
||
```
|
||
|
||
**Logika:** Ha a `vendor_organization_id` nem létezik a `fleet.organizations` táblában, `None`-ra állítjuk, és az ID-t áthelyezzük a `service_provider_id` mezőbe. Ez biztosítja, hogy:
|
||
1. Az FK constraint soha nem sérül meg
|
||
2. A provider referencia nem vész el (átkerül a `service_provider_id`-ba)
|
||
3. A frontend nem kap 500-as hibát
|
||
|
||
**További javítás:** Hiányzó `SystemParameter` import hozzáadva.
|
||
|
||
### 🔧 FIX 2: Frontend source-aware provider routing (SimpleFuelModal.vue)
|
||
|
||
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:handleSubmit)
|
||
|
||
A `handleSubmit` függvény most a provider `source` mezője alapján dönti el, hova kerüljön az ID:
|
||
|
||
```typescript
|
||
const providerSource = selectedProvider.value?.source || null
|
||
const payload: Record<string, any> = {
|
||
vendor_organization_id: hasSelectedProvider && providerSource === 'verified_org'
|
||
? selectedProvider.value!.id
|
||
: null,
|
||
service_provider_id: hasSelectedProvider && providerSource === 'crowd_added'
|
||
? selectedProvider.value!.id
|
||
: null,
|
||
// ...
|
||
}
|
||
```
|
||
|
||
**Logika:**
|
||
- `verified_org` → `vendor_organization_id` (FK biztonságos)
|
||
- `crowd_added` → `service_provider_id` (FK nélküli mező)
|
||
- `staged_data` → egyik sem (még nincs véglegesítve)
|
||
|
||
### 🔧 FIX 3: Currency selector hozzáadása
|
||
|
||
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:template)
|
||
|
||
A `form.currency` mező most már HUF/EUR választóval rendelkezik:
|
||
|
||
```html
|
||
<select v-model="form.currency" class="w-20 rounded-xl ...">
|
||
<option value="HUF">HUF</option>
|
||
<option value="EUR">EUR</option>
|
||
</select>
|
||
```
|
||
|
||
A `form` state tartalmazza: `currency: 'HUF' as string`
|
||
|
||
### 🔧 FIX 4: Magic Triangle @blur events
|
||
|
||
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:script)
|
||
|
||
**Probléma:** A korábbi `@input` események végtelen rekurziót okoztak, mert amikor az `autoCalculate()` visszaírt egy mezőbe, az újra kiváltotta az `@input` eseményt.
|
||
|
||
**Megoldás:** Mindhárom mező (`amountGross`, `unitPrice`, `quantity`) `@blur` eseményt használ:
|
||
|
||
```typescript
|
||
function onAmountGrossBlur(e: Event) {
|
||
const val = parseFloat((e.target as HTMLInputElement).value) || 0
|
||
form.amountGross = val
|
||
lastEditedField.value = 'amount_gross'
|
||
autoCalculate()
|
||
}
|
||
```
|
||
|
||
**Logika:**
|
||
1. A `@blur` csak akkor fut le, amikor a felhasználó elhagyja a mezőt (pl. Tab-bal vagy kattintással)
|
||
2. A `lastEditedField` nyomon követi, melyik mezőt szerkesztették utoljára
|
||
3. Az `autoCalculate()` a hiányzó mezőt számolja ki: `amount_gross = unit_price × quantity`
|
||
4. Nincs végtelen rekurzió, mert a `@blur` nem triggerelődik programozott értékadásra
|
||
|
||
### 🔧 FIX 5: Odometer hint response parsing
|
||
|
||
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:fetchOdometerHint)
|
||
|
||
**Probléma:** A `GET /expenses/{asset_id}` végpont `{ status, total, data: [...] }` formátumban adja vissza az adatokat, de a kód egy sima tömböt várt.
|
||
|
||
**Megoldás:**
|
||
```typescript
|
||
const body = res.data as { status?: string; total?: number; data?: Array<{ mileage_at_cost?: number | null }> }
|
||
const expenses = body.data || []
|
||
```
|
||
|
||
### 🔧 FIX 6: providers/search 500 error (AddressOut validation)
|
||
|
||
**Fájl:** [`backend/app/services/provider_service.py`](backend/app/services/provider_service.py:537)
|
||
|
||
**Probléma:** A `search_providers()` függvényben az `AddressOut` Pydantic v2 modell hibát dobott, amikor minden mező `None` volt.
|
||
|
||
**Megoldás:** Try-except blokk az `AddressOut` konstrukció körül:
|
||
```python
|
||
if any([row_address_zip, row_address_street_name, row_address_street_type, row_address_house_number, row_city]):
|
||
try:
|
||
address_detail = AddressOut(...)
|
||
except Exception as addr_e:
|
||
logger.warning(...)
|
||
address_detail = None
|
||
```
|
||
|
||
### ✅ Verifikáció
|
||
1. **Python syntax check** → `expenses.py: OK`, `provider_service.py: OK`
|
||
2. **Sync Engine** → 1287/1287 elements OK, 0 fixes needed
|
||
3. **Frontend TypeScript** → All type-safe, no new compilation errors
|
||
|
||
---
|
||
|
||
## P0 BUGFIX - Odometer Source of Truth & Sync (Gitea #406)
|
||
|
||
**Dátum:** 2026-07-10
|
||
**Scope:** Frontend (SimpleFuelModal.vue)
|
||
|
||
### 🔧 Módosított fájlok
|
||
|
||
#### 1. Frontend: [`SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:228)
|
||
- **Fix:** `fetchLastOdometer()` most már a `/assets/vehicles/{asset_id}` végpontot hívja a `/expenses/{asset_id}` helyett.
|
||
- **Logika:** A `Vehicle.current_mileage` mezőt olvassa ki, ami a Source of Truth (automatikusan szinkronizálva a `create_expense` által).
|
||
- **Korábbi hiba:** A frontend a `/expenses/{asset_id}` végpontot hívta, ami a költségek listáját adta vissza, nem az aktuális km-állást.
|
||
|
||
#### 2. Backend: [`expenses.py`](backend/app/api/v1/endpoints/expenses.py:635)
|
||
- **Ellenőrizve:** A `create_expense` függvény (635-637. sor) már tartalmazza az `asset.current_mileage` szinkronizálást:
|
||
```python
|
||
if expense.mileage_at_cost is not None and expense.mileage_at_cost > (asset.current_mileage or 0):
|
||
asset.current_mileage = expense.mileage_at_cost
|
||
```
|
||
- **Nincs változtatás:** A backend logika helyes, nem volt szükség módosításra.
|
||
|
||
#### 3. Frontend: [`ComplexExpenseModal.vue`](frontend_app/src/components/dashboard/ComplexExpenseModal.vue)
|
||
- **Nincs változtatás:** Ez a komponens nem tartalmaz odometer mezőt, így nem érintett.
|
||
|
||
### ✅ Verifikáció
|
||
1. **Python syntax check** → `expenses.py: OK`
|
||
2. **Sync Engine** → 1287/1287 elements OK, 0 fixes needed
|
||
3. **Frontend** → `fetchLastOdometer()` most a `/assets/vehicles/{id}` végpontot használja, ami a `current_mileage`-t adja vissza
|
||
|
||
---
|
||
|
||
## P0 EPIC - CostEntryWizard UI/UX Parity & Categories (Gitea #407)
|
||
|
||
**Dátum:** 2026-07-10
|
||
**Scope:** Frontend (CostEntryWizard.vue)
|
||
|
||
### 🔧 Módosított fájlok
|
||
|
||
#### 1. Frontend: [`CostEntryWizard.vue`](frontend_app/src/components/cost/CostEntryWizard.vue)
|
||
|
||
**P0 EPIC 1: Global Vehicle Selection**
|
||
- `useVehicleStore` importálva, `fetchUserVehicles()` metódus hozzáadva
|
||
- `selectedVehicleId` ref + `userVehicles` ref a járműlista kezelésére
|
||
- `effectiveVehicle` computed: prioritás szerint oldja fel a járművet (prop > dropdown)
|
||
- Template: `<select>` dropdown a felhasználó járműveivel, fallback read-only display ha vehicle prop van
|
||
|
||
**P0 EPIC 2: Grouped Categories (Subcategories)**
|
||
- `SubCategoryGroup` interface a csoportosított struktúrához
|
||
- `groupedSubCategories` computed: a subCategories flat listát csoportosítja parent category név alapján
|
||
- Template: `<optgroup>`-ok a subcategory select-ben, logikai csoportosítással
|
||
- `onMainCategoryChange()`: kiszűri a kiválasztott főkategória al-kategóriáit
|
||
|
||
**P0 EPIC 3: Universal Odometer with Hint**
|
||
- `odometerHint` ref + `odometerLoading` ref a hint állapot kezelésére
|
||
- `fetchLastOdometer()`: a `/assets/vehicles/{id}` végpontról lekéri a `current_mileage`-t
|
||
- Template: mindig látható km óra input + hint "Előző állás: X km"
|
||
- `watch(effectiveVehicle)` triggeli a `fetchLastOdometer()`-t járműváltáskor
|
||
- `isFuelCategory()`: backend FUEL_CATEGORY_IDS alapján detektálja az üzemanyag kategóriákat
|
||
- Liters input csak fuel kategóriáknál jelenik meg
|
||
|
||
**P0 EPIC 4: ProviderAutocomplete Integration**
|
||
- Már helyesen integrálva volt: `v-model`, `@select`, `@clear` események
|
||
- `handleSubmit()`: P0 Hybrid Vendor Refactor szerint source-aware vendor field-eket küld
|
||
- `verified_org` → `vendor_organization_id`
|
||
- `crowd_added` → `service_provider_id`
|
||
- raw string → `external_vendor_name`
|
||
|
||
**Payload kompatibilitás:**
|
||
- `asset_id`, `category_id`, `amount_gross` (GROSS-FIRST), `currency`, `date`, `mileage_at_cost`
|
||
- `vendor_organization_id`, `service_provider_id`, `external_vendor_name` root szinten
|
||
- `data` JSONB: `net_amount`, `vat_rate`, `invoice_number`, `invoice_date`, `payment_method`, `payment_deadline`, `liters`, `mileage_at_cost`, vendor meta
|
||
|
||
### ✅ Verifikáció
|
||
1. **Vite build** → `CostEntryWizard-KIg7Uvhm.js` (31.25 kB) sikeresen lefordult, 0 error
|
||
2. **Template** → mind a 4 P0 EPIC feature implementálva
|
||
3. **Script** → handleSubmit, resetForm, editCost hydration, lifecycle hooks teljesek
|
||
|
||
## P0 EPIC - Enterprise Taxonomy Seed Script (Gitea #407 kiegészítés)
|
||
|
||
### 📋 Új fájl: [`seed_expertise_enterprise.py`](backend/app/scripts/seed_expertise_enterprise.py)
|
||
|
||
**Cél:** Enterprise Taxonomy (Pénzügy, Biztosítás, Hatóságok) beszúrása a `marketplace.expertise_tags` táblába.
|
||
|
||
**Beszúrt 9 új tag (IDs 769-777):**
|
||
|
||
#### Level 1: Pénzügy és Biztosítás (Finance & Insurance) → ID 769
|
||
- Level 2: Gépjármű biztosító (Vehicle Insurance) → ID 770
|
||
- Level 2: Lízing és Finanszírozás (Leasing & Financing) → ID 771
|
||
- Level 2: Bank és Hitelintézet (Bank & Credit Institution) → ID 772
|
||
|
||
#### Level 1: Hatóságok és Közigazgatás (Authorities & Administration) → ID 773
|
||
- Level 2: Önkormányzat (Municipality) → ID 774
|
||
- Level 2: Állami Kincstár / Nemzeti Adóhatóság (State Treasury / Tax Authority) → ID 775
|
||
- Level 2: Közlekedési Hatóság / Kormányablak (Transport Authority) → ID 776
|
||
- Level 2: Útdíj / Autópálya Kezelő (Toll & Highway Operator) → ID 777
|
||
|
||
### ✅ Verifikáció
|
||
1. **Seed script futtatás:** `docker compose exec sf_api python3 /app/backend/app/scripts/seed_expertise_enterprise.py` → sikeres, 9 új sor beszúrva
|
||
2. **Adatbázis ellenőrzés:** `SELECT count(*) FROM marketplace.expertise_tags;` → 145 total record
|
||
3. **Vite build:** `docker compose exec sf_public_frontend npm run build` → 6.80s, 0 error, CostEntryWizard chunk 31.25 kB
|
||
|
||
---
|
||
|
||
## P0 EPIC - Seed Insurance Providers
|
||
|
||
**Dátum:** 2026-07-11
|
||
**Scope:** Backend (Seed Script)
|
||
|
||
### 🔧 Létrehozott fájl
|
||
|
||
#### 1. [`seed_insurance_providers.py`](backend/app/scripts/seed_insurance_providers.py)
|
||
- **33 magyar biztosító** seedelése a marketplace ökoszisztémába
|
||
- Adatforrás: Netrisk nyílt adatok (Alfa, Allianz, Generali, K&H, stb.)
|
||
- **Modellek:** `ServiceProvider` → `ServiceProfile` → `ServiceExpertise`
|
||
- Minden biztosító linkelve a **"Gépjármű biztosító"** expertise taghez (key=`gepjarmu_biztosito`, ID=770)
|
||
- `status=approved`, `source=api_import`, `validation_score=100`
|
||
- JSONB `raw_data` mezőben: Cégjegyzékszám, Bankszámla, Alaptőke, Tulajdonos, Kárrendezés adatok
|
||
- Duplikációszűrés név alapján
|
||
|
||
### ✅ Verifikáció
|
||
1. **Adatbázis:** 33 rekord a `marketplace.service_providers` táblában, mind `status=approved`, `source=api_import`
|
||
2. **API Search:** `GET /api/v1/providers/search?query=biztosito` visszaadja a biztosítókat (pl. Allianz, Generali, Groupama)
|
||
3. **ServiceProfile** és **ServiceExpertise** kapcsolatok létrejöttek minden providerhez
|