szolgáltatók beálltásai, szerkesztése , létrehozása

This commit is contained in:
Roo
2026-06-17 11:52:25 +00:00
parent 213ba3b0f1
commit bf3a971ff1
56 changed files with 14421 additions and 1512 deletions

View File

@@ -8,654 +8,220 @@ Backend SQL hibák javítása a GET /admin/users végponton (Address outerjoin,
### 🔧 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.
- `GET /admin/users` SQL javítás: `Address` outerjoin-ra változtatva (`isouter=True`), hogy a cím nélküli userek is visszajöjjenek
- Telefonszám keresés: `phone` mező hozzáadva a `or_()` feltételhez
**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.
**2. FRONTEND - [`AdminLayout.vue`](frontend/src/layouts/AdminLayout.vue:1):**
- `HeaderProfile` komponens beillesztve a jobb felső sarokba (belépett user avatar + név)
- A `SidebarToggle` gomb fixálva a sidebar szélén
**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.
**3. FRONTEND - [`AdminUsersView.vue`](frontend/src/views/AdminUsersView.vue:1):**
- **Sticky Bulk Action Bar:** A tömeges műveletek sáv (`BulkActionBar`) `sticky` pozícionálást kapott, hogy görgetéskor is látszódjon
- **Clear/X gomb:** A keresőmezőbe egy `X` gomb került, ami egy kattintással törli a keresési feltételt
### ✅ 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
## 2026-06-16 - Fix 404 API Router & Redesign Dashboard Launcher Card
### 🎯 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.
A Dashboard Service Finder kártya (Card 3) átalakítása elegáns, kétgombos indítópulttá (Launcher), valamint a providers API 404 hiba kivizsgálása és javítása.
### 🔧 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.
**1. BACKEND AUDIT - [`api.py`](backend/app/api/v1/api.py:38):**
- Ellenőrizve: `providers` router regisztrálva van (`include_router(providers.router, prefix="/providers")`)
- Ellenőrizve: providers végpontokon NINCS trailing slash (`/categories`, `/search`, `/quick-add`)
- **Route-ok élőben is ellenőrizve:** `GET /api/v1/providers/categories`, `GET /api/v1/providers/search`, `POST /api/v1/providers/quick-add` mind aktívak
### ✅ 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.
**2. FRONTEND - [`ProviderQuickAddModal.vue`](frontend/src/components/provider/ProviderQuickAddModal.vue:223):**
- `fetchCategories()` már rendelkezik try/catch blokkal, 11 hardcoded fallback kategóriával
- Nincs szükség módosításra
## 2026-06-08 - F5 Refresh Logout Bug Fix & HeaderLogo Contextual Navigation
**3. FRONTEND - [`DashboardView.vue`](frontend/src/views/DashboardView.vue:187):**
- **Card 3 teljes átalakítása:** A 3-lépéses kontextuális kereső űrlap (input, 2 dropdown, keresés gomb) ELTÁVOLÍTVA
- **Launcher dizájn:** Két nagy gomb, szépen formázva:
- `🔍 Szervizek Keresése` — nagy, zöld gradient gomb, `isSearchModalOpen` modalt nyit
- ` Új Szolgáltató Rögzítése` — szekunder, dashed border gomb, `isSfQuickAddOpen` modalt nyit (meglévő ProviderQuickAddModal)
- **Search Modal (Placeholder):** Teleportált modál, benne: `🔍` ikon, "Részletes kereső térképpel hamarosan..." szöveg, fejlesztés alatt státusz
- Régi `sfSearchLocation`, `sfSearchCategory`, `sfSearchVehicleId`, `sfCategories`, `fetchSfCategories()`, `onSfSearch()` eltávolítva
## 2026-06-17 - P0 Bugfix: Provider Data Persistence & Schema Alignment
### 🎯 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.
Kritikus adatperzisztencia hiba javítása a Service Finder Provider rendszerében. A frontend edit modal nem hívott backend API-t, így a szerkesztett adatok elvesztek. Emellett a contact mezők (phone, email, website, tags) nem kerültek perzisztálásra a quick-add során, és a search nem adta vissza ezeket.
### 🔧 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.
**1. BACKEND - [`provider.py`](backend/app/schemas/provider.py:1) (NEW):**
- `ProviderQuickAddIn`: `contact_phone`, `contact_email`, `website`, `tags` mezők hozzáadva
- `ProviderSearchResult`: `address_zip`, `contact_phone`, `contact_email`, `website`, `tags` mezők hozzáadva
- `ProviderUpdateIn` (NEW): name, city, address_zip, street, contact_phone, contact_email, website, tags
- `ProviderUpdateResponse` (NEW): id, name, status, message
**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é.
**2. BACKEND - [`provider_service.py`](backend/app/services/provider_service.py:486):**
- `quick_add_provider()`: contact mezők mentése ServiceProfile-ba
- `search_providers()`: LEFT JOIN ServiceProfile, contact mezők visszaadása
- `update_provider()` (NEW): Organization + ServiceProfile atomi frissítése
**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
**3. BACKEND - [`providers.py`](backend/app/api/v1/endpoints/providers.py:134):**
- `PUT /providers/{id}` végpont hozzáadva (authentikált, hibakezeléssel)
## 2026-06-12 - Dynamic Parameters Check & Centralized Odometer Implementation
**4. FRONTEND - [`ProviderEditModal.vue`](frontend/src/components/provider/ProviderEditModal.vue:191):**
- **CRITICAL FIX**: `handleSave()` most `api.put()`-et hív, nem csak eventet emitál
- `@save``@saved` (past tense, API call után)
- `address_zip`, `contact_phone`, `contact_email`, `website`, `tags` mezők támogatása
- `source` kivéve a payload-ból (backend-only)
**5. FRONTEND - [`ProviderQuickAddModal.vue`](frontend/src/components/provider/ProviderQuickAddModal.vue:253):**
- Telefon, email, weboldal, címkék mezők hozzáadva a formhoz
- Tag management (vessző/pontosvessző parsing, remove gomb)
- Payload bővítése contact mezőkkel
**6. FRONTEND - [`ServiceFinderView.vue`](frontend/src/views/ServiceFinderView.vue:375):**
- `@save``@saved` event binding
- `handleEditSaved()`: mentés után automatikus keresés újrafuttatás
### ✅ Verifikáció
- `sync_engine` lefuttatva: **1061 elem OK, 0 javítás, 0 shadow data** - rendszer tökéletesen szinkronban
## 2026-06-17 - P1 Critical Align: Atomizált címmezők a Provider sémákban
### 🎯 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).
A `street: Optional[str]` mező eltávolítása és helyette atomizált címmezők (`address_street_name`, `address_street_type`, `address_house_number`) bevezetése a Pydantic sémákban, backend service-ben és frontend űrlapokon. A kapcsolatfelvételi adatok (contact_phone, contact_email, website, tags) a ServiceProfile-ba kerülnek.
### 🔧 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.
**1. BACKEND - [`provider.py`](backend/app/schemas/provider.py):**
- `street` mező ELTÁVOLÍTVA a `ProviderQuickAddIn` és `ProviderUpdateIn` sémákból
- `address_street_name`, `address_street_type`, `address_house_number` mezők HOZZÁADVA mindkét sémához
- `ProviderSearchResult` bővítve az atomizált címmezőkkel
**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.
**2. BACKEND - [`provider_service.py`](backend/app/services/provider_service.py):**
- `quick_add_provider()`: `data.street``data.address_street_name`, `data.address_street_type`, `data.address_house_number` az Organization és Branch táblákban
- `update_provider()`: ugyanez az atomizált címkezelés
- `search_providers()`: az org SELECT most már tartalmazza az `address_street_name`, `address_street_type`, `address_house_number` mezőket
- A Branch létrehozásánál a `street_name`, `street_type`, `house_number` mezők külön-külön töltődnek
**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.
**3. FRONTEND - [`ProviderQuickAddModal.vue`](frontend/src/components/provider/ProviderQuickAddModal.vue):**
- A régi egyesített "Cím (Utca, házszám)" mező helyett 3 külön mező: Utca neve (text), Közterület jellege (select/dropdown 15 opcióval), Házszám (text)
- Payload az új atomizált kulcsokkal megy a backend felé
### 🛠️ 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
**4. FRONTEND - [`ProviderEditModal.vue`](frontend/src/components/provider/ProviderEditModal.vue):**
- Ugyanaz a 3 mezős szétbontás, a form populate az atomizált mezőkből történik
- Payload atomizált kulcsokkal
### ✅ 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
**5. FRONTEND - [`ProviderDetailModal.vue`](frontend/src/components/provider/ProviderDetailModal.vue):**
- Az intelligens címösszefűzés (`formattedAddress` computed) a `provider.address_street_name + ' ' + provider.address_street_type + ' ' + provider.address_house_number` alapján történik
- `hasAddress` computed ellenőrzi az atomizált mezők meglétét
## 2026-06-12 - Emergency Regression Fix: Vehicle Creation 500 Error (UnboundLocalError)
### ✅ Verifikáció
- `sync_engine` lefuttatva: **1061 elem OK, 0 javítás, 0 shadow data** - rendszer tökéletesen szinkronban
- Python syntax check: minden fájl szintaktikailag helyes
## 2026-06-17 - Provider Update & Search Fix Csomag (#264)
### 🎯 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
Két kritikus hiba javítása a provider endpointokban:
1. **PUT /providers/{id} → 404**: A konténer nem volt újraindítva a providers modul kódváltoztatásai után
2. **GET /providers/search → 500**: `.astext` hiba JSONB subscripten + UNION oszlopszám mismatch
3. **Multi-source update**: Az `update_provider` csak Organization-ben keresett, de a search 3 forrást használ
### 🔧 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).
**1. BACKEND - [`provider_service.py:207`](backend/app/services/provider_service.py:207):**
- `.astext``cast()` javítás: `Organization.external_integration_config["source"].astext``cast(Organization.external_integration_config["source"], String)`
- **Root cause**: JSON oszlop subscript-je `BinaryExpression`-t ad vissza, amelyen nincs `.astext`
**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.
**2. BACKEND - [`provider_service.py:232-274`](backend/app/services/provider_service.py:232):**
- UNION oszlopszám mismatch javítva: staging és crowd SELECT-ekhez hozzáadva a hiányzó `contact_phone`, `contact_email`, `website`, `specialization_tags` mezők (14 oszlopra egységesítve)
### ✅ 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
**3. BACKEND - [`provider_service.py:477-622`](backend/app/services/provider_service.py:477):**
- Multi-source update logika: `update_provider()` most már mindhárom forrást támogatja:
1. `fleet.organizations` (verified orgs) - közvetlen frissítés
2. `marketplace.service_staging` (robot adatok) - migrálás Organization-be
3. `marketplace.service_providers` (crowdsourced) - migrálás Organization-be
## 2026-06-12 - B2B/B2C Context Switcher PLG Actions
**4. INFRA - [`pre_start.sh`](backend/app/scripts/pre_start.sh):**
- Dokumentálva: a `uvicorn` `--reload` nélkül fut, kódváltoztatás után `docker compose restart sf_api` szükséges
**5. BACKEND - [`provider_service.py:578-596`](backend/app/services/provider_service.py:578):**
- **Adatvédelmi javítás (2026-06-17):** A címmezők (`address_city`, `address_zip`, `address_street_name`, `address_street_type`, `address_house_number`) most már **csak akkor íródnak felül**, ha a frontend explicit nem-`null` értéket küld. Ez megakadályozza, hogy a meglévő címadatok véletlenül `null`-ra állítódjanak, amikor a felhasználó csak más mezőket szerkeszt.
- **Trigger:** A Gitea kártya visszautasításra került (`denied` státusz) a felhasználó által: *"az adatok tárolása minden esetben bontottan történjen meg és ha hiányzik valamelyik az alap cím tárolási adatból akkor vissza kell tenni."*
### ✅ Verifikáció
- **PUT /providers/58**: 200 OK ✅
- **PUT /providers/58 (partial update - csak zip)**: 200 OK ✅ (többi mező nem nullázódik)
- **GET /providers/search?q=Dunakeszi**: 200 OK ✅ (2 provider)
- **GET /providers/categories**: 200 OK ✅ (11 categories)
- **Login**: 200 OK ✅
## 2026-06-17 - i18n: Hiányzó provider címmező fordítások hozzáadása
### 🎯 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.
A ProviderEditModal.vue 6 darab `provider.*` i18n kulcsa hiányzott mindkét nyelvi modulból (`hu.ts`, `en.ts`), így a felhasználói felületen a kulcsnevek (pl. `provider.streetNameLabel`) jelentek meg a lefordított szöveg helyett.
### 🔧 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.
**1. FRONTEND - [`hu.ts`](frontend/src/i18n/hu.ts:1300):**
- `provider.streetNameLabel`: `'Utca neve'`
- `provider.streetNamePlaceholder`: `'Pl. Egressy'`
- `provider.streetTypeLabel`: `'Közterület jellege'`
- `provider.streetTypePlaceholder`: `'Válassz típust...'`
- `provider.houseNumberLabel`: `'Házszám'`
- `provider.houseNumberPlaceholder`: `'Pl. 4'`
**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`.
**2. FRONTEND - [`en.ts`](frontend/src/i18n/en.ts:1300):**
- `provider.streetNameLabel`: `'Street Name'`
- `provider.streetNamePlaceholder`: `'e.g. Egressy'`
- `provider.streetTypeLabel`: `'Street Type'`
- `provider.streetTypePlaceholder`: `'Select type...'`
- `provider.houseNumberLabel`: `'House Number'`
- `provider.houseNumberPlaceholder`: `'e.g. 4'`
### ✅ 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.
### ✅ Verifikáció
- Mindkét i18n fájl szintaktikailag helyes (Node.js require sikeres)
- A ProviderEditModal.vue összes `t('provider.*')` hívása le van fedve
## 2026-06-12 - Clean Odometer & Switcher Logic (Odometer Revert)
## 2026-06-17 - "Dunakeszi, Dunakeszi" duplikáció javítása + irányítószám megjelenítés
### 🎯 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.
A szervizkereső oldalon a kártyán "Dunakeszi, Dunakeszi" duplikált városnév jelent meg, mert a backend `address` mezője megegyezett a `city` mezővel. A részletes nézetből hiányzott az irányítószám.
### 🔧 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.
**1. BACKEND - [`provider_service.py`](backend/app/services/provider_service.py:198):**
- Az `address` mezőt `Organization.address_city.label("address")`-ről `func.concat(...)`-re változtattuk, ami az összes atomizált címmezőt (irányítószám, város, utca, közterület, házszám) fűzi össze.
- Példa eredmény: `"2120 Dunakeszi, Egressy utca 4"` a korábbi `"Dunakeszi"` helyett.
**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.
**2. FRONTEND - [`ServiceFinderView.vue`](frontend/src/views/ServiceFinderView.vue:543):**
- Új `formatCardAddress(provider)` metódus, ami a kártyán az atomizált címmezőkből építi fel a címet.
- Fallback: ha nincs atomizált adat, a backend által összefűzött `address` mezőt használja.
**3. [`backend/app/models/vehicle/__init__.py`](backend/app/models/vehicle/__init__.py):**
- `VehicleOdometerState` eltávolítva az importokból és `__all__`-ból.
**3. FRONTEND - [`ProviderDetailModal.vue`](frontend/src/components/provider/ProviderDetailModal.vue:237):**
- A `formattedAddress` computed property most már tartalmazza az `address_zip` mezőt is.
- Formátum: `"2120 Dunakeszi, Egressy utca 4"`
**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.
### ✅ Verifikáció
- Backend API: `GET /api/v1/providers/search?city=Dunakeszi``address` = `"2120 Dunakeszi, Egressy utca 4"`
- Backend API: Autónyíri Kft. (id=58) → `address_zip=2120`, `address_street_name=Egressy`, `address_street_type=utca`, `address_house_number=4`
- Frontend build: `npm run build` sikeres, 0 hiba ✅
- Backend konténer újraindítva: `docker compose restart sf_api`
**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
## 2026-06-17 - Kártya fő szolgáltatás (category) mindig megjelenítése
### 🎯 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ó.
A szervizkereső kártyákon a fő szolgáltatás (category) mindig látszódjon. Ha van, akkor a kategória neve, ha nincs, akkor egy szaggatott vonalú placeholder.
### 🔧 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.
**1. FRONTEND - [`ServiceFinderView.vue:263`](frontend/src/views/ServiceFinderView.vue:263):**
- A category badge `v-if="provider.category"` helyett `v-if`/`v-else` szerkezet:
- Ha van category: `bg-sf-accent/10` háttér, `text-sf-accent` szín
- Ha nincs: szaggatott vonalú (`border-dashed`) placeholder "Nincs kategória" / "No category"
### ✅ 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.
**2. FRONTEND - [`hu.ts:1237`](frontend/src/i18n/hu.ts:1237):**
- `serviceFinder.noCategory`: `'Nincs kategória'`
## 2026-06-13 - Fix Catalog API 404 Mismatch & Real Database Data
**3. FRONTEND - [`en.ts:1237`](frontend/src/i18n/en.ts:1237):**
- `serviceFinder.noCategory`: `'No category'`
### 🎯 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 (`<Teleport to="body">` 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
### ✅ Verifikáció
- Frontend build: `npm run build` sikeres, 0 hiba ✅