garázs fejlesztés

This commit is contained in:
Roo
2026-07-20 11:17:52 +00:00
parent 2e0abc62a7
commit 4594de6c11
80 changed files with 9135 additions and 8702 deletions

View File

@@ -1,281 +1,333 @@
# Service Finder Fejlesztési Történet
### ✅ 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ó
## 2026-06-21 - P0 Deep Audit: Database Consistency & Zombie API Hunt
---
### 🎯 Cél
...
## P0 EPIC - UI/UX Overhaul for Quick Fuel Logging (Gitea #404)
## 2026-07-07 - P0 ServiceProvider AddressManager Integration
**Dátum:** 2026-07-09
**Scope:** Backend + Frontend (SimpleFuelModal, ProviderAutocomplete, ProviderQuickAddModal)
### 🎯 Cél
...
### 🔧 Módosított fájlok
#### 2. Seed script létrehozása és futtatása
A `seed_dismantler_category.py` szkript létrehozva (`backend/app/scripts/`), amely idempotens módon beszúrja az "Autóbontó / Használt alkatrész" kategóriát.
#### 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. **Seed script futtatva** → ✅ Sikeresen beszúrva az 'Autóbontó / Használt alkatrész' kategória (ID: 767). Összes rekord: 135.
2. **admin_providers.py szintaxis ellenőrzés**`import app.api.v1.endpoints.admin_providers` sikeres (✅ module imports OK)
3. **sync_engine futtatva** → 1282 OK, 0 issue, schema fully in sync
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
## 2026-07-07 - P0 BUGFIX: Live Provider Advanced Fields & Gas Station Categories
---
### 🎯 Cél
P0 kritikus hibajavítás: Live Provider (ID 6, MOL Fót) `opening_hours` és `category_ids` mezői elvesztek a backend `update_provider()` végpontban, mert a ServiceProvider-hez nem tartozott ServiceProfile rekord. Emellett a "Shop / Kisbolt" kategória hiányzott a rendszerből.
## P0 BUGFIX: SimpleFuelModal 500 POST crash + 5 frontend issues (Gitea #405)
### 🔧 Változtatások
**Dátum:** 2026-07-10
**Scope:** Backend (expenses.py, provider_service.py) + Frontend (SimpleFuelModal.vue)
#### 1. Backend fix: [`admin_providers.py`](backend/app/api/v1/endpoints/admin_providers.py:1571)
- **P0 BUGFIX (2026-07-07):** Ha egy Live Provider-hez nem létezik ServiceProfile, de a PATCH kérésben advanced mezők (`opening_hours`, `specializations`, `supported_vehicle_classes`, `category_ids`) érkeznek, a backend MOST automatikusan létrehoz egy ServiceProfile rekordot.
- **Response serialization fix:** A `category_ids` visszaadása előtt a rendszer lekérdezi a tényleges DB állapotot a `ServiceExpertise` táblából (`resolved_category_ids`), így a frontend mindig a valóban elmentett adatokat kapja vissza.
### 🔍 Root Cause Analysis: 500 POST /expenses/ crash
#### 2. Seed script: [`seed_gas_station.py`](backend/app/scripts/seed_gas_station.py)
- Létrehozva a hiányzó "Shop / Kisbolt" (Convenience Store) kategória (ID: 768) az "Üzemanyag és Töltőállomás" (ID=755) szülő alá.
- Meglévő kategóriák (ID=755, 756, 757) érintetlenek.
**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. **Seed script futtatva** → ✅ 'Shop / Kisbolt' (ID: 768) már létezik. Összes rekord: 136.
2. **sync_engine futtatva** → 1282 OK, 0 issue, schema fully in sync
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
## 2026-07-07 - P0 CRITICAL HOTFIX: Promotion Engine v2
---
### 🎯 Cél
Javítani a Promotion Engine-t a `PATCH /admin/providers/{id}` végponton, amely ServiceStaging rekordok jóváhagyásakor nem futott le.
## P0 BUGFIX - Odometer Source of Truth & Sync (Gitea #406)
### 🔍 Root Cause Analysis
1. **Trigger condition too fragile**: Az `if is_staging and new_status == "approved":` feltétel a `new_status` változóra támaszkodott, amely a `update_fields.get("status")`-ból származott. Ha a státusz nem volt explicit átadva a PATCH payload-ban (pl. csak `name` mezőt módosítottak), `new_status` `None` volt, és a promotion blokk teljesen kimaradt.
2. **ID handling bug**: Promotion után a `provider_obj` átállításra került az új ServiceProvider-re, de az AuditLog (`target_id=str(provider_id)`) és a ServiceProfile lekérdezés (`ServiceProfile.service_provider_id == provider_id`) továbbra is a régi staging ID-t (pl. 1754) használta, nem az új provider ID-t.
**Dátum:** 2026-07-10
**Scope:** Frontend (SimpleFuelModal.vue)
### 🔧 Javítások
1. **Trigger condition → `effective_status`**: A feltétel most a `provider_obj.status`-t (tényleges DB állapot) ellenőrzi közvetlenül, nem a `new_status` payload változót. Ez biztosítja, hogy a promotion akkor is lefusson, ha a státusz már "approved" volt a DB-ben, vagy ha nem volt explicit átadva.
2. **ID fix**: Minden `provider_id` hivatkozás (AuditLog, ServiceProfile lookup) `provider_obj.id`-ra lett cserélve, ami promotion után az új ServiceProvider ID-ját tartalmazza.
3. **Logolás**: `logger.error("P0 PROMOTION TRIGGERED")` hozzáadva a promotion blokk elejére a Docker logokban való trace-elhetőségért.
### 🔧 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. **Szintaxis ellenőrzés**`py_compile` sikeres
2. **Konténer újraindítás**`docker compose restart sf_api` sikeres
3. **API elérhető**✅ Uvicorn fut a 8000-es porton
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
## 2026-07-07 - DDD Database Refactoring 1.0 (P0 - 3 Phase)
---
### 🎯 Cél
DDD Database Refactoring 1.0 három fázisban: address_id FK bevezetése, admin_providers.py egyszerűsítése, frontend cleanup.
## P0 EPIC - CostEntryWizard UI/UX Parity & Categories (Gitea #407)
### 🔧 Változtatások
**Dátum:** 2026-07-10
**Scope:** Frontend (CostEntryWizard.vue)
#### #396 - Phase 1: address_id FK (Already Complete)
- `ServiceProvider.address_id` és `address_rel` már létezett a modellben
- Mind a 7 provider rekord rendelkezik `address_id`-vel
- `sync_engine` 1282 OK, 0 drift - nincs teendő
### 🔧 Módosított fájlok
#### #397 - Phase 2: admin_providers.py Simplify
- `_build_unified_providers_query()`: `ServiceProvider.address_id` hozzáadva a SELECT-hez (12. oszlop)
- `ServiceStaging` és `Organization` UNION ágak: `literal(None).label("address_id")` placeholder a column parityért
- `list_providers()`: `address_detail` feloldása `AddressManager.get_normalized()` segítségével minden sorban
- Ellenőrizve: `list_providers` endpoint minden provider esetén visszaadja az `address_detail`-t
#### 1. Frontend: [`CostEntryWizard.vue`](frontend_app/src/components/cost/CostEntryWizard.vue)
#### #398 - Phase 3: Frontend Cleanup
- `garages/index.vue` line 157: `garage.city` fallback eltávolítva → csak `garage.address_detail?.city`
- `providers/index.vue` line 137: már `address_detail`-t használ (nem kellett módosítani)
- `providers/[id]/index.vue` lines 144-166: már `address_detail`-t használ (nem kellett módosítani)
- `providers/[id]/edit.vue` lines 1133-1148: már `data.address_detail`-t használ (nem kellett módosítani)
**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. **#397 API teszt** → ✅ `list_providers` endpoint 5 provider-t ad vissza `address_detail` mezővel
2. **#398 Frontend regex audit** → ✅ Nincs több `provider.address` vagy `provider.city` flat field referencia
3. **Konténer újraindítás**`docker compose restart sf_api` sikeres
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
## 2026-07-07 - P0 BUGFIX: Category Sync & Parent-Child UX Automation
## P0 EPIC - Enterprise Taxonomy Seed Script (Gitea #407 kiegészítés)
### 🎯 Cél
A provider admin felület kategória szinkronizációjának javítása legacy provider-eknél (ahol a ServiceProfile `organization_id`-n keresztül kapcsolódik), valamint a frontend kategória-választó UX automatizálása.
### 📋 Új fájl: [`seed_expertise_enterprise.py`](backend/app/scripts/seed_expertise_enterprise.py)
### 🔧 Változtatások
**Cél:** Enterprise Taxonomy (Pénzügy, Biztosítás, Hatóságok) beszúrása a `marketplace.expertise_tags` táblába.
#### Backend: `backend/app/api/v1/endpoints/admin_providers.py`
- **`get_provider_detail`** (line 615-618): ServiceProfile lekérdezés kiterjesztve OR-ra: `(ServiceProfile.service_provider_id == provider_id) | (ServiceProfile.organization_id == provider_id)`
- **`update_provider`** (line 1575-1578): Ugyanez a javítás a PATCH végpontban, `provider_obj.id`-t használva
- **Root Cause**: Legacy provider-eknél (pl. ID 6) a ServiceProfile `organization_id` mezőn keresztül kapcsolódik, nem `service_provider_id`-n. Az egyszerű FK keresés miatt a profil nem található -> `category_ids` elveszett a válaszból és a mentésből.
**Beszúrt 9 új tag (IDs 769-777):**
#### Frontend: `frontend_admin/pages/providers/[id]/edit.vue`
- **Import** (line 626): `watch` hozzáadva a Vue import-hoz
- **`parentLookupMap`** (line 942): Flat Map<number, number> a kategória fa rekurzív bejárásához
- **`buildParentLookupMap()`** (line 944): Rekurzív fa bejáró, ami minden gyermek node-hoz hozzárendeli a szülő ID-t
- **`watch(categoryTree, ...)`** (line 956): A map újraépítése minden fa betöltéskor
- **`toggleCategory()`** (line 961): Gyermek kategória kiválasztásakor automatikusan hozzáadja a szülő kategória ID-t is a `form.category_ids` tömbhöz
#### 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. **Backend szintaxis** -> OK: `admin_providers` modul sikeresen betöltve a konténerben
2. **Sync Engine** -> OK: 1282 OK, 0 hiba, adatbázis szinkronban
3. **Frontend** -> OK: Import és `toggleCategory` módosítva, `watch` + `parentLookupMap` beépítve
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
## 2026-07-07 - P0 BUGFIX: ServiceProfile Auto-Creation on PATCH for New Providers
---
### 🎯 Cél
P0 kritikus hibajavítás: `PATCH /admin/providers/{id}` 500-as hibát dobott, ha a provider-hez nem tartozott ServiceProfile rekord. A `_sync_service_expertises()` hívás `profile.id`-t használt, de `profile` `None` maradt, mert a ServiceProfile létrehozás feltételes volt (`has_advanced_fields`).
## P0 EPIC - Seed Insurance Providers
### 🔧 Változtatások
**Dátum:** 2026-07-11
**Scope:** Backend (Seed Script)
#### 1. Import fix: [`admin_providers.py`](backend/app/api/v1/endpoints/admin_providers.py:48)
- `ServiceExpertise` hozzáadva az import-hoz (hiányzott, `NameError`-t okozott futásidőben)
### 🔧 Létrehozott fájl
#### 2. ServiceProfile unconditional creation: [`admin_providers.py`](backend/app/api/v1/endpoints/admin_providers.py:1588)
- **Előtte:** ServiceProfile csak akkor jött létre, ha `has_advanced_fields` True volt
- **Utána:** Ha nincs ServiceProfile, MINDIG létrejön egy alapértelmezett profil (fingerprint: `auto-created-{provider_id}`, status: `active`, trust_score: 30)
- Debug logolás hozzáadva (`print` + `logger.info`) a sync hívás előtt
#### 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. **Sync Engine** -> OK: 1282 OK, 0 hiba, adatbázis szinkronban
2. **E2E teszt** -> ✅ PATCH 200 SUCCESS: provider #21 (Test Provider No Address - Fixed v4) sikeresen frissítve `category_ids=[12, 13, 14]`-gyel
3. **Docker log** -> ✅ `P0 BUGFIX: Auto-created ServiceProfile #24 for Live provider #21`
## 2026-07-07 - P0 HOTFIX: Edit Form Chips & Reactivity Deep Clone
### 🎯 Cél
P0 kritikus hibajavítás: A provider edit űrlapon a kiválasztott kategória chipek nyers ID-kat jelenítettek meg (#760) név helyett, és a "Mentés" gomb disabled maradt kategória toggle után.
### 🔍 Root Cause Analysis
1. **Chip Display**: A template már `{{ getCategoryName(id) }}`-t használt (line 243), és a `useCategories` composable `getCategoryName()` függvénye helyesen visszaadja a nevet, ha a `categoryMap` be van töltve. A `#760` fallback akkor jelenik meg, ha a kategóriák még nem töltődtek be — ez normális átmeneti állapot.
2. **Reactivity Bug (Fő probléma)**: A `mapProviderToForm()` függvényben a `form.category_ids = data.category_ids || []` direkt referenciát másolt, nem deep clone-t. Mivel a `form.category_ids` és a `provider.value.category_ids` ugyanarra a tömbre mutattak, a `hasChanges` computed property sosem érzékelte a változást — a kategória toggle mindkét referenciát egyszerre módosította.
### 🔧 Változtatások
#### [`frontend_admin/pages/providers/[id]/edit.vue`](frontend_admin/pages/providers/[id]/edit.vue:1137)
- **`mapProviderToForm()`** (lines 1137-1148): Minden tömb értékadás deep clone-ra javítva:
- `form.supported_vehicle_classes = [...(data.supported_vehicle_classes || [])]`
- `form.category_ids = [...(data.category_ids || [])]`
- `form.specializations.brands = Array.isArray(spec.brands) ? [...spec.brands] : []`
- `form.specializations.propulsion = Array.isArray(spec.propulsion) ? [...spec.propulsion] : []`
### ✅ Verifikáció
1. **Deep clone ellenőrzés**: `form.category_ids` és `provider.value.category_ids` most külön referenciák — a toggleCategory() hívás után a `hasChanges` helyesen `true`-t ad vissza.
2. **Chip display**: A template már `{{ getCategoryName(id) }}`-t használ (line 243), a `useCategories.getCategoryName()` pedig a `categoryMap`-ből adja vissza a nevet, vagy `#${id}` fallback-et, ha még nem töltődött be.
## 2026-07-07 - P0 EPIC: i18n JSONB Database Migration (Phase 1 & 2)
### 🎯 Cél
Architect által jóváhagyott i18n JSONB migrációs terv ([`plans/logic_spec_i18n_jsonb_migration_phase1.md`](plans/logic_spec_i18n_jsonb_migration_phase1.md)) első két fázisának implementálása: Séma bővítés (Phase 1) és Adatmigráció (Phase 2).
### 🔧 Változtatások
#### Phase 1: Schema Expansion (3 SQLAlchemy model)
1. **`backend/app/models/marketplace/service.py`** — `ExpertiseTag` osztály:
- `name_i18n: Mapped[dict] = mapped_column(JSONB, nullable=False, server_default=text("'{\"hu\": \"\"}'::jsonb"))`
- `description_i18n: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)`
- GIN index: `Index('idx_expertise_tags_name_i18n', 'name_i18n', postgresql_using='gin')`
2. **`backend/app/models/vehicle/vehicle_definitions.py`** — `BodyTypeDictionary` osztály:
- `name_i18n: Mapped[dict] = mapped_column(JSONB, nullable=False, server_default=text("'{\"hu\": \"\"}'::jsonb"))`
- GIN index: `Index('idx_dict_body_types_name_i18n', 'name_i18n', postgresql_using='gin')`
3. **`backend/app/models/core_logic.py`** — `ServiceCatalog` osztály:
- `name_i18n: Mapped[dict] = mapped_column(JSONB, nullable=False, server_default=text("'{\"hu\": \"\"}'::jsonb"))`
- `description_i18n: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)`
- GIN index: `Index('idx_service_catalog_name_i18n', 'name_i18n', postgresql_using='gin')`
#### Phase 2: Data Migration Script
4. **`backend/app/scripts/migrate_i18n_jsonb.py`** — Létrehozva:
- Aszinkron migrációs script, amely a régi `name_hu`/`name_en` oszlopokat JSONB struktúrába csomagolja
- Kezeli a `name_translations` meglévő JSONB mező merge-elését is
- `json.dumps()` használata az asyncpg JSONB kompatibilitás miatt
- Idempotens: `ON CONFLICT DO NOTHING` + meglévő adatok felülírása
### 📊 Migrációs Eredmények
| Tábla | Sorok | name_i18n | description_i18n |
|-------|-------|-----------|------------------|
| `marketplace.expertise_tags` | 136 | ✅ `{"hu": "...", "en": "..."}` | ✅ `{"hu": "..."}` |
| `vehicle.dict_body_types` | 16 | ✅ `{"hu": "Szedán", ...}` | ❌ N/A |
| `system.service_catalog` | 3 | ✅ `{"hu": "Adat Export"}` | ✅ `{"hu": "PDF és Excel..."}` |
### 🗄️ GIN Indexek
| Index | Tábla | Típus |
|-------|-------|-------|
| `idx_expertise_tags_name_i18n` | `marketplace.expertise_tags` | GIN on `name_i18n` |
| `idx_dict_body_types_name_i18n` | `vehicle.dict_body_types` | GIN on `name_i18n` |
| `idx_service_catalog_name_i18n` | `system.service_catalog` | GIN on `name_i18n` |
### ✅ Verifikáció
1. **sync_engine futtatva** → 1287 OK, 0 Fixed, 0 Shadow — schema fully in sync
2. **5 új JSONB oszlop** létrehozva a DB-ben (3× `name_i18n`, 2× `description_i18n`)
3. **3 GIN index** manuálisan létrehozva (sync_engine nem kezeli az indexeket)
4. **155 sor** sikeresen migrálva (136 expertise_tags + 16 dict_body_types + 3 service_catalog)
5. **Régi oszlopok** (`name_hu`, `name_en`, `name`, `description`) érintetlenek — backward compatibility biztosítva
## 2026-07-08 - P0 i18n Static Dictionary Cleanup (Admin Frontend)
### 🎯 Cél
Az admin frontend összes providers oldalának és a layout sidebar menüjének teljes i18n konverziója. Minden hardcoded magyar szöveg `$t()` / `t()` hívásokra cserélése, és a hiányzó fordítási kulcsok felvétele a `hu.json` és `en.json` fájlokba.
### 🔧 Változtatások
#### 1. Layout (`default.vue`)
- `menuGroups` array összes label-je i18n kulcsokra cserélve (`menu.center`, `menu.dashboard`, `menu.providers`, stb.)
- `pageTitle` computed property összes hardcoded string i18n kulcsokra cserélve
- Key fix: `menu.ledger``menu.points_ledger`, `gamification.users.detail``gamification.users.user_detail_title`
#### 2. Providers oldalak
- **index.vue** - Teljes konverzió: template + script (`statusLabel()`)
- **pending.vue** - Teljes konverzió: template + script (toast üzenetek)
- **rejected.vue** - Teljes konverzió: template + script (toast üzenetek)
- **[id]/index.vue** - Teljes konverzió: template (minden szekció, modal, drawer) + script (tabs, DAYS, statusLabel, statusDescription, vehicleClassLabel, validationTypeLabel, historyActionLabel, toast üzenetek)
- **[id]/edit.vue** - Teljes konverzió: template (minden tab, szekció, drawer) + script (tabs, VEHICLE_CLASSES, DAYS, copyWeekdays, saveForm toasts)
#### 3. Lokalizációs fájlok
- **hu.json** - ~200+ új providers kulcs (index, pending, rejected, detail, edit)
- **en.json** - ~200+ új providers kulcs angol fordítással
### ✅ Verifikáció
1. **Build**`npm run build` sikeres (sf_admin_frontend konténerben) ✅
2. **Nincs fordítási hiba** - minden használt i18n kulcs létezik a locale fájlokban ✅
## 2026-07-08 - P0 Architectural Upgrade: Modular i18n Locales Migration
### 🎯 Cél
Migrate the Nuxt admin frontend from monolithic i18n locale files (single `hu.json`, `en.json`) to Nuxt i18n Modular Locales (multiple domain-specific JSON files per locale).
### 🔧 Változtatások
1. **`frontend_admin/nuxt.config.ts`** (31. sor): Changed `langDir` from `'i18n/locales/'` to `'locales/'` to avoid path doubling with Nuxt i18n module's automatic `i18nDir` detection. Updated hu and en locale configs to use `files` array pattern instead of single `file`.
2. **`frontend_admin/i18n/locales/hu/common.json`** (CREATED): Extracted all `common.*` keys from old monolithic `hu.json` (loading, saving, error, retry, save, cancel, delete, edit, search, filter, status, permissions, etc.)
3. **`frontend_admin/i18n/locales/en/common.json`** (CREATED): English equivalents of all common.* keys.
4. **`frontend_admin/i18n/locales/hu/menu.json`** (CREATED): Extracted all `menu.*` keys (center, dashboard, providers, garages, users, finance, packages, gamification, system, etc.)
5. **`frontend_admin/i18n/locales/en/menu.json`** (CREATED): English equivalents of all menu.* keys.
6. **`frontend_admin/i18n/locales/hu/providers.json`** (UPDATED): Expanded from ~84 lines to ~280 lines with all provider-related keys from old monolithic file.
7. **`frontend_admin/i18n/locales/en/providers.json`** (UPDATED): English equivalents, expanded similarly.
8. **`frontend_admin/locales/hu.json`** (DELETED): Old monolithic Hungarian locale file (1003 lines).
9. **`frontend_admin/locales/en.json`** (DELETED): Old monolithic English locale file (1003 lines).
### 🔍 Megjegyzések
- A Nuxt i18n modul automatikusan detektálja az `i18n/` könyvtárat a projekt gyökerében, és azt használja `i18nDir`-ként. Ezért a `langDir`-t relatívan kell megadni ehhez a könyvtárhoz (`'locales/'`), nem pedig abszolút módon (`'i18n/locales/'`).
- A többi nyelv (de, fr, ro, cz, sk) továbbra is a régi `frontend_admin/locales/` könyvtárban található monolit fájlokat használja.
- A konténer újraindítás után sikeresen elindult, a Nitro szerver és a Vite klienst is felépítette.
- A `@intlify/message-compiler` `Invalid linked format` hibát dobott az `info@example.com` email placeholder értékben lévő `@` karakter miatt. A javítás: `{'@'}` Intlify szintaxis használata a `hu/providers.json` és `en/providers.json` fájlokban.
## 2026-07-08 - P0 CRITICAL i18n Audit & Repair (HU/EN) - Fix #6: Missing persons.json
### 🎯 Cél
A Nuxt i18n modul `computeLocaleHashes` függvénye `ENOENT` hibát dobott, mert a `persons.json` fájl nem létezett a `frontend_admin/i18n/locales/en/` és `hu/` könyvtárakban, pedig a `nuxt.config.ts` `files` tömbje hivatkozott rá.
### 🔧 Végrehajtott Javítások
1. **`frontend_admin/i18n/locales/en/persons.json`** (LÉTREHOZVA): Angol nyelvű személykezelő fordítások (3456 byte), 80+ kulcs a `persons.*` és `persons.details.*` névtérben.
2. **`frontend_admin/i18n/locales/hu/persons.json`** (LÉTREHOZVA): Magyar nyelvű személykezelő fordítások (3898 byte), azonos struktúrával.
### 📝 Technikai Részletek
- A `persons.*` kulcsokat a `pages/persons/index.vue` (lista nézet) és `pages/persons/[id]/index.vue` (részletek nézet) template-ek használják.
- A hiányzó fájl a `nuxt.config.ts` 24 moduláris fájljának egyike volt, amelyet a Fix #3 során adtunk hozzá a `files` tömbhöz, de a fájl ténylegesen nem létezett a lemezen.
- A `.nuxt` cache törlése (`sudo rm -rf frontend_admin/.nuxt/dev`) és a konténer újraindítása után a build sikeresen lefutott i18n hibák nélkül.
### ✅ Verifikáció
1. **Konténer újraindítva**`docker compose restart sf_admin_frontend`
2. **Build logok**`✔ Vite client built in 40ms`, `✔ Vite server built in 445ms`, `[nitro] ✔ Nuxt Nitro server built in 1526ms`
3. **i18n hibák** → Nincs `ERROR`, nincs `ENOENT`, nincs i18n compilation error ✅
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