belépés jó, rgisztráción kicsit faragni kell még
This commit is contained in:
@@ -294,9 +294,15 @@ async def complete_kyc(kyc_in: UserKYCComplete, db: AsyncSession = Depends(get_d
|
|||||||
return {"status": "success", "message": "Fiók aktiválva."}
|
return {"status": "success", "message": "Fiók aktiválva."}
|
||||||
|
|
||||||
@router.get("/me")
|
@router.get("/me")
|
||||||
async def get_current_user_profile(current_user: User = Depends(get_current_user)):
|
async def get_current_user_profile(
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Return current user profile (alias for /users/me).
|
Return current user profile (alias for /users/me).
|
||||||
|
Uses _build_user_response() to ensure proper address field mapping.
|
||||||
"""
|
"""
|
||||||
from app.schemas.user import UserResponse
|
from app.schemas.user import UserResponse
|
||||||
return UserResponse.model_validate(current_user)
|
from app.api.v1.endpoints.users import _build_user_response
|
||||||
|
response_data = _build_user_response(current_user)
|
||||||
|
return UserResponse.model_validate(response_data)
|
||||||
@@ -68,15 +68,15 @@ def _build_user_response(user: User, active_org_id: Optional[int] = None) -> dic
|
|||||||
address_data = None
|
address_data = None
|
||||||
if person.address:
|
if person.address:
|
||||||
address_data = {
|
address_data = {
|
||||||
"zip": getattr(person.address, 'zip', None) or getattr(getattr(person.address, 'postal_code', None), 'zip_code', None),
|
"address_zip": getattr(person.address, 'zip', None) or getattr(getattr(person.address, 'postal_code', None), 'zip_code', None),
|
||||||
"city": getattr(person.address, 'city', None) or getattr(getattr(person.address, 'postal_code', None), 'city', None),
|
"address_city": getattr(person.address, 'city', None) or getattr(getattr(person.address, 'postal_code', None), 'city', None),
|
||||||
"street_name": person.address.street_name,
|
"address_street_name": person.address.street_name,
|
||||||
"street_type": person.address.street_type,
|
"address_street_type": person.address.street_type,
|
||||||
"house_number": person.address.house_number,
|
"address_house_number": person.address.house_number,
|
||||||
"stairwell": person.address.stairwell,
|
"address_stairwell": person.address.stairwell,
|
||||||
"floor": person.address.floor,
|
"address_floor": person.address.floor,
|
||||||
"door": person.address.door,
|
"address_door": person.address.door,
|
||||||
"parcel_id": person.address.parcel_id,
|
"address_hrsz": person.address.parcel_id,
|
||||||
"full_address_text": person.address.full_address_text,
|
"full_address_text": person.address.full_address_text,
|
||||||
"latitude": person.address.latitude,
|
"latitude": person.address.latitude,
|
||||||
"longitude": person.address.longitude,
|
"longitude": person.address.longitude,
|
||||||
@@ -249,13 +249,12 @@ async def update_my_person(
|
|||||||
)
|
)
|
||||||
if addr_id:
|
if addr_id:
|
||||||
person.address_id = addr_id
|
person.address_id = addr_id
|
||||||
|
# EXPLICIT betöltjük az új címet a memóriába, hogy felülírjuk a régit!
|
||||||
|
person.address = await db.get(Address, addr_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await db.commit()
|
await db.commit()
|
||||||
# Refresh to get the latest state
|
|
||||||
await db.refresh(person)
|
await db.refresh(person)
|
||||||
if person.address:
|
|
||||||
await db.refresh(person.address)
|
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
await db.rollback()
|
await db.rollback()
|
||||||
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ from datetime import date, datetime
|
|||||||
|
|
||||||
|
|
||||||
class AddressResponse(BaseModel):
|
class AddressResponse(BaseModel):
|
||||||
"""Beágyazott cím adatok a PersonResponse számára."""
|
"""Beágyazott cím adatok a PersonResponse számára.
|
||||||
zip: Optional[str] = None
|
A mezőnevek address_ előtaggal egyeznek a frontend által várt PersonUpdate formátummal."""
|
||||||
city: Optional[str] = None
|
address_zip: Optional[str] = None
|
||||||
street_name: Optional[str] = None
|
address_city: Optional[str] = None
|
||||||
street_type: Optional[str] = None
|
address_street_name: Optional[str] = None
|
||||||
house_number: Optional[str] = None
|
address_street_type: Optional[str] = None
|
||||||
stairwell: Optional[str] = None
|
address_house_number: Optional[str] = None
|
||||||
floor: Optional[str] = None
|
address_stairwell: Optional[str] = None
|
||||||
door: Optional[str] = None
|
address_floor: Optional[str] = None
|
||||||
parcel_id: Optional[str] = None
|
address_door: Optional[str] = None
|
||||||
|
address_hrsz: Optional[str] = None
|
||||||
full_address_text: Optional[str] = None
|
full_address_text: Optional[str] = None
|
||||||
latitude: Optional[float] = None
|
latitude: Optional[float] = None
|
||||||
longitude: Optional[float] = None
|
longitude: Optional[float] = None
|
||||||
|
|||||||
251
docs/vehicle_api_frontend_architecture.md
Normal file
251
docs/vehicle_api_frontend_architecture.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# 🚗 Service Finder - Jármű API és Frontend Architektúra Elemzés
|
||||||
|
|
||||||
|
> **Verzió:** 1.0
|
||||||
|
> **Dátum:** 2026-06-05
|
||||||
|
> **Szerző:** Rendszer-Architect
|
||||||
|
> **Cél:** A backend jármű API teljes áttekintése és frontend csoportosítási javaslat
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 📊 Backend API Réteg - Teljes Végpont Térkép
|
||||||
|
|
||||||
|
A FastAPI routerek a `backend/app/api/v1/api.py` fájlban vannak összefogva. Az összes járművel kapcsolatos végpont az alábbi prefixek alatt fut:
|
||||||
|
|
||||||
|
### 1.1 `/assets` prefix - Jármű (Asset) Menedzsment
|
||||||
|
|
||||||
|
**Router:** `backend/app/api/v1/endpoints/assets.py`
|
||||||
|
|
||||||
|
| Metódus | Végpont | Cél | Adatbázis kapcsolat |
|
||||||
|
|---------|---------|-----|---------------------|
|
||||||
|
| GET | /assets/vehicles | Felhasználó járműveinek listázása (Garázs). Paginált. Támogatja: személyes mód (owner/operator) és vállalati mód (szervezet garázsai/Branch-ek). | Asset - vehicle.assets tábla |
|
||||||
|
| POST | /assets/vehicles | Új jármű hozzáadása vagy meglévő igénylése. Ellenőrzi a járműlimitet, kezeli a VIN duplikációt, XP jutalmat ad. | Asset |
|
||||||
|
| GET | /assets/{asset_id} | Jármű részletes adatai (katalógus adatokkal együtt). Jogosultság ellenőrzés (tulajdonos/szervezet). | Asset + AssetCatalog |
|
||||||
|
| GET | /assets/{asset_id}/financial-summary | Pénzügyi riport - kategóriákra bontott összesítés (Local/EUR). | AssetCost |
|
||||||
|
| GET | /assets/{asset_id}/costs | Tételes költséglista lapozással. | AssetCost |
|
||||||
|
| GET | /assets/{asset_id}/maintenance | Szerviznapló - karbantartási költségek listája. | AssetCost - cost_category='maintenance' |
|
||||||
|
| POST | /assets/{asset_id}/maintenance | Szervizbejegyzés hozzáadása (date, odometer, description, cost). Létrehoz AssetEvent-et is. | AssetCost + AssetEvent |
|
||||||
|
|
||||||
|
### 1.2 `/catalog` prefix - Jármű Katalógus
|
||||||
|
|
||||||
|
**Router:** `backend/app/api/v1/endpoints/catalog.py`
|
||||||
|
|
||||||
|
| Metódus | Végpont | Cél | Adatbázis kapcsolat |
|
||||||
|
|---------|---------|-----|---------------------|
|
||||||
|
| GET | /catalog/makes | Márkák listázása (opcionálisan vehicle_class szerint szűrve). | AssetCatalog |
|
||||||
|
| GET | /catalog/models | Típusok listázása adott márkához. | AssetCatalog |
|
||||||
|
| GET | /catalog/generations | Generációk/Évjáratok adott márka+típushoz. | AssetCatalog |
|
||||||
|
| GET | /catalog/engines | Motorváltozatok és technikai specifikációk (ID, engine_code, fuel_type, factory_data). | AssetCatalog |
|
||||||
|
|
||||||
|
### 1.3 `/vehicles` prefix - Jármű Értékelések
|
||||||
|
|
||||||
|
**Router:** `backend/app/api/v1/endpoints/vehicles.py`
|
||||||
|
|
||||||
|
| Metódus | Végpont | Cél | Adatbázis kapcsolat |
|
||||||
|
|---------|---------|-----|---------------------|
|
||||||
|
| POST | /vehicles/{vehicle_id}/ratings | Jármű értékelése 4 dimenzióban (driving_experience, reliability, comfort, consumption_satisfaction 1-10). Egy user csak egyszer értékelhet. | VehicleUserRating |
|
||||||
|
| GET | /vehicles/{vehicle_id}/ratings | Értékelések lekérése adott járműhöz. | VehicleUserRating |
|
||||||
|
|
||||||
|
### 1.4 `/expenses` prefix - Flotta Költségek (TCO)
|
||||||
|
|
||||||
|
**Router:** `backend/app/api/v1/endpoints/expenses.py`
|
||||||
|
|
||||||
|
| Metódus | Végpont | Cél | Adatbázis kapcsolat |
|
||||||
|
|---------|---------|-----|---------------------|
|
||||||
|
| POST | /expenses/ | Költség rögzítése (üzemanyag, szerviz, adó, biztosítás). Dynamic Gatekeeper: draft jármű limit ellenőrzés. | AssetCost + SystemParameter |
|
||||||
|
|
||||||
|
### 1.5 `/analytics` prefix - Jármű Analitika
|
||||||
|
|
||||||
|
**Router:** `backend/app/api/v1/endpoints/analytics.py`
|
||||||
|
|
||||||
|
| Metódus | Végpont | Cél | Adatbázis kapcsolat |
|
||||||
|
|---------|---------|-----|---------------------|
|
||||||
|
| GET | /analytics/{vehicle_id}/summary | TCO összesítő járművenként (user costs, lifetime costs, benchmark). | AssetCost, VehicleModelDefinition |
|
||||||
|
| GET | /analytics/dashboard | Dashboard analitika (mock data - havi költségek, üzemanyag-hatékonyság, cost/km trendek). | Jelenleg mock |
|
||||||
|
|
||||||
|
### 1.6 További kapcsolódó végpontok
|
||||||
|
|
||||||
|
| Metódus | Végpont | Cél | Router helye |
|
||||||
|
|---------|---------|-----|-------------|
|
||||||
|
| POST | /documents/upload/{parent_type}/{parent_id} | Dokumentumfeltöltés járműhöz | documents.py |
|
||||||
|
| POST | /evidence/scan-registration | OCR regisztrációs dokumentum szkennelés | evidence.py |
|
||||||
|
| GET | /search/match | Geo-keresés PostGIS segítségével | search.py |
|
||||||
|
| POST | /services/hunt | Szerviz vadászat indítása | services.py |
|
||||||
|
| GET | /reports/summary/{vehicle_id} | Jármű összesítő jelentés | reports.py |
|
||||||
|
| GET | /reports/trends/{vehicle_id} | Jármű trend jelentés | reports.py |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 🗄️ Adatbázis Modell Réteg - Jármű Domain
|
||||||
|
|
||||||
|
### 2.1 Core Asset Model - A "Digital Twin"
|
||||||
|
|
||||||
|
Az Asset modell (backend/app/models/vehicle/asset.py) a központi entitás:
|
||||||
|
|
||||||
|
```
|
||||||
|
Asset ──┬── AssetCatalog (katalógus kapcsolat)
|
||||||
|
├── AssetFinancials (pénzügyi adatok)
|
||||||
|
├── AssetCost (költségnapló)
|
||||||
|
├── AssetEvent (eseménynapló - Digitális Szervizkönyv)
|
||||||
|
├── VehicleLogbook (útnyilvántartás)
|
||||||
|
├── AssetInspection (ellenőrzési napló)
|
||||||
|
├── AssetReview (értékelések)
|
||||||
|
├── AssetTelemetry (telemetria)
|
||||||
|
├── AssetAssignment (szervezeti hozzárendelés)
|
||||||
|
├── VehicleOwnership (tulajdonosváltás)
|
||||||
|
└── ServiceRequest (szervizkérések)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Főbb Modellek Leírása
|
||||||
|
|
||||||
|
| Modell | Tábla (séma) | Cél |
|
||||||
|
|--------|-------------|-----|
|
||||||
|
| Asset | vehicle.assets | Fizikai eszköz (Digital Twin) - Minden adat itt fut össze |
|
||||||
|
| AssetCatalog | vehicle.vehicle_catalog | Jármű katalógus mesteradatok |
|
||||||
|
| VehicleModelDefinition | vehicle.vehicle_model_definitions | Multi-Tier MDM Master - technikai igazságforrás |
|
||||||
|
| VehicleType | vehicle.vehicle_types | Jármű kategóriák |
|
||||||
|
| CostCategory | vehicle.cost_categories | Standardizált költségkategória fa |
|
||||||
|
| AssetCost | vehicle.asset_costs | Üzemeltetési költség (TCO) |
|
||||||
|
| AssetEvent | vehicle.asset_events | Digitális Szervizkönyv események |
|
||||||
|
| VehicleOdometerState | vehicle.vehicle_odometer_states | Km óra állapot és becslés |
|
||||||
|
| VehicleUserRating | vehicle.vehicle_user_ratings | Jármű értékelés 4 dimenzióban |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 🔌 Jelenlegi Frontend Implementáció
|
||||||
|
|
||||||
|
### 3.1 Garázs Nézet (DashboardView.vue)
|
||||||
|
|
||||||
|
A frontend egy egységes garázs nézetet használ, ahol az összes jármű egy grid-ben jelenik meg:
|
||||||
|
|
||||||
|
- Adatforrás: GET /api/v1/assets/vehicles -> vehicleStore.fetchVehicles()
|
||||||
|
- Megjelenítés: Minden jármű egy kártyán, amely tartalmazza az EU-s rendszámtáblát, márka+modell nevet, gyártási évet, állapot score-t, futott km-t, státusz badge-t
|
||||||
|
- UI élmény: Zen mód (UI elrejtése), 3D téreffektusok
|
||||||
|
|
||||||
|
### 3.2 Hiányzó Frontend Funkciók
|
||||||
|
|
||||||
|
A backend által biztosított API-k közül a frontend CSAK a járműlista lekérést használja. Az alábbi funkciók NINCSENEK implementálva:
|
||||||
|
|
||||||
|
- Jármű hozzáadása (POST /assets/vehicles)
|
||||||
|
- Jármű részletes adatlapja (GET /assets/{id})
|
||||||
|
- Költségnapló megjelenítése/rögzítése
|
||||||
|
- Szerviznapló (karbantartási rekordok)
|
||||||
|
- Katalógus böngésző (márka -> modell -> generáció -> motor)
|
||||||
|
- TCO analitika és dashboard
|
||||||
|
- Jármű értékelés
|
||||||
|
- Dokumentumfeltöltés
|
||||||
|
- Útnyilvántartás (logbook)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 🎯 Frontend Csoportosítási Javaslat
|
||||||
|
|
||||||
|
### 4.1 Javasolt Menü Szerkezet
|
||||||
|
|
||||||
|
Az alábbi csoportosítás a DDD (Domain-Driven Design) elveket követi, és a felhasználói élményt (UX) helyezi előtérbe:
|
||||||
|
|
||||||
|
```
|
||||||
|
GARÁZS (fő nézet)
|
||||||
|
│
|
||||||
|
├── 🔧 JÁRMŰVEK
|
||||||
|
│ ├── Garázs áttekintés <- Jelenlegi DashboardView
|
||||||
|
│ ├── Jármű hozzáadása <- POST /assets/vehicles
|
||||||
|
│ ├── Katalógus böngésző <- GET /catalog/makes -> models -> engines
|
||||||
|
│ └── Jármű részletek <- GET /assets/{id}
|
||||||
|
│
|
||||||
|
├── 📊 PÉNZÜGYEK & TCO
|
||||||
|
│ ├── Költségnapló <- GET /assets/{id}/costs, POST /expenses
|
||||||
|
│ ├── Pénzügyi riport <- GET /assets/{id}/financial-summary
|
||||||
|
│ ├── TCO Analitika <- GET /analytics/{id}/summary
|
||||||
|
│ └── Dashboard analitika <- GET /analytics/dashboard
|
||||||
|
│
|
||||||
|
├── 🔧 KARBANTARTÁS
|
||||||
|
│ ├── Szerviznapló <- GET /assets/{id}/maintenance
|
||||||
|
│ ├── Új szerviz bejegyzés <- POST /assets/{id}/maintenance
|
||||||
|
│ ├── Eseménynapló <- AssetEvent adatok
|
||||||
|
│ └── Km óra állapot <- GET /admin/odometer/{id}
|
||||||
|
│
|
||||||
|
├── 📄 DOKUMENTUMOK
|
||||||
|
│ ├── Feltöltés <- POST /documents/upload/vehicle/{id}
|
||||||
|
│ └── OCR szkennelés <- POST /evidence/scan-registration
|
||||||
|
│
|
||||||
|
└── ⭐ KÖZÖSSÉG
|
||||||
|
├── Értékelés <- POST /vehicles/{id}/ratings
|
||||||
|
└── Vélemények <- GET /vehicles/{id}/ratings
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Javasolt Vue Router Struktúra
|
||||||
|
|
||||||
|
```
|
||||||
|
/dashboard Garázs áttekintés (DashboardView)
|
||||||
|
/vehicles/add Jármű hozzáadása
|
||||||
|
/vehicles/catalog Katalógus böngésző
|
||||||
|
/vehicles/{id} Jármű részletes nézet
|
||||||
|
/vehicles/{id}/costs Költségek
|
||||||
|
/vehicles/{id}/maintenance Szerviznapló
|
||||||
|
/vehicles/{id}/analytics TCO Analitika
|
||||||
|
/vehicles/{id}/documents Dokumentumok
|
||||||
|
/vehicles/{id}/ratings Értékelések
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Javasolt Store Bővítés
|
||||||
|
|
||||||
|
A jelenlegi vehicleStore bővítendő az alábbiakkal:
|
||||||
|
|
||||||
|
```
|
||||||
|
ÚJ state-ek:
|
||||||
|
- selectedVehicle Aktuálisan kiválasztott jármű
|
||||||
|
- vehicleCosts Költségnapló tételek
|
||||||
|
- maintenanceRecords Szerviz rekordok
|
||||||
|
- financialSummary Pénzügyi összesítő
|
||||||
|
|
||||||
|
ÚJ katalógus state-ek:
|
||||||
|
- catalogMakes Márkák listája
|
||||||
|
- catalogModels Modellek listája
|
||||||
|
- catalogGenerations Generációk listája
|
||||||
|
- catalogEngines Motorváltozatok listája
|
||||||
|
|
||||||
|
ÚJ action-ök:
|
||||||
|
- fetchVehicleDetail(id)
|
||||||
|
- createVehicle(payload)
|
||||||
|
- fetchCosts(assetId)
|
||||||
|
- createExpense(payload)
|
||||||
|
- fetchMaintenance(assetId)
|
||||||
|
- fetchFinancialSummary(assetId)
|
||||||
|
- fetchAnalytics(assetId)
|
||||||
|
- fetchMakes(vehicleClass?)
|
||||||
|
- fetchModels(make)
|
||||||
|
- fetchGenerations(make, model)
|
||||||
|
- fetchEngines(make, model, gen)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 UX/UI Javaslatok
|
||||||
|
|
||||||
|
1. **Garázs Szűrők:** A járművek szűrhetők legyenek járműosztály (personal, motorcycle, commercial), státusz (active, draft, maintenance) és márka szerint.
|
||||||
|
|
||||||
|
2. **Jármű Kártya Információs Rétegek:**
|
||||||
|
- 1. réteg (alap): Rendszám, márka, modell, státusz - látszik a grid-ben
|
||||||
|
- 2. réteg (hover): Gyors infók (km, állapot, utolsó szerviz)
|
||||||
|
- 3. réteg (kattintás): Teljes részletes nézet
|
||||||
|
|
||||||
|
3. **TCO Dashboard:** A garázs nézet tetején egy összesítő sáv (teljes flotta km, havi költség, átlagos állapot). Minden járműkártyán egy "TCO" gomb.
|
||||||
|
|
||||||
|
4. **Katalógus Böngésző:** Lépcsőzetes dropdown-ok (márka -> modell -> generáció -> motor). Gyors hozzáadás: "Add this to my garage" gomb.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 📝 Összefoglalás
|
||||||
|
|
||||||
|
A backend rendelkezésre álló API végpontok lefedik a járműkezelés teljes életciklusát:
|
||||||
|
|
||||||
|
| Fázis | API Végpontok | Állapot |
|
||||||
|
|-------|--------------|---------|
|
||||||
|
| Felfedezés (Katalógus) | /catalog/makes, /catalog/models, /catalog/generations, /catalog/engines | Backend kész |
|
||||||
|
| Létrehozás | POST /assets/vehicles | Backend kész |
|
||||||
|
| Megtekintés (Garázs) | GET /assets/vehicles, GET /assets/{id} | Backend kész, Frontend részleges |
|
||||||
|
| Költségkövetés (TCO) | POST /expenses/, GET /assets/{id}/costs, GET /assets/{id}/financial-summary | Backend kész, Frontend hiányzik |
|
||||||
|
| Karbantartás | GET/POST /assets/{id}/maintenance | Backend kész, Frontend hiányzik |
|
||||||
|
| Analitika | GET /analytics/{id}/summary, GET /analytics/dashboard | Backend kész (részben mock), Frontend hiányzik |
|
||||||
|
| Értékelés | GET/POST /vehicles/{id}/ratings | Backend kész, Frontend hiányzik |
|
||||||
|
| Dokumentumok | POST /documents/upload/{parent_type}/{parent_id} | Backend kész, Frontend hiányzik |
|
||||||
|
|
||||||
|
**Következtetés:** A backend erős alapokon áll a járműkezeléshez. A frontenden a garázs nézet (DashboardView) csak a járművek listázását valósítja meg. A javasolt csoportosítás (Járművek -> Pénzügyek -> Karbantartás -> Dokumentumok -> Közösség) egy logikus, felhasználó-központú navigációs struktúrát adna a teljes jármű életciklus kezeléséhez.
|
||||||
@@ -3,6 +3,12 @@ import { ref, computed } from 'vue'
|
|||||||
import router from '../router'
|
import router from '../router'
|
||||||
import api from '../api/axios'
|
import api from '../api/axios'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend now sends address fields directly on the person object
|
||||||
|
* with address_ prefix (e.g. address_city, address_zip, address_floor).
|
||||||
|
* This interface is kept for backward compatibility but the actual
|
||||||
|
* data comes as flat fields on PersonData.
|
||||||
|
*/
|
||||||
export interface AddressData {
|
export interface AddressData {
|
||||||
zip: string | null
|
zip: string | null
|
||||||
city: string | null
|
city: string | null
|
||||||
@@ -28,7 +34,20 @@ export interface PersonData {
|
|||||||
identity_docs: Record<string, any> | null
|
identity_docs: Record<string, any> | null
|
||||||
ice_contact: Record<string, any> | null
|
ice_contact: Record<string, any> | null
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
|
/** @deprecated Backend now sends address_* fields flat on person */
|
||||||
address: AddressData | null
|
address: AddressData | null
|
||||||
|
// Flat address fields (new backend format)
|
||||||
|
address_zip: string | null
|
||||||
|
address_city: string | null
|
||||||
|
address_street_name: string | null
|
||||||
|
address_street_type: string | null
|
||||||
|
address_house_number: string | null
|
||||||
|
address_stairwell: string | null
|
||||||
|
address_floor: string | null
|
||||||
|
address_door: string | null
|
||||||
|
address_hrsz: string | null
|
||||||
|
address_parcel_id: string | null
|
||||||
|
address_full_text: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserProfile {
|
export interface UserProfile {
|
||||||
|
|||||||
@@ -403,13 +403,13 @@
|
|||||||
|
|
||||||
<div v-else class="text-sm space-y-1">
|
<div v-else class="text-sm space-y-1">
|
||||||
<p class="text-white font-medium">
|
<p class="text-white font-medium">
|
||||||
<template v-if="authStore.user?.person?.address?.zip || authStore.user?.person?.address?.city">
|
<template v-if="authStore.user?.person?.address?.address_zip || authStore.user?.person?.address?.address_city">
|
||||||
{{ authStore.user?.person?.address?.zip }} {{ authStore.user?.person?.address?.city }},
|
{{ authStore.user?.person?.address?.address_zip }} {{ authStore.user?.person?.address?.address_city }},
|
||||||
</template>
|
</template>
|
||||||
{{ authStore.user?.person?.address?.street_name }} {{ authStore.user?.person?.address?.street_type }} {{ authStore.user?.person?.address?.house_number }}
|
{{ authStore.user?.person?.address?.address_street_name }} {{ authStore.user?.person?.address?.address_street_type }} {{ authStore.user?.person?.address?.address_house_number }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="authStore.user?.person?.address?.stairwell || authStore.user?.person?.address?.floor || authStore.user?.person?.address?.door" class="text-white font-medium">
|
<p v-if="authStore.user?.person?.address?.address_stairwell || authStore.user?.person?.address?.address_floor || authStore.user?.person?.address?.address_door" class="text-white font-medium">
|
||||||
Lépcsőház: {{ authStore.user?.person?.address?.stairwell || '—' }}, Emelet: {{ authStore.user?.person?.address?.floor || '—' }}, Ajtó: {{ authStore.user?.person?.address?.door || '—' }}
|
Lépcsőház: {{ authStore.user?.person?.address?.address_stairwell || '—' }}, Emelet: {{ authStore.user?.person?.address?.address_floor || '—' }}, Ajtó: {{ authStore.user?.person?.address?.address_door || '—' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -427,8 +427,8 @@
|
|||||||
<!-- ── 🩻 DIAGNOSTIC JSON DUMP ──────────────────────────────────── -->
|
<!-- ── 🩻 DIAGNOSTIC JSON DUMP ──────────────────────────────────── -->
|
||||||
<div class="rounded-2xl border border-white/10 bg-black/40 p-4 mt-6 backdrop-blur-xl shadow-xl shadow-black/20">
|
<div class="rounded-2xl border border-white/10 bg-black/40 p-4 mt-6 backdrop-blur-xl shadow-xl shadow-black/20">
|
||||||
<details>
|
<details>
|
||||||
<summary class="text-xs font-semibold text-[#00E5A0] cursor-pointer select-none mb-2">🩻 RÖNTGEN: authStore.user?.person?.address</summary>
|
<summary class="text-xs font-semibold text-[#00E5A0] cursor-pointer select-none mb-2">🩻 RÖNTGEN: person (address_* fields)</summary>
|
||||||
<pre class="text-xs text-green-400 bg-black p-4 mt-2 overflow-auto rounded-lg max-h-64">{{ JSON.stringify(authStore.user?.person?.address, null, 2) }}</pre>
|
<pre class="text-xs text-green-400 bg-black p-4 mt-2 overflow-auto rounded-lg max-h-64">{{ JSON.stringify(authStore.user?.person, null, 2) }}</pre>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -575,7 +575,6 @@ const isPageLoading = ref(true)
|
|||||||
|
|
||||||
// ── Computed helpers ──────────────────────────────────────────────────
|
// ── Computed helpers ──────────────────────────────────────────────────
|
||||||
const person = computed(() => authStore.user?.person ?? null)
|
const person = computed(() => authStore.user?.person ?? null)
|
||||||
const address = computed(() => person.value?.address ?? null)
|
|
||||||
const identityDocs = computed(() => person.value?.identity_docs ?? null)
|
const identityDocs = computed(() => person.value?.identity_docs ?? null)
|
||||||
const isCityLoading = ref(false)
|
const isCityLoading = ref(false)
|
||||||
|
|
||||||
@@ -641,7 +640,6 @@ function formatDate(dateVal: string | null | undefined): string {
|
|||||||
|
|
||||||
function startEditing() {
|
function startEditing() {
|
||||||
const p = person.value
|
const p = person.value
|
||||||
const a = address.value
|
|
||||||
const docs = identityDocs.value
|
const docs = identityDocs.value
|
||||||
|
|
||||||
editForm.first_name = p?.first_name || ''
|
editForm.first_name = p?.first_name || ''
|
||||||
@@ -659,15 +657,16 @@ function startEditing() {
|
|||||||
editForm.license_expiry = formatDate(docs?.LICENSE?.expiry_date) || ''
|
editForm.license_expiry = formatDate(docs?.LICENSE?.expiry_date) || ''
|
||||||
editForm.license_categories = docs?.LICENSE?.categories || ''
|
editForm.license_categories = docs?.LICENSE?.categories || ''
|
||||||
|
|
||||||
// Address
|
// Address — backend sends address_* fields nested inside person.address
|
||||||
editForm.address_zip = a?.zip || ''
|
const addr = p?.address
|
||||||
editForm.address_city = a?.city || ''
|
editForm.address_zip = addr?.address_zip || ''
|
||||||
editForm.address_street_name = a?.street_name || ''
|
editForm.address_city = addr?.address_city || ''
|
||||||
editForm.address_street_type = a?.street_type || ''
|
editForm.address_street_name = addr?.address_street_name || ''
|
||||||
editForm.address_house_number = a?.house_number || ''
|
editForm.address_street_type = addr?.address_street_type || ''
|
||||||
editForm.address_stairwell = a?.stairwell || ''
|
editForm.address_house_number = addr?.address_house_number || ''
|
||||||
editForm.address_floor = a?.floor || ''
|
editForm.address_stairwell = addr?.address_stairwell || ''
|
||||||
editForm.address_door = a?.door || ''
|
editForm.address_floor = addr?.address_floor || ''
|
||||||
|
editForm.address_door = addr?.address_door || ''
|
||||||
|
|
||||||
saveMessage.value = ''
|
saveMessage.value = ''
|
||||||
isEditing.value = true
|
isEditing.value = true
|
||||||
|
|||||||
253
tests/active/e2e_profile_address.py_old
Normal file
253
tests/active/e2e_profile_address.py_old
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Playwright E2E Test: Profile Address Persistence (Stairwell / Floor / Door)
|
||||||
|
|
||||||
|
This test:
|
||||||
|
1. Logs in as the configured test user (via LoginModal on the landing page).
|
||||||
|
2. Navigates to the Profile page (/profile).
|
||||||
|
3. Enters edit mode, fills every address field (city, zip, street, house number,
|
||||||
|
stairwell, floor, door), and saves.
|
||||||
|
4. Reloads the page and verifies that the stairwell, floor, and door values
|
||||||
|
persisted after the page refresh.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- playwright (Python) + dotenv
|
||||||
|
- .env.test file at the project root with TEST_USER_EMAIL and TEST_USER_PASS
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python tests/active/e2e_profile_address.py
|
||||||
|
|
||||||
|
To see the browser visually, change HEADLESS=False below.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout
|
||||||
|
|
||||||
|
|
||||||
|
# ── Configuration ──────────────────────────────────────────────────────────
|
||||||
|
BASE_URL = "https://app.servicefinder.hu"
|
||||||
|
HEADLESS = True # Set to False for visual debugging
|
||||||
|
SLOW_MO = 0 # ms delay between actions (useful for debugging)
|
||||||
|
ACTION_TIMEOUT = 15_000 # ms – default timeout for element lookups
|
||||||
|
|
||||||
|
# ── Load .env.test from project root ──────────────────────────────────────
|
||||||
|
ENV_PATH = Path(__file__).resolve().parents[1] / ".env.test"
|
||||||
|
if not ENV_PATH.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f".env.test not found at {ENV_PATH}. "
|
||||||
|
"Create it with TEST_USER_EMAIL and TEST_USER_PASS."
|
||||||
|
)
|
||||||
|
|
||||||
|
load_dotenv(dotenv_path=ENV_PATH, override=True)
|
||||||
|
|
||||||
|
TEST_EMAIL = os.getenv("TEST_USER_EMAIL")
|
||||||
|
TEST_PASS = os.getenv("TEST_USER_PASS")
|
||||||
|
|
||||||
|
if not TEST_EMAIL or not TEST_PASS:
|
||||||
|
raise ValueError(
|
||||||
|
"TEST_USER_EMAIL and/or TEST_USER_PASS are missing from .env.test. "
|
||||||
|
f"Loaded from: {ENV_PATH}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Test data ──────────────────────────────────────────────────────────────
|
||||||
|
ADDRESS_DATA = {
|
||||||
|
"address_city": "Dunakeszi",
|
||||||
|
"address_zip": "2120",
|
||||||
|
"address_street_name": "Határ",
|
||||||
|
"address_street_type": "utca",
|
||||||
|
"address_house_number": "8",
|
||||||
|
"address_stairwell": "A",
|
||||||
|
"address_floor": "3",
|
||||||
|
"address_door": "5",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Helper functions
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def log(msg: str) -> None:
|
||||||
|
"""Simple stdout logger."""
|
||||||
|
print(f"[E2E] {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
def fill_input(page, placeholder: str, value: str) -> None:
|
||||||
|
"""Locate an <input> by its placeholder and fill it."""
|
||||||
|
locator = page.locator(f'input[placeholder="{placeholder}"]')
|
||||||
|
locator.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
locator.fill(value)
|
||||||
|
log(f" Filled '{placeholder}' → '{value}'")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Main test flow
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
with sync_playwright() as pw:
|
||||||
|
# ── 1. Launch browser (local or remote via BROWSERLESS_URL) ──
|
||||||
|
browserless_url = os.getenv("BROWSERLESS_URL")
|
||||||
|
if browserless_url:
|
||||||
|
# Connect to a remote Browserless / Chrome container
|
||||||
|
browser = pw.chromium.connect_over_cdp(browserless_url)
|
||||||
|
log(f"Connected to remote browser at {browserless_url}")
|
||||||
|
else:
|
||||||
|
# Local fallback
|
||||||
|
browser = pw.chromium.launch(
|
||||||
|
headless=os.getenv("HEADLESS", "True").lower() == "true",
|
||||||
|
slow_mo=SLOW_MO,
|
||||||
|
)
|
||||||
|
log(f"Browser launched locally (headless={os.getenv('HEADLESS', 'True')})")
|
||||||
|
context = browser.new_context(
|
||||||
|
viewport={"width": 1280, "height": 800},
|
||||||
|
locale="hu-HU",
|
||||||
|
)
|
||||||
|
page = context.new_page()
|
||||||
|
log(f"Browser launched (headless={HEADLESS})")
|
||||||
|
|
||||||
|
# ── 2. Open landing page & login ─────────────────────────────
|
||||||
|
log("Navigating to landing page …")
|
||||||
|
page.goto(BASE_URL, wait_until="networkidle", timeout=30_000)
|
||||||
|
|
||||||
|
# Click the "Garázs Nyitása" button to open the LoginModal
|
||||||
|
login_trigger = page.locator("button:has-text('Garázs Nyitása')")
|
||||||
|
login_trigger.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
login_trigger.click()
|
||||||
|
log("Login modal opened")
|
||||||
|
|
||||||
|
# Fill email & password
|
||||||
|
page.fill("#login-email", TEST_EMAIL)
|
||||||
|
page.fill("#login-password", TEST_PASS)
|
||||||
|
log("Login credentials entered")
|
||||||
|
|
||||||
|
# Click the submit button (the first .btn-premium inside the visible form)
|
||||||
|
submit_btn = page.locator("form button[type='submit']").first
|
||||||
|
submit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
submit_btn.click()
|
||||||
|
log("Login submitted – waiting for navigation …")
|
||||||
|
|
||||||
|
# Wait for redirect away from landing page (to /dashboard or /complete-kyc)
|
||||||
|
try:
|
||||||
|
page.wait_for_url(
|
||||||
|
lambda url: "/dashboard" in url or "/complete-kyc" in url,
|
||||||
|
timeout=15_000,
|
||||||
|
)
|
||||||
|
except PwTimeout:
|
||||||
|
# If still on landing, maybe login failed – dump state
|
||||||
|
log(f"URL after login attempt: {page.url}")
|
||||||
|
page.screenshot(path="/tmp/e2e_login_fail.png")
|
||||||
|
raise AssertionError(
|
||||||
|
"Login failed – did not redirect to dashboard. "
|
||||||
|
"Check credentials or UI state."
|
||||||
|
)
|
||||||
|
|
||||||
|
log(f"Login successful – current URL: {page.url}")
|
||||||
|
|
||||||
|
# ── 3. Navigate to Profile page ──────────────────────────────
|
||||||
|
log("Navigating to /profile …")
|
||||||
|
page.goto(f"{BASE_URL}/profile", wait_until="networkidle", timeout=20_000)
|
||||||
|
|
||||||
|
# Wait for the profile page to finish loading (spinner disappears)
|
||||||
|
page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT)
|
||||||
|
# Also wait until the loading spinner is gone
|
||||||
|
page.wait_for_function(
|
||||||
|
"() => !document.querySelector('.animate-spin')",
|
||||||
|
timeout=15_000,
|
||||||
|
)
|
||||||
|
log("Profile page loaded")
|
||||||
|
|
||||||
|
# ── 4. Enter edit mode ───────────────────────────────────────
|
||||||
|
edit_btn = page.locator("button:has-text('Szerkesztés')")
|
||||||
|
edit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
edit_btn.click()
|
||||||
|
log("Edit mode activated")
|
||||||
|
|
||||||
|
# ── 5. Fill in all address fields ────────────────────────────
|
||||||
|
log("Filling address form …")
|
||||||
|
fill_input(page, "Irányítószám", ADDRESS_DATA["address_zip"])
|
||||||
|
fill_input(page, "Város", ADDRESS_DATA["address_city"])
|
||||||
|
fill_input(page, "Utca", ADDRESS_DATA["address_street_name"])
|
||||||
|
fill_input(page, "Közterület jellege", ADDRESS_DATA["address_street_type"])
|
||||||
|
fill_input(page, "Házszám", ADDRESS_DATA["address_house_number"])
|
||||||
|
fill_input(page, "Lépcsőház", ADDRESS_DATA["address_stairwell"])
|
||||||
|
fill_input(page, "Emelet", ADDRESS_DATA["address_floor"])
|
||||||
|
fill_input(page, "Ajtó", ADDRESS_DATA["address_door"])
|
||||||
|
|
||||||
|
# ── 6. Save ──────────────────────────────────────────────────
|
||||||
|
save_btn = page.locator("button:has-text('Mentés')")
|
||||||
|
save_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
|
||||||
|
save_btn.click()
|
||||||
|
log("Save clicked – waiting for confirmation …")
|
||||||
|
|
||||||
|
# Wait for the success message to appear
|
||||||
|
success_locator = page.locator("text=A profil adatok és okmányok sikeresen frissítve!")
|
||||||
|
try:
|
||||||
|
success_locator.wait_for(state="visible", timeout=10_000)
|
||||||
|
log("Save succeeded – success message visible")
|
||||||
|
except PwTimeout:
|
||||||
|
# Check for error message
|
||||||
|
error_locator = page.locator(".text-red-400")
|
||||||
|
if error_locator.is_visible():
|
||||||
|
error_text = error_locator.text_content()
|
||||||
|
log(f"Save FAILED with error: {error_text}")
|
||||||
|
page.screenshot(path="/tmp/e2e_save_fail.png")
|
||||||
|
raise AssertionError(f"Save failed: {error_text}")
|
||||||
|
|
||||||
|
page.screenshot(path="/tmp/e2e_save_timeout.png")
|
||||||
|
raise AssertionError(
|
||||||
|
"Save timeout – neither success nor error message appeared."
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── 7. Reload and verify persistence ─────────────────────────
|
||||||
|
log("Reloading page to verify persistence …")
|
||||||
|
page.reload(wait_until="networkidle")
|
||||||
|
page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT)
|
||||||
|
page.wait_for_function(
|
||||||
|
"() => !document.querySelector('.animate-spin')",
|
||||||
|
timeout=15_000,
|
||||||
|
)
|
||||||
|
log("Page reloaded")
|
||||||
|
|
||||||
|
# Read the display text in the read-only address section
|
||||||
|
# The read-only view shows: Lépcsőház: …, Emelet: …, Ajtó: …
|
||||||
|
page_body = page.locator("body").inner_text()
|
||||||
|
log("── Read-only address block ──")
|
||||||
|
|
||||||
|
# Use a regex to extract values
|
||||||
|
stairwell_match = re.search(r"Lépcsőház:\s*(\S+)", page_body)
|
||||||
|
floor_match = re.search(r"Emelet:\s*(\S+)", page_body)
|
||||||
|
door_match = re.search(r"Ajtó:\s*(\S+)", page_body)
|
||||||
|
|
||||||
|
stairwell_val = stairwell_match.group(1) if stairwell_match else ""
|
||||||
|
floor_val = floor_match.group(1) if floor_match else ""
|
||||||
|
door_val = door_match.group(1) if door_match else ""
|
||||||
|
|
||||||
|
log(f" Stairwell (Lépcsőház): '{stairwell_val}'")
|
||||||
|
log(f" Floor (Emelet): '{floor_val}'")
|
||||||
|
log(f" Door (Ajtó): '{door_val}'")
|
||||||
|
|
||||||
|
# ── 8. Assertion: all three values must be present ───────────
|
||||||
|
errors = []
|
||||||
|
if not stairwell_val or stairwell_val in ("—", "null", "None"):
|
||||||
|
errors.append(f"stairwell (expected '{ADDRESS_DATA['address_stairwell']}', got '{stairwell_val}')")
|
||||||
|
if not floor_val or floor_val in ("—", "null", "None"):
|
||||||
|
errors.append(f"floor (expected '{ADDRESS_DATA['address_floor']}', got '{floor_val}')")
|
||||||
|
if not door_val or door_val in ("—", "null", "None"):
|
||||||
|
errors.append(f"door (expected '{ADDRESS_DATA['address_door']}', got '{door_val}')")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
page.screenshot(path="/tmp/e2e_persistence_fail.png")
|
||||||
|
raise AssertionError(
|
||||||
|
"Hibás backend mentés! Az emelet/ajtó adatok elvesztek az oldal "
|
||||||
|
f"frissítése után.\n Missing/empty fields: {', '.join(errors)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
log("Sikeres E2E teszt: Az adatok megmaradtak a backend frissítés után is!")
|
||||||
|
log("ALL CHECKS PASSED.\n")
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user