frontend admin refakctorálás

This commit is contained in:
Roo
2026-07-08 08:03:57 +00:00
parent 07b59032ce
commit 2e0abc62a7
455 changed files with 14428 additions and 21651 deletions

View File

@@ -3,215 +3,279 @@
## 2026-06-21 - P0 Deep Audit: Database Consistency & Zombie API Hunt
### 🎯 Cél
Teljes körű adatbázis konzisztencia ellenőrzés és zombie API végpontok felderítése a `vehicle`, `finance`, `fleet_finance` sémákban, mielőtt a frontend fejlesztés elkezdődik.
...
### 🔧 Eredmények
**1. ADATBÁZIS TISZTASÁG:** ✅ PASS
### ✅ Verifikáció
- Backend Python szintaxis: OK
- Sync Engine: 1297 elem OK, 0 hiba, teljes szinkronban
- API Test 1: `PATCH is_always_open=True``{"always_open": true, "days": {"monday": {"open": "00:00", "close": "23:59"}, ...}}`
- API Test 2: `PATCH is_always_open=False``{"monday": {"open": "09:00", "close": "17:00"}}` (always_open metadata stripped) ✅
## 2026-07-01 - Unified Address API Refactoring (Gitea #386-#391)
## 2026-07-07 - P0 ServiceProvider AddressManager Integration
### 🎯 Cél
Teljes körű cím API egységesítés a backend összes endpointján. Az audit során 4 különböző címformátumot és 5 kritikus inkonzisztenciát azonosítottunk. A refaktoring 3 fázisban (P1/P2/P3) 6 Gitea kártyán keresztül valósult meg.
...
### 🔧 Eredmények
**1. Audit & Tervezés:**
- Audit dokumentum: `docs/address_api_consistency_audit.md`
- Logic spec: `plans/logic_spec_unified_address_refactoring.md`
- 6 Gitea kártya létrehozva (#386-#391)
**2. P1 #386 — Admin endpointok (admin.py, admin_persons.py):**
- `admin.py list_users`: Flat dict → `AddressOut.model_validate(address)` nested object
- `admin_persons.py`: `AddressResponse``AddressIn`/`AddressOut`, `PersonAddressUpdate` törölve
- 3 `AddressResponse(...)``AddressOut.model_validate(person.address)`
- Address update field mapping javítva (address_zip→zip, address_hrsz→parcel_id)
**3. P2 #387 — User endpointok (users.py, schemas/user.py):**
- `PersonUpdate`: 9 flat `address_*` field → `address: Optional[AddressIn]`
- `_build_user_response`: Manual dict → `AddressOut.model_validate(person.address)`
- `.dict()``.model_dump()` (Pydantic v2)
**4. P2 #388 — Admin providers (admin_providers.py):**
- `ProviderDetail`: Flat fields → `address_detail: Optional[AddressOut]`
- `ProviderUpdateInput`: Flat fields → `address_detail: Optional[AddressIn]`
- STEP 0: address_detail extraction + flat field mapping (zip→address_zip, city→city, stb.)
**5. P2 #389 — Admin Organizations (admin_organizations.py):**
- `GarageDetailsResponse`: 15 flat address fields → `address_detail: Optional[AddressOut]`, `billing_address_detail: Optional[AddressOut]`, `notification_address_detail: Optional[AddressOut]` (3 AddressOut nested objects)
- `OrganizationUpdate`: 15 flat address fields → `address_detail: Optional[AddressIn]`, `billing_address_detail: Optional[AddressIn]`, `notification_address_detail: Optional[AddressIn]` (3 AddressIn nested objects)
- `get_org_detail`: AddressOut construction from denormalized org fields for all 3 address types
- `update_organization`: STEP 0 with 3 address_detail extraction + flat field mapping blocks (primary/billing/notification)
- Response includes `address_detail` as unified AddressOut object
**6. P3 #390 — Public providers (schemas/provider.py, provider_service.py):**
- `ProviderSearchResult`: `address_detail: Optional[AddressOut]`
- `ProviderQuickAddIn`: `address_detail: Optional[AddressIn]`
- `ProviderUpdateIn`: `address_detail: Optional[AddressIn]`
- `search_providers`: AddressOut construction from flat SQL columns
**7. P3 #391 — Gamification (gamification.py):**
- `submit_new_service` uses `city`/`address` as plain `Body()` string params
- Maps directly to `ServiceStaging.city`/`ServiceStaging.address_line1`
- Simple crowdsourcing — AddressIn conversion would be over-engineering
**8. Phase 4 — AddressResponse removal:**
- `AddressResponse` class completely removed from `schemas/user.py`
- `PersonResponse.address`: `Optional[AddressResponse]``Optional[AddressOut]`
#### 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.
### ✅ Verifikáció
- Backend Python szintaxis: OK (all files pass py_compile)
- Sync Engine: 1274 OK, 0 Fixed, 26 Shadow Data (pre-existing)
- All 6 Gitea cards (#386-#391): CLOSED ✅
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
## 2026-07-01 - Service Provider Auto-Discovery (Gitea #348, #347)
## 2026-07-07 - P0 BUGFIX: Live Provider Advanced Fields & Gas Station Categories
### 🎯 Cél
Implementálni a `find_or_create_provider_by_name()` service függvényt (#348) és az auto-discovery hook-ot az expense creation-ben (#347) a logic_spec alapján.
### 🔧 Eredmények
**1. find_or_create_provider_by_name() (#348):**
- **Exact match:** ILIKE keresés a ServiceProvider.name mezőn
- **Fuzzy match:** pg_trgm similarity > 0.3 threshold, legjobb találat kiválasztása
- **Create new:** Ha nincs találat, új ServiceProvider létrehozása `pending` státusszal, `source=SourceType.api_import` ("api_import"), `validation_score=0`
- **Gamification:** PROVIDER_DISCOVERY (300 XP), PROVIDER_CONFIRMATION (150 XP), PROVIDER_VERIFIED_USE (100 XP), USE_UNVERIFIED_PROVIDER (200 XP)
**2. Auto-discovery hook expense creation-ben (#347):**
- Már implementálva volt a `POST /expenses/` végpontban
- Ha `external_vendor_name` meg van adva, de `service_provider_id` nincs, meghívja a `find_or_create_provider_by_name()`-t
- Hiba esetén warning log, nem blokkolja a költség létrehozását
**3. Adatbázis módosítások:**
- `pg_trgm` extension telepítve
- `marketplace.source_type` enum kiegészítve `'api_import'` értékkel
### ✅ Verifikáció
- Sync Engine: 1297 elem OK, 0 hiba, teljes szinkronban
- Test `test_use_unverified_provider.py`: ALL TESTS PASSED ✅
- PROVIDER_DISCOVERY (300 XP): ✅ User A created provider
- PROVIDER_CONFIRMATION (150 XP): ✅ User A reused own provider
- USE_UNVERIFIED_PROVIDER (200 XP): ✅ User B used pending provider
## 2026-07-01 - P0 ARCHITECTURE CLEANUP: Final Address Refactor (Ghost Columns & AddressManager)
### 🎯 Cél
A `docs/p0_address_manager_usage_audit_2026-07-01.md` auditban feltárt P0 kritikus hiba és P1 hiányosságok javítása.
### 🔧 Eredmények
**1. P0 FIX — `admin.py:search_organizations_by_name`:**
- `Organization.address_city``GeoPostalCode.city` (JOIN `Address``GeoPostalCode`)
- Hozzáadva: `outerjoin(Address, Organization.address_id == Address.id)`
- Hozzáadva: `outerjoin(GeoPostalCode, Address.postal_code_id == GeoPostalCode.id)`
- Változás: `row.address_city``row.city` a response builderben
**2. P1 FIX — `admin_persons.py:update_person`:**
- 50+ sor direkt Address mező írás → egyetlen `AddressManager.create_or_update()` hívás
- Import hozzáadva: `from app.services.address_manager import AddressManager`
**3. P1 FIX — `admin_organizations.py:list_organizations`:**
- `org.address_city``org.address.city if org.address else None` (relationship-based)
**4. P1 FIX — `OrganizationUpdate` séma:**
- Minden flat address field `[DEPRECATED]` prefixet kapott a description-ben
**5. SQL Cleanup Script:**
- `docs/sql/cleanup_ghost_columns_2026_07.sql` — 15 DROP COLUMN utasítás `IF EXISTS` védelemmel
### ✅ Verifikáció
- Backend Python szintaxis: OK (all 3 files pass py_compile)
- Import verifikáció: `Organization.address_id`=True, `Organization.address_city`=False (confirmed removed)
- Sync Engine: 1274 OK, 0 Fixed, 26 Shadow Data (pre-existing ghost columns confirmed)
## 2026-07-01 - P0 CRITICAL HOTFIX: Fix 500 Error on /admin/providers List View
### 🎯 Cél
A GET /api/v1/admin/providers végpont 500-as hibát dobott a denormalizált cím oszlopok eltávolítása után.
### 🔧 Root Cause & Fix
**Root Cause 1 — Wrong ServiceStaging import:**
Az `admin_providers.py` a `ServiceStaging`-et az `app.models.marketplace.service` modulból importálta, amely NEM tartalmazza a `full_address` és `city` mezőket. A helyes import az `app.models.marketplace.staged_data` modulból származik, amely rendelkezik ezekkel a mezőkkel.
**Root Cause 2 — AddressOut missing required `id` field:**
A `get_provider_detail` és `update_provider` végpontok `AddressOut(...)`-ot konstruáltak a ServiceProvider flat address mezőiből, de az `AddressOut` séma `id: uuid.UUID` mezője kötelező (nem Optional). Mivel a ServiceProvider nem rendelkezik Address kapcsolattal, az `address_detail` mezőt `None`-ra kell állítani.
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.
### 🔧 Változtatások
1. **`backend/app/api/v1/endpoints/admin_providers.py:41`** — Import csere: `from app.models.marketplace.service import ServiceStaging``from app.models.marketplace.staged_data import ServiceStaging`
2. **`backend/app/api/v1/endpoints/admin_providers.py:568`** — `address_detail=AddressOut(...)``address_detail=None` (get_provider_detail)
3. **`backend/app/api/v1/endpoints/admin_providers.py:1340`** — `address_detail=AddressOut(...)``address_detail=None` (update_provider)
#### 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.
#### 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.
### ✅ Verifikáció
- `GET /api/v1/admin/providers` (default): 200 ✅ (50 items)
- `GET /api/v1/admin/providers?status=pending`: 200 ✅ (50 items)
- `GET /api/v1/admin/providers/4`: 200 ✅ (address_detail=None)
- Sorting by name/validation_score/status: 200 ✅
- Search: 200 ✅ (4 results)
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
## 2026-07-01 - P0 BUGFIX: Organization Details 500 Error & Provider Raw Data
## 2026-07-07 - P0 CRITICAL HOTFIX: Promotion Engine v2
### 🎯 Cél
Két P0 hibajavítás:
1. **Organization Details 500 error** - A `fleet.organizations` táblából fizikailag eltávolított denormalizált cím oszlopok (15 db: address_city, billing_zip, notification_street_name, stb.) miatt a `PUT /{org_id}` végpont `setattr`-nál 500-as hibát dobott, ha a frontend elküldte ezeket a mezőket.
2. **Provider Raw Data hiány** - A `GET /admin/providers/{id}` végpont ServiceProvider rekordoknál nem adta vissza a `raw_data` mezőt, így a frontend "Nyers adatok" tab üresen/mock adatként jelent meg.
Javítani a Promotion Engine-t a `PATCH /admin/providers/{id}` végponton, amely ServiceStaging rekordok jóváhagyásakor nem futott le.
### 🔧 Eredmények
### 🔍 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.
**1. `admin_organizations.py` - Deprecated address field filter** (1341-1361. sor)
- Bevezetve a `DEPRECATED_ADDRESS_FIELDS` halmaz, amely tartalmazza mind a 15 ghost oszlopot
- A `update_organization` függvény `setattr` ciklusa előtt ezek a mezők kiszűrésre kerülnek `logger.warning` kíséretében
- A GET végpont (`get_organization_details`) már korábban is helyesen, relationship-ekből töltötte a denormalizált mezőket (1114-1139. sor)
**2. `admin_providers.py` - Raw data hozzáadása ServiceProvider válaszhoz** (589-591. sor)
- A `get_provider_detail` ServiceProvider ágához hozzáadva: `raw_data=sp.raw_data or {}`
- Ezzel a frontend `RawDataViewer` komponense valós adatokat kap, nem üres/mock állapotot
### 🔧 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.
### ✅ Verifikáció
- Mindkét fájl Python szintaxis ellenőrzése: **OK**
- `sync_engine.py` futtatás: **1274 elem OK, 0 hiba** - teljes szinkronban
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
---
## 2026-07-02: Bugfix - Provider validációs érték és research_in_progress státusz (Gitea #393)
## 2026-07-07 - DDD Database Refactoring 1.0 (P0 - 3 Phase)
### 🎯 Cél
Provider 1752 (Carnanny) esetén a validációs érték üres volt (validation_score=0), és a `research_in_progress` státusz nem jelent meg helyesen a frontend státuszablakában.
DDD Database Refactoring 1.0 három fázisban: address_id FK bevezetése, admin_providers.py egyszerűsítése, frontend cleanup.
### 🔧 Root Cause & Fix - 4 bug javítva
### 🔧 Változtatások
**Bug 1 - Backend: `validation_score` hardcoded to 0 for staging providers**
- **Hely:** [`admin_providers.py:613`](backend/app/api/v1/endpoints/admin_providers.py:613)
- **Ok:** A `get_provider_detail` ServiceStaging ágában `validation_score=0` volt keménykódolva
- **Fix:** `validation_score=ss.trust_score or 0` - így a trust_score értéke jelenik meg validation_score-ként
#### #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ő
**Bug 2 - Frontend: `research_in_progress` hiányzott a statusLabel/statusDescription függvényekből**
- **Hely:** [`index.vue:869-887`](frontend_admin/pages/providers/[id]/index.vue:869)
- **Ok:** A switch csak 'approved', 'pending', 'rejected' eseteket kezelte
- **Fix:** Hozzáadva: `case 'research_in_progress': return 'Kutatás folyamatban'` és magyarázó leírás
#### #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
**Bug 3 - Frontend: `research_in_progress` hiányzott a CSS computed property-kből**
- **Hely:** [`index.vue:774-808`](frontend_admin/pages/providers/[id]/index.vue:774)
- **Ok:** A `statusBannerClass`, `statusIconClass`, `statusTextClass`, `statusSubtextClass` nem kezelték ezt a státuszt → piros (elutasított) stílust kapott
- **Fix:** Mind a 4 computed property-hez hozzáadva a `research_in_progress` case kék színű (blue-500) Tailwind osztályokkal
**Bug 4 - Frontend: Moderációs műveletek rossz ágba kerültek**
- **Hely:** [`index.vue:283`](frontend_admin/pages/providers/[id]/index.vue:283)
- **Ok:** `v-if="provider.status === 'pending'"` csak a pending státuszt kezelte, a `research_in_progress` a `v-else` ágba esett ("elutasításra került")
- **Fix:** `v-if="provider.status === 'pending' || provider.status === 'research_in_progress'"` - így a research_in_progress státuszú provider-ek is látják az Approve/Reject gombokat
### 📊 Adatbázis megállapítás
- A `service_staging.trust_score` mező értéke 0 a DB-ben, de a `raw_data` JSON tartalmazza: `"trust_score": 20`
- Ez egy OSM scout bot enrichment pipeline hiányosság - a bot nem extractálja a `trust_score`-t a `raw_data`-ból a staging oszlopba
- A fix biztosítja, hogy AMIKOR a trust_score populálva lesz, az helyesen megjelenjen validation_score-ként
#### #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)
### ✅ Verifikáció
- Backend Python szintaxis: **OK**
- API konténer restart: **OK**
- API hívás provider 1752-re: **200 OK** - status=research_in_progress, validation_score=0 (trust_score=0 miatt), raw_data jelen van
- Frontend: mind a 4 Vue komponens módosítás érvényben
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
## 2026-07-07 - P0 BUGFIX: Category Sync & Parent-Child UX Automation
### 🎯 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.
### 🔧 Változtatások
#### 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.
#### 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
### ✅ 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
## 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`).
### 🔧 Változtatások
#### 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)
#### 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
### ✅ 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 ✅