frontend 2026-06-10 bontva a 2 felület

This commit is contained in:
Roo
2026-06-10 08:06:07 +00:00
parent b84b1bab41
commit 90e3173fbc
59 changed files with 8616 additions and 1412 deletions

View File

@@ -0,0 +1,139 @@
# 📐 Logic Spec: Dashboard Header Restructure
## 1. Cél és Masterbook 2 illeszkedés
**Cél:** A privát garázs dashboard fejlécének (`DashboardHeader.vue`) átalakítása a felhasználó specifikációja szerint:
- **Bal:** 👋 Üdvözlés + keresztnév
- **Közép:** `{firstName}_garázsa` (i18n kulcsból)
- **Jobb:** Cégváltó dropdown → Nyelvváltó → Avatár menü (sorrendben)
- **Layout struktúra:** Marad a `PrivateLayout` + `DashboardHeader` felosztás
**Masterbook 2.0 illeszkedés:** A feladat illeszkedik az Epic 11 (Public Frontend) és a B2C dashboard UX specifikációjához.
---
## 2. Jelenlegi Problémák (`DashboardHeader.vue` - 685 sor)
| Probléma | Leírás |
|----------|--------|
| P1 - Túl komplex | 7 funkció egy komponensben (WelcomeCard, Logo, Funkciók menu, Center label, Org dropdown, LanguageSwitcher, Avatar) |
| P2 - Welcome Card | Lebegő elem, nem a header része - megtartható, de auditálni kell |
| P3 - Funkciók menu | Nem illik a header-be, 100+ sor Bento dropdown - **ELTÁVOLÍTANDÓ** |
| P4 - Logo + Brand | 2 sor + kép - **ELTÁVOLÍTANDÓ** a header-ből |
| P5 - Center label | Duplikált logika a jobb oldali org dropdown-nal |
| P6 - Jobb oldal sorrend | Jelenleg: Org dropdown → LanguageSwitcher → Avatar. **Megtartandó sorrendben** |
---
## 3. Új 3-Oszlopos Header Layout
```
┌─────────────────────────────────────────────────────────────────────────┐
│ [BAL] [KÖZÉP] [JOBB] │
│ 👋 Üdv, János! János_garázsa [🏢 Cégem ▼] [🌐] [👤] │
│ ↓ ↓ ↓ │
│ Org Dropdown Lang Avatar│
│ (cég listával) Switcher│
└─────────────────────────────────────────────────────────────────────────┘
```
### 3.1 Bal oldal: Welcome + keresztnév
```vue
<div class="flex items-center gap-2">
<span class="text-lg">👋</span>
<span class="text-sm font-medium text-white/80">
{{ t('header.welcome') }}, <span class="font-semibold text-white">{{ displayName }}</span>!
</span>
</div>
```
**`displayName` computed** - már létezik, elsődlegesen `props.firstName`, fallback email prefix.
### 3.2 Középső: `{firstName}_garázsa`
```vue
<div class="absolute left-1/2 -translate-x-1/2">
<span class="text-sm font-semibold text-white/70 tracking-wide">
{{ centerLabel }}
</span>
</div>
```
**`centerLabel` computed logika:**
- Ha `isOnCompanyGarage` (szervezeti nézetben): `t('header.companyGarageLabel', { name: activeCompanyName })`
- Ha `isCorporateMode` (vállalati módban): `t('header.companyGarageLabel', { name: activeCompanyName })`
- Különben (személyes mód): `t('header.personalGarageLabel', { name: firstName })`
### 3.3 Jobb oldal (sorrendben: Cég → Nyelv → Avatár)
**Cég dropdown** - Meglévő funkció tisztítva:
- Megtartani: org-dropdown-container (dropdown cég listával)
- Eltávolítani: duplikált "Funkciók menüben lévő Organization Menu Item"
- Állapotok: nincs cég → " Új cég" (navigáció `/company/onboard`); van cég → "🏢 Cégem" (dropdown); corporate mód → "👤 Személyes Garázs" (visszaváltás)
**Nyelvváltó** - Változatlan, 6 nyelv támogatása.
**Avatár menü** - Változatlan (Profile, Logout).
---
## 4. Eltávolítandó elemek
| Elem | Sorok | Hova kerül? |
|------|-------|-------------|
| Logo + Brand | 30-43 | ELTÁVOLÍTVA |
| Funkciók menu (Bento) | 46-154 | ELTÁVOLÍTVA, később `DashboardView.vue`-ba |
| Funkciók-belüli Org Menu Item | 119-135 | ELTÁVOLÍTVA (duplikáció) |
**Megtartva:** Floating Welcome Card (2-23. sorok) - vizuálisan elkülönül.
---
## 5. Érintett fájlok
| Fájl | Művelet | Mérték |
|------|---------|--------|
| `frontend/src/components/DashboardHeader.vue` | **ÁTÍRÁS** ~685 → ~300 sor | Nagy |
| `frontend/src/i18n/hu.ts` | Ellenőrzés (várhatóan nincs új kulcs) | Apró |
| `frontend/src/i18n/en.ts` | Ellenőrzés (várhatóan nincs új kulcs) | Apró |
**Nem érintett:** `PrivateLayout.vue`, `OrganizationLayout.vue`, `LanguageSwitcher.vue`, `auth.ts`
---
## 6. AuthStore használt értékei
| Érték | Típus |
|-------|-------|
| `authStore.user?.first_name` | `string \| undefined` |
| `authStore.user?.email` | `string \| undefined` |
| `authStore.myOrganizations` | `OrganizationItem[]` |
| `authStore.hasBusinessOrganization` | `boolean` (computed) |
| `authStore.isCorporateMode` | `boolean` (computed) |
| `authStore.businessOrganization` | `OrganizationItem \| undefined` (computed) |
| `authStore.switchOrganization(id \| null)` | `function` (async) |
---
## 7. Tesztelési terv
1. `vite build` hiba nélkül
2. Vizuális: Bal 👋 Welcome; Közép `{name}_garázsa`; Jobb: Cég → 🌐 → Avatár
3. Cég dropdown működik (list, select, back)
4. LanguageSwitcher működik (6 nyelv)
5. Avatár menü működik (Profile, Logout)
---
## 8. Módosítási sorrend
1. `DashboardHeader.vue` - Teljes átírás
2. `hu.ts` / `en.ts` - Ellenőrzés
3. `vite build` - Verifikáció
---
## ⏸️ Jóváhagyási pont
**Kérem a felhasználó jóváhagyását a fenti specifikációhoz!**

View File

@@ -0,0 +1,232 @@
# logic_spec_opten_integration.md — Opten REST API Integrációs Terv
## Modul Célja
A jelenlegi mock `GET /lookup-tax/{tax_number}` végpont lecserélése valós [Opten REST API](https://www.opten.hu/) hívásra. A frontend [`CompanyOnboardingView.vue`](frontend/src/views/organization/CompanyOnboardingView.vue:654) `lookupTaxNumber()` függvénye már készen áll a valós adatok fogadására — csak a backend oldali integráció hiányzik.
## Masterbook 2.0 Illeszkedés
- **DDD Séma:** `identity.organizations` — a cégadatok automatikus kitöltése csökkenti a hibalehetőséget.
- **Geo-logika:** Csak magyar (HU) adószámok esetén aktív. Más országok esetén a mock marad, vagy külön integráció később.
---
## Érintett Fájlok és Műveletek
| # | Fájl | Művelet | Leírás |
|---|------|---------|--------|
| 1 | [`backend/app/core/config.py`](backend/app/core/config.py:11) | ✏️ Módosítás | `OPTEN_API_KEY`, `OPTEN_API_BASE_URL`, `OPTEN_CACHE_TTL` hozzáadása a `Settings` osztályhoz |
| 2 | [`backend/app/services/opten_service.py`](backend/app/services/opten_service.py) | **ÚJ fájl** | `OptenService` osztály: `httpx.AsyncClient` + `tenacity` retry logika |
| 3 | [`backend/app/schemas/organization.py`](backend/app/schemas/organization.py) | **ÚJ fájl** | `TaxLookupResponse` Pydantic séma |
| 4 | [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:125) | ✏️ Módosítás | Mock végpont → valós `OptenService.lookup_by_tax_number()` hívás |
| 5 | [`backend/requirements.txt`](backend/requirements.txt:17) | 🟢 **Nincs teendő** | `httpx` már szerepel. `tenacity` opcionális (retry logikához) |
| 6 | [`frontend/src/views/organization/CompanyOnboardingView.vue`](frontend/src/views/organization/CompanyOnboardingView.vue:654) | 🟢 **Már kész** | `lookupTaxNumber()` teljes mértékben használható |
---
## Adatmodell — TaxLookupResponse
```python
# backend/app/schemas/organization.py
from pydantic import BaseModel
from typing import Optional
class TaxLookupResponse(BaseModel):
full_name: str
name: str
display_name: Optional[str] = None
address_zip: str
address_city: str
address_street_name: str
address_street_type: Optional[str] = None
address_house_number: str
legal_form: Optional[str] = None # pl. "Kft.", "Bt.", "Zrt."
status: Optional[str] = None # pl. "active", "dissolved"
```
### Frontend Kompatibilitás
A fenti mezők teljes mértékben lefedik a frontend [`CompanyOnboardingView.vue:654-684`](frontend/src/views/organization/CompanyOnboardingView.vue:654) által várt mezőket. Az új `legal_form` és `status` mezők a frontend `tax_lookup_result` objektumban tárolódhatnak későbbi felhasználásra.
---
## Service Réteg — OptenService
### Osztály Felépítése
```python
# backend/app/services/opten_service.py
import logging
from typing import Optional
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential
logger = logging.getLogger(__name__)
class OptenService:
def __init__(self, api_key: str, base_url: str = "https://api.opten.hu/v2"):
self.api_key = api_key
self.base_url = base_url
self.client = httpx.AsyncClient(
base_url=self.base_url,
headers={"X-Api-Key": self.api_key, "Accept": "application/json"},
timeout=10.0
)
@retry(stop=stop_after_attempt(2), wait=wait_exponential(multiplier=1, min=1, max=5))
async def lookup_by_tax_number(self, tax_number: str) -> dict:
"""Lekérdezi az Opten API-t adószám alapján."""
normalized = self._normalize_tax_number(tax_number)
response = await self.client.get(f"/company/{normalized}")
response.raise_for_status()
return self._map_opten_response(response.json())
def _normalize_tax_number(self, raw: str) -> str:
"""Csak számjegyeket tart meg (8 vagy 10 vagy 11 karakter)."""
digits = "".join(c for c in raw if c.isdigit())
# HU adószám: 8 számjegy (egyéni) vagy 10-11 számjegy (cég, áfával)
return digits[:11]
def _map_opten_response(self, data: dict) -> dict:
"""Opten API válasz → TaxLookupResponse formátumba."""
return {
"full_name": data.get("cegnev", ""),
"name": data.get("rovitett_cegnev", data.get("cegnev", "")),
"display_name": data.get("rovitett_cegnev"),
"address_zip": data.get("iranyitoszam", ""),
"address_city": data.get("telepules", ""),
"address_street_name": data.get("kozt_nev", ""),
"address_street_type": data.get("kozt_jelleg", ""),
"address_house_number": str(data.get("hazszam", "")),
"legal_form": data.get("cegforma", ""),
"status": data.get("ceg_statusz", ""),
}
async def close(self):
await self.client.aclose()
```
### Gráciális Fallback
Ha az `OPTEN_API_KEY` nincs beállítva (üres string), a végpont NEM dob hibát, hanem visszaadja a mock adatokat:
```python
# backend/app/api/v1/endpoints/organizations.py
@router.get("/lookup-tax/{tax_number}", response_model=TaxLookupResponse)
async def lookup_tax_number(tax_number: str, db: AsyncSession = Depends(get_db)):
settings = get_settings()
if not settings.OPTEN_API_KEY:
logger.warning("OPTEN_API_KEY not set — returning mock data")
return await _mock_lookup(tax_number)
service = OptenService(settings.OPTEN_API_KEY, settings.OPTEN_API_BASE_URL)
try:
result = await service.lookup_by_tax_number(tax_number)
return result
except httpx.HTTPStatusError as e:
return await _handle_opten_error(e)
finally:
await service.close()
```
---
## Geo-Logika
| Ország | Forrás | Státusz |
|--------|--------|---------|
| Magyarország (HU) | Opten REST API | ✅ Ezen terv |
| Más EU országok | Mock (később: saját provider) | ⏳ Tervezett |
Csak magyar adószámok esetén aktív az Opten hívás. A `_normalize_tax_number()` metódus alapján: ha a szám nem felel meg a HU formátumnak (8-11 számjegy), a végpont mock adatokat ad vissza.
---
## Hibakezelési Mátrix
| Opten Hiba | HTTP Státusz | Felhasználói Üzenet |
|------------|-------------|---------------------|
| 404 — Nincs ilyen cég | 404 | "Nem található cég ezzel az adószámmal." |
| 403 — Rossz API kulcs | 500 | "Belső szolgáltatói hiba. Kérjük, próbáld később." |
| 429 — Rate limit | 429 | "Túl sok lekérdezés. Kérjük, várj egy percet." |
| 422 — Érvénytelen adószám | 422 | "Érvénytelen adószám formátum." |
| 504 — Gateway timeout | 504 | "A szolgáltató nem válaszol. Próbáld újra később." |
| Ismeretlen hiba | 502 | "Váratlan hiba történt. Próbáld újra később." |
### `_handle_opten_error()` pszeudokód
```python
async def _handle_opten_error(e: httpx.HTTPStatusError) -> dict:
status_map = {
404: (404, "Nem található cég ezzel az adószámmal."),
403: (500, "Belső szolgáltatói hiba."),
429: (429, "Túl sok lekérdezés. Várj egy percet."),
422: (422, "Érvénytelen adószám formátum."),
504: (504, "A szolgáltató nem válaszol."),
}
http_code, msg = status_map.get(e.response.status_code, (502, "Váratlan hiba."))
raise HTTPException(status_code=http_code, detail=msg)
```
---
## Biztonsági Megfontolások
| # | Intézkedés | Leírás |
|---|-----------|--------|
| 1 | `.env`-ben tartás | `OPTEN_API_KEY` csak a `.env` fájlban, SOHOHA hardcode-olva |
| 2 | Rate limiting | Nginx/API Gateway szintű korlátozás a `/lookup-tax/` végpontra |
| 3 | Audit log | Minden Opten hívás naplózása: tax_number, timestamp, sikeresség |
| 4 | Cache | `OPTEN_CACHE_TTL` (alapértelmezett: 3600s) — redis cache a gyakori ismétlődésekhez |
| 5 | Adatvédelem | A visszaadott adatok csak a szervezet létrehozásához használhatók |
---
## Teszt Terv
### Unit tesztek
| Teszt | Leírás |
|-------|--------|
| `test_normalize_tax_number` | 8, 10, 11 jegyű, kötőjeles, szóközös bemenetek |
| `test_map_opten_response` | Opten JSON → TaxLookupResponse mezőtérkép |
| `test_mock_fallback` | Ha nincs API kulcs, mock adat jön |
| `test_error_mapping` | Minden hibaeset lefedése |
### Integrációs tesztek
```bash
# Teszt élő Opten API-val (ha van API kulcs)
docker compose exec sf_api python3 -c "
from app.services.opten_service import OptenService
import asyncio
async def test():
s = OptenService('teszt_kulcs')
result = await s.lookup_by_tax_number('12345678')
print(result)
await s.close()
asyncio.run(test())
"
```
---
## Végrehajtási Terv (6 Kártya)
```mermaid
graph TD
A[1. Config: OPTEN_API_KEY] --> B[2. Séma: TaxLookupResponse]
A --> C[3. Service: OptenService]
B --> D[4. Endpoint: Mock → Valós]
C --> D
D --> E[5. Tesztek]
E --> F[6. Dokumentáció]
```
| # | Kártya Név | Függőség | Becsült Méret |
|---|-----------|----------|---------------|
| 1 | Config: `OPTEN_API_KEY` hozzáadása | Nincs | S (< 10 sor) |
| 2 | Séma: `TaxLookupResponse` Pydantic | 1 | S (< 20 sor) |
| 3 | Service: `OptenService` osztály | Nincs | M (~60 sor) |
| 4 | Endpoint: Mock → valós hívás | 2, 3 | S (~20 sor) |
| 5 | Tesztek (unit + smoke) | 4 | M (~50 sor) |
| 6 | Dokumentáció frissítése | 5 | S (< 20 sor) |
---
## Kockázatok
- **Opten API kulcs beszerzése** — a fejlesztés mock módban is végezhető.
- **Opten API változás** — a `_map_opten_response()` metódus könnyen adaptálható.
- **Éles rate limit** — szükség esetén Opten előfizetés frissítése.