# Service Finder Fejlesztési Történet ## 2026-06-21 - P0 Deep Audit: Database Consistency & Zombie API Hunt ### 🎯 Cél ... ## 2026-07-07 - P0 ServiceProvider AddressManager Integration ### 🎯 Cél ... #### 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ó 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-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. ### 🔧 Változtatások #### 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ó 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-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. ### 🔍 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. ### 🔧 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ó 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-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. ### 🔧 Változtatások #### #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ő #### #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 #### #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ó 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 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 ✅