Files
service-finder/.roo/history.md
2026-07-20 11:17:52 +00:00

334 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### ✅ 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