# Service Finder Fejlesztési Történet ## 2026-06-14 - Admin Search Bugs Fix & Sticky UX ### 🎯 Cél Backend SQL hibák javítása a GET /admin/users végponton (Address outerjoin, phone search), valamint frontend UX fejlesztések (sticky bulk action bar, clear/X gomb, üres állapot üzenet, HeaderProfile az AdminLayout-ban). ### 🔧 Változtatások **1. BACKEND - [`admin.py`](backend/app/api/v1/endpoints/admin.py:502):** - **Kritikus SQL javítás:** A `selectinload` helyett explicit `outerjoin` + `contains_eager` használata a `Person` és `Address` táblákhoz. A `selectinload` külön query-ben tölti be a kapcsolódó adatokat, így a `WHERE` feltételek a `Person.phone`, `Person.first_name` stb. oszlopokra nem működtek (500-as hiba). - **Address keresés javítás:** Az `Address.zip` és `Address.city` Python property-k, nem DB oszlopok. A tényleges SQL lekérdezésben a `GeoPostalCode.zip_code` és `GeoPostalCode.city` oszlopokat kell használni. Ehhez egy 3. outerjoin került a `GeoPostalCode` táblára. - **Phone search:** A `query = query.where(Person.phone.ilike(...))` értékadás most már működik, mert a `Person` tábla explicit outerjoin-olva van. - **404 hiba:** Nem volt 404-es hiba az üres találatnál - a végpont helyesen 200 OK-val és üres listával tér vissza. **2. FRONTEND - [`AdminUsersView.vue`](frontend/src/views/admin/AdminUsersView.vue):** - **Sticky Bulk Action Bar:** `sticky top-0 z-20 bg-gray-800 shadow-lg` osztályok hozzáadva, hogy görgetésnél a táblázat tetején tapadjon. - **Clear/X gomb:** A kereső input mezőbe egy X gomb került (csak akkor látszik, ha van szöveg), ami üríti a `searchQuery`-t és alapállapotba állítja a listát. - **clearSearch() metódus:** Új metódus, ami nullázza a keresési paramétereket és újratölti a listát. - **Üres állapot:** Barátságos üzenet ikonnal és "Keresés törlése" gombbal, ha nincs találat. **3. FRONTEND - [`AdminLayout.vue`](frontend/src/layouts/AdminLayout.vue):** - **HeaderProfile komponens:** Importálva és elhelyezve a top bar jobb oldalán a `LanguageSwitcher` és `ModeSwitcher` mellett. Ezzel az admin felületen is látszik a felhasználó neve, profilba lépés és kilépés lehetősége. ### ✅ Eredmény - Python szintaxis ellenőrzés: OK - Backend SQL lekérdezések: outerjoin-ök helyesen használva, nincs 500-as hiba keresésnél - Frontend: sticky, clear gomb, üres állapot, HeaderProfile mind implementálva ## 2026-06-14 - 405 DELETE Debugging & Fix ### 🎯 Cél A `DELETE /api/v1/users/me` végpont 405 Method Not Allowed hibát dobott. Felderíteni a hiba okát és javítani. ### 🔍 Vizsgálat lépései **1. LÉPÉS - Végpont fizikai ellenőrzése ([`users.py`](backend/app/api/v1/endpoints/users.py:502)):** - A `@router.delete("/me", status_code=200)` dekorátor létezik, helyes útvonallal (`/me`, nincs perjel a végén) - A végpont implementálva van, meghívja az `AuthService.soft_delete_user()` függvényt - A router listában a DELETE `/me` regisztrálva van **2. LÉPÉS - CORS és Proxy ellenőrzése:** - CORS: `allow_methods=["*"]` - minden metódus engedélyezve - Nginx proxy: `location /api/` blokk minden kérést továbbít, nincs metódus-szűrés - Router bekötés: `users.router` a `/users` prefix alá kötve **3. LÉPÉS - Belső tesztelés:** - Közvetlen teszt a konténerben (`localhost:8000`, proxy-t megkerülve) szintén 405-öt adott - A konténer `--reload` nélkül indult, így a kód módosításai nem léptek életbe - **Megoldás:** `docker compose restart sf_api` után a DELETE `/me` **200 OK** választ adott ### ✅ Eredmény - A 405-ös hibát **nem a kód**, hanem a konténer újraindításának hiánya okozta - A `DELETE /api/v1/users/me` végpont a `tester_pro2@profibot.hu` fiókkal tesztelve **200 OK** választ ad - A soft-delete funkció helyesen anonimizálja az email címet és inaktiválja a felhasználót - Teszt után a `tester_pro2@profibot.hu` fiók visszaállításra került ## 2026-06-13 - Emergency Fix: Vehicle Creation 500 Error (FK Violation on catalog_id) ### 🎯 Cél A `POST /api/v1/assets/vehicles` végpont 500 Internal Server Error-t dobott új jármű rögzítésekor. A hiba oka az `AssetMatcherService` által visszaadott `VehicleModelDefinition.id` közvetlen hozzárendelése volt az `Asset.catalog_id` mezőhöz, amelynek FK-ja a `vehicle.vehicle_catalog` (`AssetCatalog`) táblára mutat, nem a `vehicle.vehicle_model_definitions` táblára. ### 🔧 Változtatások **1. `backend/app/services/asset_service.py` (228-255. sorok):** - A matcher integrációs blokkban a `new_asset.catalog_id = matched_def.id` sor kicserélve. - Az új logika először megkeresi a `VehicleModelDefinition`-hez tartozó `AssetCatalog` rekordot (`master_definition_id` alapján). - Ha nem létezik, automatikusan létrehozza a megfelelő `AssetCatalog` bejegyzést a definíció adataiból. - Csak ezután állítja be a `catalog_id`-t a valódi `AssetCatalog.id`-ra. ### ✅ Eredmény - A teszt `POST /api/v1/assets/vehicles` kérés `201 Created` státusszal tért vissza. - A jármű `catalog_id: 1312975` (AssetCatalog ID) értékkel jött létre, adatgazdagítással (`engine_capacity: 176`, `power_kw: 13`, `data_status: enriched`). - Nincs több ForeignKeyViolationError a logokban. ## 2026-06-08 - F5 Refresh Logout Bug Fix & HeaderLogo Contextual Navigation ### 🎯 Cél Két frontend hiba javítása: (1) F5 oldalfrissítéskor a Vue Router guard hamarabb fut le, mint a Pinia Auth Store init() befejeződik, ami kijelentkezést okoz. (2) A HeaderLogo komponens fixen a /dashboard-ra navigált, nem vette figyelembe a /organization/:id útvonalat. ### 🔧 Változtatások **1. `frontend/src/stores/auth.ts`:** - Új `isInitialized` ref állapot (alapértelmezett: `false`) hozzáadva a store state-hez. - Az `init()` függvény legvégén `isInitialized.value = true` beállítás, így a router guard meg tudja várni az inicializálás végét. - Az `isInitialized` exportálva a return objektumban. **2. `frontend/src/router/index.ts`:** - A `beforeEach` guard elején ellenőrzés: ha `!authStore.isInitialized`, akkor `await authStore.init()` meghívása. - Ezzel a router garantáltan megvárja a token kiolvasását és a user profil lekérését, mielőtt a `requiresAuth` ellenőrzést elvégezné. **3. `frontend/src/components/header/HeaderLogo.vue`:** - `useRoute()` importálva a Vue Router-ből. - Quick Action gombok: már nem néma hibásak, figyelmeztetnek ha nincs jármű - FUEL kategória: dinamikus feloldás API-ból, nem hardcoded ID - OdometerReading tábla létrehozva a `vehicle` sémában - CostCategory.min_tier oszlop hozzáadva `'free'` default értékkel - subscription_service.py elkészítve (Gitea #239) - AssetCost.document_id oszlop hozzáadva - 20 CostCategory rekord tier beállítva ## 2026-06-12 - Dynamic Parameters Check & Centralized Odometer Implementation ### 🎯 Cél 1. Rendszerparaméter-tábla vizsgálata (no hardcoding elv érvényesítése) 2. Odometer (kilométeróra) automatikus bekötése a költségrögzítésbe 3. Valós km állás visszaadása a frontend felé ### 🔍 1. Rendszerparaméterek Vizsgálata **Megállapítás:** A `system.system_parameters` tábla létezik és használatban van az alábbi struktúrával: - `key` (VARCHAR) - paraméter kulcs - `category` (VARCHAR) - kategória (pl. vehicle, finance, security) - `value` (JSONB) - érték (tetszőleges JSON struktúra) - `scope_level` (ENUM: global, organization, user) - hatókör - `scope_id` (VARCHAR) - opcionális hatókör azonosító - `is_active` (BOOLEAN) - aktív/inaktív flag **Példa bejegyzések:** `VEHICLE_DRAFT_MAX_EXPENSES`, `VEHICLE_DRAFT_MAX_DAYS`, `VEHICLE_LIMIT`, `EXCHANGE_RATE_EUR_HUF`, `auth_min_password_length`, stb. **Következtetés:** A 30 napos trial logika dinamikus paraméterezéséhez a `system.system_parameters` tábla használható. A trial periódus bevezetése előtt létre kell hozni egy `TRIAL_PERIOD_DAYS` kulcsú rekordot ebben a táblában. ### 🔧 2. Backend: Odometer bekötése (expenses.py) **Módosított fájlok:** - [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py) - Odometer integráció hozzáadva - [`backend/app/models/__init__.py`](backend/app/models/__init__.py) - `OdometerReading` export hozzáadva - [`backend/app/models/vehicle/__init__.py`](backend/app/models/vehicle/__init__.py) - `OdometerReading` export hozzáadva **Implementáció részletei:** - Az `AssetCostCreate` séma már tartalmazza a `mileage_at_cost` mezőt - A `create_expense` végpont most: 1. Létrehoz egy `OdometerReading` rekordot (`source="cost_entry"`, `cost_id=new_cost.id`) ha `mileage_at_cost` meg van adva 2. Frissíti az `Asset.current_mileage` mezőt, ha az új km állás nagyobb, mint a jelenlegi - Az `OdometerReading` modell már létezett a `vehicle.odometer_readings` táblában (korábban nem volt exportálva a modell `__init__`-ből) ### ✅ 3. Frontend / API: Valós Km állás Visszaadása **Megállapítás:** A teljes frontend-backend lánc már működik: - `AssetResponse.current_mileage` (Pydantic schema) - tartalmazza a mezőt - `GET /api/v1/assets/{id}` végpont - visszaadja a `current_mileage`-t - `VehicleData` TypeScript típus - definiálja a `current_mileage` mezőt - `VehicleDetailModal.vue` - "Alapadatok" fülön megjeleníti (`vehicle.current_mileage`) - `VehicleCardStandard.vue` - kártyanézetben is megjelenik ### 📊 Összefoglalás - A rendszerparaméterek dinamikus kezelése biztosított a `system.system_parameters` táblán keresztül - Az OdometerReading automatikusan rögzítésre kerül minden költségbejegyzésnél, ahol `mileage_at_cost` meg van adva - Az `Asset.current_mileage` mindig a legmagasabb rögzített km állást tükrözi - A frontend valós időben látja a frissített km állást ## 2026-06-12 - Auth Registration 500 Error Fix & Container Startup Repair ### Cél A `POST /api/v1/auth/register` végpont 500 Internal Server Error hibájának kijavítása. A hiba oka a PostgreSQL `audit.log_severity` enum és a Python `LogSeverity` enum közötti mismatch volt. ### Változtatások **1. `backend/app/services/auth_service.py`:** - `LogSeverity` enum import hozzáadva - `severity="INFO"` → `severity=LogSeverity.info` (line 149) - `severity="WARNING"` → `severity=LogSeverity.warning` (line 494) **2. `backend/app/models/vehicle/vehicle.py`:** - `CostCategory.__table_args__`: `extend_existing=True` hozzáadva - `VehicleUserRating.id`: `UUID` → `PG_UUID` javítás (NameError) **3. `backend/app/scripts/unified_db_sync.py`:** - `dynamic_import_models()` átírva: egyszerű `import app.models` **4. `backend/app/models/__init__.py`:** - Import sorrend javítva: Organization/Branch a Rating elé került ### Eredmény - `POST /api/v1/auth/register` → **201 Created** sikeres regisztráció - `GET /health` → **200 OK** (database connected) - Container stabilan fut, nincs restart loop ## 2026-06-12 - KYC Flow Bugfix & Repair (verify_email + Gamification commit) ### 🎯 Cél Két kritikus hiba javítása a KYC (Know Your Customer) folyamatban, ami miatt a `gy.krisztina76@gmail.com` felhasználó nem tudott aktiválódni. A light regisztráció → email verifikáció → KYC kitöltés után nem jöttek létre a szükséges adatbázis kapcsolatok (Organization, Wallet, Branch, OrgMember, UserStats). ### 🔧 Változtatások **1. `backend/app/services/auth_service.py` - `verify_email()` metódus (320-359. sor):** - **Bug:** A Person rekord keresése `Person.user_id == user.id` alapján történt, de `Person.user_id` NULL (ez egy külön back-reference FK, ami sosem lett beállítva regisztrációnál). - **Fix:** Módosítva `Person.id == user.person_id`-re, ami a `User.person_id` FK-n keresztül helyesen megtalálja a Person rekordot. - Plusz: `person.user_id = user.id` beállítás a back-reference konzisztencia fenntartásához. **2. `backend/app/services/gamification_service.py` - `award_points()` és `process_activity()` (20-123. sor):** - **Bug:** `process_activity()` belső `await db.commit()`-et hívott, ami konfliktusba került a `complete_kyc()` külső tranzakciójával. A gamification commit részlegesen elmentette az adatokat (Org, Wallet, stb.), de ha a külső metódusban hiba történt, a rollback már nem tudta visszavonni a commitált adatokat. - **Fix:** `commit: bool = True` paraméter hozzáadva mindkét metódushoz. Amikor `commit=False`, a metódus nem hív `db.commit()`-ot, így a hívó fél (pl. `complete_kyc()`) kezeli a tranzakciót atomikusan. **3. `backend/app/services/auth_service.py` - `complete_kyc()` (289. sor):** - `commit=False` átadva a `GamificationService.award_points()` hívásoknak, hogy ne legyen dupla commit. ### 🛠️ Repair Script - `backend/scripts/repair_kyc_user.py` - Létrehozva a meglévő `gy.krisztina76@gmail.com` (ID 100) user javítására: - Person aktiválása (`is_active=True`, `user_id=100`) - Organization létrehozása ("Gyöngyössy Flotta", ID 46) - Branch létrehozása ("Home Base") - OrganizationMember létrehozása (OWNER) - Wallet létrehozása (HUF) - UserStats létrehozása (500 XP KYC bónusz) - `user.scope_id` beállítása ### ✅ Eredmény - E2E teszt (`backend/scripts/test_kyc_e2e.py`) sikeresen lefutott: **6/6 teszt pass, 0 failed** - Teljes flow: Lite Registration → Email Verification → Login → KYC Complete → Infrastructure Verify - Minden szükséges adatbázis kapcsolat létrejön: Person, Organization, Branch, OrgMember, Wallet, UserStats, scope_id - A javított user (`gy.krisztina76@gmail.com`) adatai helyreállítva ## 2026-06-12 - Emergency Regression Fix: Vehicle Creation 500 Error (UnboundLocalError) ### 🎯 Cél Két kritikus végpont 500-as hibájának elhárítása a legutóbbi architekturális változtatások (Odometer, Trial) után: - `POST /api/v1/auth/register` - Ellenőrizve: nincs hardcoded Trial periódus, a regisztráció 201 Created választ ad - `POST /api/v1/assets/vehicles` - Javítva: Python `UnboundLocalError` a `select` változó scope-hibája miatt ### 🔧 Változtatások **1. [`backend/app/api/v1/endpoints/assets.py`](backend/app/api/v1/endpoints/assets.py:399) - UnboundLocalError javítás:** - **Bug:** A `create_or_claim_vehicle` függvényben a 399. sorban (`if org_id is None:` blokkban) volt egy `from sqlalchemy import select` lokális import. Python scope-szabályai miatt a `select` lokális változóként lett kezelve a teljes függvényben. Amikor a kódvégrehajtás a 358. sorban (`select(Asset).where(...)`) elérte a `select`-et, mielőtt a lokális import (399. sor) lefutott volna, Python `UnboundLocalError`-t dobott. - **Fix:** A duplikált `from sqlalchemy import select` eltávolítva a 399. sorból. A `select` már importálva van a fájl tetején (9. sor). **2. [`backend/app/services/asset_service.py`](backend/app/services/asset_service.py:599) - UserBadge `awarded_at` → `earned_at` javítás:** - **Bug:** A `_award_first_car_badge` metódus `awarded_at=datetime.utcnow()` paramétert használt, de a `UserBadge` modell mezőneve `earned_at` (lásd [`backend/app/models/gamification/gamification.py`](backend/app/models/gamification/gamification.py:89)). - **Fix:** `awarded_at` → `earned_at` átnevezés. Ez a hiba nem volt kritikus (az `except` blokk elnyelte), de ERROR szinten naplózódott. ### ✅ Eredmény - `POST /api/v1/auth/register` → **201 Created** (nem volt hibás, csak ellenőrizve) - `POST /api/v1/assets/vehicles` → **201 Created** (javítás után) - Teljes E2E flow tesztelve: Register → Verify Email → Complete KYC → Create Vehicle → minden **200/201** - Docker logokban nincs `error|500|traceback` a javítás után ## 2026-06-12 - B2B/B2C Context Switcher PLG Actions ### 🎯 Cél A Context Switcher ([`HeaderCompanySwitcher.vue`](frontend/src/components/header/HeaderCompanySwitcher.vue)) átalakítása Product-Led Growth (PLG) szemléletűvé. Amikor a felhasználónak nincs cége, a váltógomb ne egy üres "Céges nézetbe" vezessen, hanem két PLG akciógombot mutasson: "➕ Cég létrehozása" és "🔗 Csatlakozás céghez". Ezek a gombok akkor is láthatók, ha a felhasználónak már van cége. ### 🔧 Változtatások **1. [`frontend/src/components/header/HeaderCompanySwitcher.vue`](frontend/src/components/header/HeaderCompanySwitcher.vue):** - **Template:** Az üres állapot ("No companies yet") eltávolítva, helyette egy PLG Actions szekció került be, amely mindig látható a dropdownban. - Ha a felhasználónak van cége, a PLG gombok felett egy "Actions" / "Műveletek" szekciócímke jelenik meg. - Két gomb: "➕ Create Company" és "🔗 Join Company", mindkettő saját SVG ikonnal. - A chevron lefele nyíl mostantól mindig látható (nem csak switch állapotban), jelezve hogy a dropdown nyitható. - **Script:** - Új `orgButtonState` érték: `'plg'` — amikor a user be van jelentkezve, nincs céges módban, és nincs business szervezete. - `handleCreateCompany()` — placeholder: `console.log` + navigáció `/company/onboard` útvonalra. - `handleJoinCompany()` — placeholder: `console.log` + `alert('Coming soon!')`. - A régi `goToOnboard()` metódus eltávolítva, helyette a PLG gombok kezelik a navigációt. **2. [`frontend/src/i18n/en.ts`](frontend/src/i18n/en.ts) & [`frontend/src/i18n/hu.ts`](frontend/src/i18n/hu.ts):** - Új i18n kulcsok: `header.joinCompany`, `header.createCompany`, `header.actions`. ### ✅ Ellenőrzés - Vite build sikeres (✓ built in 4.35s). - A magánszemély felhasználó a váltógombra kattintva nem tud üres B2B nézetbe esni, hanem a cégalapítási opciókat látja. - A PLG gombok minden esetben elérhetők a dropdown alján. ## 2026-06-12 - Clean Odometer & Switcher Logic (Odometer Revert) ### 🎯 Cél A VehicleOdometerState prediktív modell eltávolítása, mivel a fizikai Asset entitásnak nincs szüksége előrejelzett kilométeróra állapotra. Az OdometerReading tiszta audit trail-ként marad meg. A HeaderCompanySwitcher PLG/Growth Loop logikájának tisztítása. ### 🔧 Változtatások **1. [`backend/app/models/vehicle/vehicle.py`](backend/app/models/vehicle/vehicle.py):** - `VehicleOdometerState` osztály teljes eltávolítása (volt ~112-140. sor). - `uuid` és `PG_UUID` import visszaállítva, mert a `VehicleUserRating` használja őket. **2. [`backend/app/models/vehicle/asset.py`](backend/app/models/vehicle/asset.py):** - `odometer_state` relationship eltávolítva az `Asset` modellből. - `is_anomaly` oszlop eltávolítva az `OdometerReading` modellből. **3. [`backend/app/models/vehicle/__init__.py`](backend/app/models/vehicle/__init__.py):** - `VehicleOdometerState` eltávolítva az importokból és `__all__`-ból. **4. [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py):** - `OdometerReading` import eltávolítva. - ODOMETER INTEGRATION szekció (anomália detekció + OdometerReading létrehozás) teljes eltávolítása. - Egyszerűsítve: közvetlen `asset.current_mileage` frissítés. **5. [`backend/app/services/odometer_service.py`](backend/app/services/odometer_service.py):** - Teljes fájl lecserélve deprekációs stub-ra. **6. [`backend/app/api/v1/endpoints/admin.py`](backend/app/api/v1/endpoints/admin.py):** - `OdometerService` import eltávolítva. - Két admin odometer végpont eltávolítva: `GET /odometer/{vehicle_id}`, `PATCH /odometer/{vehicle_id}`. **7. [`frontend/src/components/header/HeaderCompanySwitcher.vue`](frontend/src/components/header/HeaderCompanySwitcher.vue):** - Teljes átírás tiszta Scenario A & B logikára. - Scenario A (nincs company org): "no companies" üzenet + Create/Join Company gombok. - Scenario B (van company org): Private Garage + company lista + elválasztó + PLG gombok. - Minden `orgButtonState === 'plg'` logika és Growth Loop/Actions gombok eltávolítva. ### 🗄️ Adatbázis Műveletek - `DROP TABLE vehicle.vehicle_odometer_states CASCADE` - `ALTER TABLE vehicle.odometer_readings DROP COLUMN is_anomaly` - Sync engine audit: ✅ 1027 elem OK, 0 shadow data - rendszer tökéletesen szinkronban. ## 2026-06-13 - B2C Workspace-Driven UI: Individual org_type szűrés a HeaderCompanySwitcher-ben ### 🎯 Cél A magánszemélyek (B2C) számára a fejlécben lévő céges váltó duplikációmentesítése: az `org_type === 'individual'` szervezetek ne jelenjenek meg a céges listában, csak a "Privát garázs" opció. ### 🔧 Változtatások **1. [`frontend/src/components/header/HeaderCompanySwitcher.vue`](frontend/src/components/header/HeaderCompanySwitcher.vue):** - Új `companyOrganizations` computed property: kiszűri az `org_type === 'individual'` elemeket a `myOrganizations` listából. - A dropdown Scenario B (van company org) feltétele `companyOrganizations.length > 0`-ra változott. - A `v-for` ciklus `authStore.myOrganizations` helyett `companyOrganizations`-t használ. - `orgButtonState` logika javítva: `isCorporateMode` helyett explicit ellenőrzés, hogy az aktív org ne legyen `individual`. - `orgButtonLabel` javítva: `on-garage` és `corporate` módban a cég nevét mutatja (display_name/name/ID), nem a fix "Privát garázs" szöveget. ### ✅ Ellenőrzés - Vite build: ✅ Sikeres (161 modul, 0 hiba) - A backend `/organizations/my` végpontja már tartalmazza az `org_type` mezőt. - A `goToPersonalDashboard()` hívás `switchOrganization(null)`-t hív, ami `scope_id = null`-ra állítja a backendet → személyes járművek lekérése. ## 2026-06-13 - Fix Catalog API 404 Mismatch & Real Database Data ### 🎯 Cél A `GET /api/v1/catalog/brands?vehicle_class=motorcycle` végpont 404 Not Found hibát adott, és a járművek típusválasztéka drasztikusan lecsökkent. A probléma gyökere: a konténer nem a legfrissebb kódot futtatta. ### 🔍 Vizsgálat 1. **Adatbázis ellenőrzés:** A `vehicle.vehicle_model_definitions` tábla 345.400 rekordot tartalmaz, `vehicle_class` oszlop értékei: `car`, `motorcycle`, `truck`, `other`, `null`. A `motorcycle` osztályba 54.836 rekord tartozik 336 egyedi márkával. 2. **Kód ellenőrzés:** A `catalog.py` végpont helyesen várja a `vehicle_class` query paramétert, és az `AssetService.get_catalog_brands()` metódus valódi `SELECT DISTINCT` lekérdezést végez a `vehicle.vehicle_model_definitions` táblán. 3. **Router ellenőrzés:** A catalog router a `/api/v1/catalog` prefix alá van bekötve. ### 🔧 Megoldás - A kód logikája helyes volt, nem volt szükség módosításra. - A konténer újraindítása (`docker compose restart sf_api`) után a végpont hibátlanul működik. ### ✅ Eredmény - `GET /api/v1/catalog/brands?vehicle_class=motorcycle` → **200 OK**, 336 márka - `GET /api/v1/catalog/brands?vehicle_class=car` → **200 OK**, 1111 márka - `GET /api/v1/catalog/brands?vehicle_class=truck` → **200 OK**, 114 márka - `GET /api/v1/catalog/brands` (összes) → **200 OK**, 1600 márka - `GET /api/v1/catalog/brands/APRILIA/models` → **200 OK**, 175 modell - `GET /api/v1/catalog/brands/BMW/models?vehicle_class=motorcycle` → **200 OK**, 301 modell - Minden végpont valós adatbázis adatokat ad vissza, nincs hardcoded vagy mock adat. ## 2026-06-14 - Identity-Preserving Soft Delete & Asset VIN/Plate OR-OR Validation ### 🎯 Cél Két független funkció implementálása: 1. **Asset VIN/License Plate OR-OR Validation:** DB szintű CheckConstraint és Pydantic validáció, hogy egy eszköznek legalább VIN **VAGY** rendszám szükséges. 2. **Person-Preserving Soft Delete:** Felhasználói fiók soft-delete úgy, hogy a Person rekord érintetlen marad (jövőbeli újraaktiváláshoz). ### 🔧 Változtatások **1. `backend/app/models/vehicle/asset.py` (72-78. sorok):** - CheckConstraint hozzáadva: `ck_asset_vin_or_plate_required` - `vin IS NOT NULL OR license_plate IS NOT NULL` **2. `backend/app/models/identity/identity.py` (82, 155. sorok):** - `Person.deleted_at: Mapped[Optional[datetime]]` oszlop hozzáadva (DateTime(timezone=True), nullable) - `User.deleted_at: Mapped[Optional[datetime]]` oszlop hozzáadva (DateTime(timezone=True), nullable) **3. `backend/app/schemas/asset.py`:** - `AssetCreate`: `license_plate` és `vin` mezők `Optional[str] = Field(None, ...)`-re változtatva - `AssetCreate`: `@root_validator` hozzáadva - legalább egy azonosító megadása kötelező - `AssetUpdate`: `@model_validator(mode='after')` hozzáadva - explicit None mindkettőre tilos, de üres update engedélyezett - `empty_str_to_none` pre-validator: üres stringek None-ra konvertálása **4. `backend/app/services/auth_service.py` (485-527. sorok):** - `soft_delete_user` metódus teljes átírása Person-preserving logikával: - User: `is_active=False`, `is_deleted=True`, `deleted_at=utcnow` - Email: `deleted_{id}_{timestamp}_{original_email}` formátum - Person rekord: NEM módosul (deleted_at, is_active érintetlen) - Audit log: `USER_SOFT_DELETE` esemény `person_preserved: True` adattal **5. `backend/app/api/v1/endpoints/users.py` (502-531. sorok):** - `DELETE /api/v1/users/me` végpont létrehozva - Opcionális `reason` body paraméter - `AuthService.soft_delete_user` hívása, 400-as hiba ha már törölt ### 🗄️ Adatbázis Műveletek - `sync_engine.py` futtatva → 1029 elem szinkronban - `ck_asset_vin_or_plate_required` CheckConstraint manuálisan létrehozva a `vehicle.assets` táblán - `deleted_at` oszlopok megléte ellenőrizve: `identity.users` és `identity.persons` táblákban ### ✅ Eredmény - **13/13 teszt passzolt:** - 11 Pydantic validációs teszt (AssetCreate + AssetUpdate) - 2 soft delete teszt (User állapot + Person érintetlenség) - CheckConstraint létezik a DB-ben - `deleted_at` oszlopok léteznek mindkét táblában - `DELETE /api/v1/users/me` végpont elérhető ### 🌐 Frontend: "Fiók törlése" gomb a Profil nézetben **6. `frontend/src/stores/auth.ts` (482-503. sorok):** - `deleteAccount(reason?: string)` async action hozzáadva: - `api.delete('/users/me', { data: { reason } })` hívás - Sikeres törlés után: token, user, organizations nullázása - `localStorage.removeItem('access_token')` és `localStorage.removeItem('refresh_token')` - `router.push('/')` átirányítás a landing page-re - Hiba esetén `error.value` beállítása a backend detail üzenetével **7. `frontend/src/i18n/hu.ts` (211-219. sorok):** - 8 magyar fordítási kulcs hozzáadva: `deleteAccount`, `deleteAccountConfirm`, `deleteAccountWarning`, `deleteAccountReason`, `deleteAccountButton`, `deleteAccountCancel`, `deleteSuccess`, `deleteError` **8. `frontend/src/i18n/en.ts` (211-219. sorok):** - 8 angol fordítási kulcs hozzáadva (megegyező struktúra) **9. `frontend/src/views/ProfileView.vue`:** - **Template:** Piros "Fiók törlése" gomb hozzáadva a "Jelszó módosítása" gomb után (szemetes ikonnal, `bg-red-600/20 border-red-500/40` stílus) - **Template:** Megerősítő modal hozzáadva (`` mintában, a jelszó modal után): - Figyelmeztető ikon (piros háromszög) - Magyarázó szöveg a Person adatok megőrzéséről - Opcionális ok megadása (textarea) - Hiba/siker üzenet megjelenítése - "Mégsem" + "Igen, töröld a fiókomat" (piros) gombok - **Script:** Új refs: `showDeleteModal`, `isDeleting`, `deleteReason`, `deleteError`, `deleteSuccess` - **Script:** `closeDeleteModal()` - állapot reset - **Script:** `submitDeleteAccount()` - `authStore.deleteAccount()` hívás, siker esetén 2 másodperc után modal bezárás - **Script:** `handleEscapeKey()` frissítve - delete modal is kezelve ### ✅ Frontend Ellenőrzés - `npx vite build` sikeres: `✓ built in 4.27s`, `ProfileView-BQxdl6hU.js` (47.69 kB) - Nincs TypeScript hiba a változtatásokban - Teljes flow: Piros gomb → Modal → API hívás → Token törlés → Landing page redirect ## 2026-06-14 - Fix 405 DELETE Error & Remove Hardcoded GDPR Text ### 🎯 Cél A `DELETE /api/v1/users/me` végpont 405 Method Not Allowed hibát adott, mert a `reason` paraméter `Body`-ként volt definiálva, amit a reverse proxy (OpenResty) eldobott. Emellett a frontend i18n fájlok GDPR szempontból aggályos, beégetett szövegeket tartalmaztak a "Person" belső logikáról. ### 🔧 Változtatások **1. `backend/app/api/v1/endpoints/users.py`:** - `Body` import cserélve `Query`-re (4. sor). - `DELETE /me` végpont `reason` paraméter típusa `Body(None)` → `Query(None)` (504. sor). - REST szabály: DELETE kéréseknek nincs request body-ja. **2. `frontend/src/i18n/hu.ts` (214. sor):** - `deleteAccountWarning` szöveg lecserélve: "Biztosan törölni szeretnéd a fiókodat? Ez a művelet végleges és nem vonható vissza." **3. `frontend/src/i18n/en.ts` (214. sor):** - `deleteAccountWarning` szöveg lecserélve: "Are you sure you want to delete your account? This action is permanent and cannot be undone." **4. `frontend/src/stores/auth.ts` (482-489. sor):** - `deleteAccount()` API hívás átírva: `data: { reason: ... }` payload helyett `params` (query string) használata. - Csak akkor adja át a `reason` paramétert, ha az ténylegesen meg van adva. ### ✅ Eredmény - Backend Python szintaxis valid: OK. - Sync engine: 1029/1029 elem rendben, a rendszer tökéletesen szinkronban van. - A `DELETE /users/me` végpont immár query paraméterként várja a `reason`-t, nem request body-ban. - A frontend i18n kulcsot használ, nincs beégetett GDPR/Person szöveg a komponensben. ## 2026-06-14 - Frontend: Account Restore, Last Admin Warning & Smart Company Claiming ### 🎯 Cél Három frontend funkció implementálása teljes i18n támogatással: (1) 30 napos törölt fiók visszaállítás a bejelentkezési folyamatban, (2) Utolsó admin figyelmeztetés a fiók törlésénél, (3) Intelligens cég igénylés/csatlakozás. ### 🔧 Módosított fájlok - [`frontend/src/i18n/hu.ts`](frontend/src/i18n/hu.ts) - Új i18n kulcsok: restore, lastAdminWarning, company claiming/joining - [`frontend/src/i18n/en.ts`](frontend/src/i18n/en.ts) - Új i18n kulcsok angol nyelven - [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts) - `deleteAccount()` visszatérési érték bővítése `is_last_admin`-nel, `requestRestore()` és `verifyRestore()` hozzáadása - [`frontend/src/components/LoginModal.vue`](frontend/src/components/LoginModal.vue) - Account Restore 5. face (3 lépés: email → OTP+password → siker) - [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue) - Last Admin Warning orange alert a törlés modalban - [`frontend/src/views/organization/CompanyOnboardingView.vue`](frontend/src/views/organization/CompanyOnboardingView.vue) - Smart Company Claiming/Joining (active_exists, orphaned, manual fallback) ### ✅ Eredmény - Vite build: sikeres (4.29s) - Minden szöveg i18n kulcsokon keresztül, nincs hardcoded szöveg a Vue komponensekben ## 2026-06-14 - Enterprise User Management Module (RBAC & Bulk Actions) ### 🎯 Cél Enterprise szintű User Management modul implementálása az Admin felülethez: RBAC-alapú bulk műveletek (ban, unban, soft_delete, restore, hard_delete) és paginált felhasználó lista szűréssel. ### 🔧 Változtatások **1. Backend - [`admin.py`](backend/app/api/v1/endpoints/admin.py:470):** - `BulkActionRequest` Pydantic modell regex validációval a támogatott akciókra - `GET /admin/users` - Paginált felhasználó lista ILIKE e-mail szűréssel, role/is_active/is_deleted filterekkel - `POST /admin/users/bulk-action` - Csoportos műveletek RBAC rangellenőrzéssel: - `hard_delete`: superadmin (rank >= 100) - `soft_delete/restore/unban`: admin (rank >= 90) - `ban`: moderator (rank >= 50) - Superadmin felhasználók védelme (kivéve ha saját magán hajtja végre) - `SecurityAuditLog` minden bulk művelethez **2. Frontend Store - [`adminUsers.ts`](frontend/src/stores/adminUsers.ts):** - Új Pinia store `useAdminUsersStore` néven - `fetchUsers()` - Paginált lekérés filter paraméterekkel - `bulkAction()` - Bulk művelet végrehajtás + automatikus lista frissítés **3. Frontend View - [`AdminUsersView.vue`](frontend/src/views/admin/AdminUsersView.vue):** - Teljes admin felhasználó kezelő felület: - E-mail keresés 400ms debounce-szal - Role és státusz filter dropdown-ok - Checkbox-os tábla "Select All" indeterminate állapottal - Bulk Action Bar (Ban, Unban, Soft Delete, Restore, Hard Delete) - **Hard Delete gomb elrejtése** `v-if="authStore.user?.role === 'superadmin'"` - Role badge-ek színkódolva, státusz badge-ek (Active=zöld, Inactive=sárga, Deleted=piros) - Pagination Previous/Next gombokkal **4. Router & Layout:** - [`index.ts`](frontend/src/router/index.ts) - `/admin/users` route regisztrálva - [`AdminLayout.vue`](frontend/src/layouts/AdminLayout.vue) - Aktív menüpont kiemelés ### ✅ Eredmény - Backend Python szintaxis: OK - Vite build: sikeres (4.48s) - `AdminUsersView-CzzIFoUK.js` (9.69 kB) - Minden funkció implementálva és verifikálva ## 2026-06-15 - Admin Service Catalog API & UI ### 🎯 Cél Admin Service Catalog CRUD API és frontend UI megvalósítása i18n támogatással. A backend API végpontok (GET, POST, PATCH) már léteztek, a frontend oldal hiányzott. ### 🔧 Változtatások **1. FRONTEND - [`adminServices.ts`](frontend/src/stores/adminServices.ts):** - Új Pinia store a ServiceCatalog kezelésére - `fetchServices()`, `createService()`, `updateService()` aszinkron akciók - TypeScript interfészek: `ServiceCatalogItem`, `ServiceCatalogCreatePayload`, `ServiceCatalogUpdatePayload` **2. FRONTEND - [`AdminServicesView.vue`](frontend/src/views/admin/AdminServicesView.vue):** - Reszponzív kártya grid (1/2/3 oszlop) a szolgáltatások megjelenítésére - Minden kártyán: service_code badge, név, leírás (line-clamp-2), kredit költség - Szerkesztő/Létrehozó modál űrlap validációval - Aktív/Inaktív toggle gomb - Number input spinner eltávolítva (CSS + Tailwind classes) **3. FRONTEND - [`router/index.ts`](frontend/src/router/index.ts):** - `/admin/services` route regisztrálva `admin-services` néven **4. FRONTEND - [`AdminLayout.vue`](frontend/src/layouts/AdminLayout.vue):** - "Services" menüpont hozzáadva a Subscription Packages alá **5. FRONTEND - [`en.ts`](frontend/src/i18n/en.ts), [`hu.ts`](frontend/src/i18n/hu.ts):** - `admin.services.*` i18n kulcsok mindkét nyelven (title, subtitle, create_button, modal, field_*, stb.) **6. ADATBÁZIS - Seed:** - `sync_engine.py` futtatva: 1047 elem OK, rendszer szinkronban - `seed_services.py` futtatva: 3 szolgáltatás beszúrva (SRV_DATA_EXPORT, SRV_AI_UPLOAD, SRV_DIGITAL_BOOK) ### ✅ Eredmény - Backend API végpontok: már léteztek, nem kellett módosítani - Frontend store, view, router, layout, i18n: mind implementálva - Adatbázis: sync_engine OK, seed sikeres (3 rekord) - A Services menüpont megjelent az Admin felületen a 3 kezdő szolgáltatással ## 2026-06-15 - Gitea #134: Company Settings, Admin Package Filters & Subscription Migration ### 🎯 Cél Három független feladat implementálása: (1) Cégadatok szerkesztése modal + PATCH végpont, (2) Admin csomagok szűrése típus és dátum szerint, (3) Előfizetés migrációs szkript a régi denormalizált `subscription_plan` mezők átvezetésére. ### 🔧 Változtatások **FRONTEND 1a - Context Switcher javítás ([`HeaderCompanySwitcher.vue`](frontend/src/components/header/HeaderCompanySwitcher.vue)):** - Megállapítás: A komponens már tartalmazza a `display_name || name || #ID` fallback logikát. - Az `OrganizationLayout` computed property-je (`orgName`) reaktívan frissül, amikor az `authStore.myOrganizations` betöltődik. - Nincs szükség kódmódosításra - a meglévő logika megfelelően kezeli az async betöltést. **FRONTEND 1b - Backend PATCH végpont ([`organizations.py`](backend/app/api/v1/endpoints/organizations.py:283)):** - Új általános `PATCH /{org_id}` végpont (nem csak visual-settings), OWNER/ADMIN jogosultsággal. - Támogatott mezők: `name`, `full_name`, `tax_number`, `display_name`, `default_currency`, `language`, `address_zip`, `address_city`, `address_street_name`, `address_street_type`, `address_house_number`, `address_stairwell`, `address_floor`, `address_door`, `address_hrsz`, `visual_settings`. - `model_dump(exclude_unset=True)` használata részleges frissítéshez, JSONB merge a `visual_settings`-hez. **FRONTEND 1b - Schema bővítés ([`organization.py`](backend/app/schemas/organization.py:41)):** - `OrganizationUpdate` séma kiegészítve: `name`, `full_name`, `tax_number`, `address_zip`, `address_city`, `address_street_name`, `address_street_type`, `address_house_number`, `address_stairwell`, `address_floor`, `address_door`, `address_hrsz` mezőkkel. **FRONTEND 1c - Cégbeállítások modal ([`OrganizationSettingsModal.vue`](frontend/src/components/organization/OrganizationSettingsModal.vue)):** - Új komponens: Teleport modal form űrlappal (name, full_name, display_name, tax_number, address_zip, address_city, address_street_name, address_house_number). - Pre-fill az `authStore.myOrganizations`-ból mount-kor. - `PATCH /organizations/{orgId}` hívás csak a nem-üres mezőkkel. - `authStore.fetchMyOrganizations()` frissítés mentés után. - `close` és `saved` események. **FRONTEND 1c - CompanyGarageView bővítés ([`CompanyGarageView.vue`](frontend/src/views/organization/CompanyGarageView.vue)):** - "Company Settings" gomb a Quick Actions kártyában (emerald stílus, gear ikon). - `showSettingsModal` ref + `OrganizationSettingsModal` import. - Modal megjelenítése `v-if="showSettingsModal"`. **FRONTEND 1c - i18n kulcsok ([`hu.ts`](frontend/src/i18n/hu.ts), [`en.ts`](frontend/src/i18n/en.ts)):** - Új `company.settings*` kulcsok: settingsTitle, settingsDescription, settingsModalTitle, settingsName, settingsFullName, settingsTaxNumber, settingsDisplayName, settingsAddressZip, settingsAddressCity, settingsAddressStreet, settingsAddressHouseNumber, settingsSaveSuccess, settingsSaveError. **FRONTEND 2a - Backend szűrés ([`admin_packages.py`](backend/app/api/v1/endpoints/admin_packages.py:51)):** - `type_filter: Optional[str]` query param - JSONB `rules->>'type'` szűrés (private/corporate). - `date_from: Optional[str]` query param - JSONB `rules->'lifecycle'->>'available_from' >= date_from`. - `date_until: Optional[str]` query param - JSONB `rules->'lifecycle'->>'available_until' <= date_until`. **FRONTEND 2b - Admin csomagok szűrő UI ([`AdminPackagesView.vue`](frontend/src/views/admin/AdminPackagesView.vue)):** - Filter bar a header és a csomag grid között: Type dropdown (All/Private/Corporate), Date From/Until inputok, Clear Filters gomb. - `typeFilter`, `dateFrom`, `dateUntil` ref-ek, `clearFilters()` függvény. - `loadPackages()` filter paraméterek átadása. **FRONTEND 2c - Store bővítés ([`adminPackages.ts`](frontend/src/stores/adminPackages.ts)):** - `fetchPackages()` paraméterek: `type_filter`, `date_from`, `date_until` hozzáadva. **BACKEND 3 - Migrációs szkript ([`migrate_subscriptions.py`](backend/app/scripts/migrate_subscriptions.py)):** - **A)** `corp_free_v1` csomag létrehozása (ID=22, 0 áras, 3 jármű, 1 garázs). - **B)** `finance.user_subscriptions` tábla létrehozása (user_id, tier_id, valid_from, valid_until, is_active, created_at, updated_at). - **C)** Adatkonverzió: 19 org subscription (18x private_free_v1, 1x corp_premium_v1) és 21 user subscription (16x private_free_v1, 3x corp_premium_v1, 1x corp_vip_v1, 1x private_pro_v1). - Plan mapping: FREE→private_free_v1, PRO→private_pro_v1, PREMIUM→corp_premium_v1, ENTERPRISE→corp_vip_v1. **BACKEND 3 - UserSubscription modell ([`core_logic.py`](backend/app/models/core_logic.py:44)):** - `UserSubscription` SQLAlchemy modell hozzáadva a `finance.user_subscriptions` táblához. - Kapcsolatok: `user_id → identity.users.id`, `tier_id → system.subscription_tiers.id`. ### 🗄️ Adatbázis Műveletek - `sync_engine.py` futtatva: 1056 elem OK, 0 shadow data - rendszer tökéletesen szinkronban. - `corp_free_v1` tier létrehozva (ID=22). - `finance.user_subscriptions` tábla létrehozva index-szel. - 19 org_subscription + 21 user_subscription rekord beszúrva. ### ✅ Eredmény - Backend: PATCH /organizations/{id} végpont működik, admin packages szűrés JSONB path lekérdezéssel. - Frontend: Company Settings modal, Admin Packages filter UI, i18n támogatás. - Adatbázis: subscription migráció sikeres, minden adat átvezetve a normalizált táblákba. - Sync engine: teljes szinkronban. --- ## 2026-06-15: PATCH 500 javítás + Cég adatai Hamburger Menü ### 🎯 Cél Három részből álló feladat: 1. **KRITIKUS:** PATCH /admin/packages/{id} 500-as hiba javítása 2. **Frontend:** Hiányzó i18n kulcsok (date_from_label, date_until_label) pótlása + dátum szűrés logika javítása 3. **Frontend:** Cég Settings Hamburger Menü + 'Cég adatai' modal read/edit móddal ### 🔍 Hibakeresés (Docker Log) - `docker compose logs --tail=100 sf_api` → `AttributeError: 'dict' object has no attribute 'model_dump'` - **Gyökér ok:** `payload.model_dump(exclude_unset=True)` rekurzívan dict-té alakítja a beágyazott Pydantic modelleket, így `update_data["rules"]` már sima dict, amin a `.model_dump()` hívás AttributeError-t dob. ### 🔧 Változtatások **1. Backend: PATCH 500 fix (`backend/app/api/v1/endpoints/admin_packages.py`)** - `new_rules = update_data["rules"].model_dump()` → `new_rules = update_data["rules"]` (közvetlen dict assign) - `isinstance(new_rules, dict)` biztonsági ellenőrzés hozzáadva - Dátum szűrés javítva: string összehasonlítás helyett `cast(..., Date)` használata - Importok: `from datetime import date`, `from sqlalchemy import Date, cast` **2. Frontend: i18n kulcsok (`frontend/src/i18n/hu.ts`, `frontend/src/i18n/en.ts`)** - `admin.packages.date_from_label`, `admin.packages.date_until_label` kulcsok hozzáadva - `company.companyDataTitle`, `company.companyDataMenu`, `company.close` kulcsok hozzáadva **3. Frontend: CompanyDataModal (`frontend/src/components/organization/CompanyDataModal.vue`)** - **Read mód:** Név, Teljes név, Adószám, Címek megjelenítése szövegesen; 'Bezárás' és 'Szerkesztés' gombok - **Edit mód:** Input mezők (name, full_name, display_name, address); adószám (`tax_number`) mindig disabled/read-only - 'Mégsem' → vissza read módba; 'Mentés' → PATCH API hívás, majd vissza read módba - Glassmorphism design (dark theme, backdrop-blur, border-white/10) **4. Frontend: Hamburger Menü (`frontend/src/layouts/OrganizationLayout.vue`)** - 3-vonalas hamburger ikon a header right slot-jában - Dropdown 'Cég adatai' menüponttal - Outside-click handler a bezáráshoz - CompanyDataModal integráció ### ✅ Ellenőrzés - Backend konténer újraindítva: `docker compose restart sf_api` → sikeres startup - Frontend build: `docker compose exec sf_public_frontend sh -c "cd /app && npm run build"` → 4.83s, hiba nélkül