From 90e3173fbcda79dc3251bfdcffcc1b73334fc440 Mon Sep 17 00:00:00 2001 From: Roo Date: Wed, 10 Jun 2026 08:06:07 +0000 Subject: [PATCH] =?UTF-8?q?frontend=202026-06-10=20bontva=20a=202=20fel?= =?UTF-8?q?=C3=BClet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .roo/history.md | 287 ++++ backend/app/api/v1/endpoints/documents.py | 2 +- backend/app/api/v1/endpoints/organizations.py | 132 +- backend/app/api/v1/endpoints/users.py | 6 +- backend/app/core/config.py | 17 + backend/app/models/identity/identity.py | 7 + .../app/models/marketplace/organization.py | 7 + backend/app/schemas/organization.py | 32 +- backend/app/schemas/user.py | 4 +- backend/app/services/auth_service.py | 2 +- backend/app/services/geo_service.py | 18 +- backend/app/services/nav_service.py | 410 +++++ ...esz specifikacio_HU_v3.0. (2026.02.12).pdf | Bin 0 -> 11307062 bytes ...auth_registration_full_audit_2026-06-07.md | 890 +++++++++++ docs/company_garage_onboarding_plan.md | 138 ++ ...d_architecture_2_0_feasibility_analysis.md | 239 +++ ...ontend_white_screen_analysis_2026-06-07.md | 114 ++ docs/gitea_open_cards_summary_2026-06-09.md | 145 ++ docs/identity_schema_complete_audit.md | 311 ++++ docs/nav_api_lookup_tax_analysis.md | 111 ++ docs/nav_api_v3_fix_audit.md | 94 ++ docs/nav_api_v3_fix_summary.md | 235 +++ docs/profile_person_user_audit_2026-06-06.md | 191 +++ frontend/package-lock.json | 17 + frontend/package.json | 2 + frontend/public/images/sf_landing_back.png | Bin 0 -> 7330228 bytes frontend/src/assets/sf_landing_back.png | Bin 0 -> 7330228 bytes frontend/src/components/DashboardHeader.vue | 301 ---- frontend/src/components/LanguageSwitcher.vue | 55 +- .../components/header/HeaderBackButton.vue | 63 + .../header/HeaderCompanySwitcher.vue | 224 +++ frontend/src/components/header/HeaderLogo.vue | 39 + .../src/components/header/HeaderProfile.vue | 145 ++ frontend/src/components/layout/BaseHeader.vue | 48 + frontend/src/components/ui/AddressDisplay.vue | 174 +++ frontend/src/components/ui/GlassCard.vue | 77 + frontend/src/i18n/cz.ts | 83 ++ frontend/src/i18n/de.ts | 83 ++ frontend/src/i18n/en.ts | 283 ++++ frontend/src/i18n/hu.ts | 283 ++++ frontend/src/i18n/ro.ts | 83 ++ frontend/src/i18n/sk.ts | 83 ++ frontend/src/layouts/OrganizationLayout.vue | 82 + frontend/src/layouts/PrivateLayout.vue | 48 + frontend/src/main.ts | 71 + frontend/src/router/index.ts | 52 +- frontend/src/stores/auth.ts | 97 ++ frontend/src/stores/theme.ts | 81 + frontend/src/types/organization.ts | 24 + frontend/src/views/CompleteKycView.vue | 768 +++++----- frontend/src/views/DashboardView.vue | 525 ++++--- frontend/src/views/LandingView.vue | 62 +- frontend/src/views/ProfileView.vue | 1319 +++++++++++------ frontend/src/views/VerifyEmailView.vue | 41 +- .../views/organization/CompanyGarageView.vue | 283 ++++ .../organization/CompanyOnboardingView.vue | 761 ++++++++++ frontend/tailwind.config.js | 8 +- ...logic_spec_dashboard_header_restructure.md | 139 ++ plans/logic_spec_opten_integration.md | 232 +++ 59 files changed, 8616 insertions(+), 1412 deletions(-) create mode 100644 backend/app/services/nav_service.py create mode 100644 docs/Online_Szamla_interfesz specifikacio_HU_v3.0. (2026.02.12).pdf create mode 100644 docs/auth_registration_full_audit_2026-06-07.md create mode 100644 docs/company_garage_onboarding_plan.md create mode 100644 docs/frontend_architecture_2_0_feasibility_analysis.md create mode 100644 docs/frontend_white_screen_analysis_2026-06-07.md create mode 100644 docs/gitea_open_cards_summary_2026-06-09.md create mode 100644 docs/identity_schema_complete_audit.md create mode 100644 docs/nav_api_lookup_tax_analysis.md create mode 100644 docs/nav_api_v3_fix_audit.md create mode 100644 docs/nav_api_v3_fix_summary.md create mode 100644 docs/profile_person_user_audit_2026-06-06.md create mode 100644 frontend/public/images/sf_landing_back.png create mode 100644 frontend/src/assets/sf_landing_back.png delete mode 100644 frontend/src/components/DashboardHeader.vue create mode 100644 frontend/src/components/header/HeaderBackButton.vue create mode 100644 frontend/src/components/header/HeaderCompanySwitcher.vue create mode 100644 frontend/src/components/header/HeaderLogo.vue create mode 100644 frontend/src/components/header/HeaderProfile.vue create mode 100644 frontend/src/components/layout/BaseHeader.vue create mode 100644 frontend/src/components/ui/AddressDisplay.vue create mode 100644 frontend/src/components/ui/GlassCard.vue create mode 100644 frontend/src/i18n/cz.ts create mode 100644 frontend/src/i18n/de.ts create mode 100644 frontend/src/i18n/ro.ts create mode 100644 frontend/src/i18n/sk.ts create mode 100644 frontend/src/layouts/OrganizationLayout.vue create mode 100644 frontend/src/layouts/PrivateLayout.vue create mode 100644 frontend/src/stores/theme.ts create mode 100644 frontend/src/types/organization.ts create mode 100644 frontend/src/views/organization/CompanyGarageView.vue create mode 100644 frontend/src/views/organization/CompanyOnboardingView.vue create mode 100644 plans/logic_spec_dashboard_header_restructure.md create mode 100644 plans/logic_spec_opten_integration.md diff --git a/.roo/history.md b/.roo/history.md index c29ddb8..40320bb 100644 --- a/.roo/history.md +++ b/.roo/history.md @@ -1,5 +1,79 @@ # Service Finder Fejlesztési Történet +## 2026-06-08 - F5 Refresh Logout Bug Fix & HeaderLogo Contextual Navigation + +### 🎯 Cél +Két frontend hiba javítása: (1) F5 oldalfrissítéskor a Vue Router guard hamarabb fut le, mint a Pinia Auth Store init() befejeződik, ami kijelentkezést okoz. (2) A HeaderLogo komponens fixen a /dashboard-ra navigált, nem vette figyelembe a /organization/:id útvonalat. + +### 🔧 Változtatások + +**1. `frontend/src/stores/auth.ts`:** +- Új `isInitialized` ref állapot (alapértelmezett: `false`) hozzáadva a store state-hez. +- Az `init()` függvény legvégén `isInitialized.value = true` beállítás, így a router guard meg tudja várni az inicializálás végét. +- Az `isInitialized` exportálva a return objektumban. + +**2. `frontend/src/router/index.ts`:** +- A `beforeEach` guard elején ellenőrzés: ha `!authStore.isInitialized`, akkor `await authStore.init()` meghívása. +- Ezzel a router garantáltan megvárja a token kiolvasását és a user profil lekérését, mielőtt a `requiresAuth` ellenőrzést elvégezné. + +**3. `frontend/src/components/header/HeaderLogo.vue`:** +- `useRoute()` importálva a Vue Router-ből. +- `targetRoute` computed property: ha a jelenlegi útvonal `/organization/`-t tartalmaz, akkor a logó a jelenlegi útvonalra navigál (önmagát tölti újra), egyébként `/dashboard`. +- A prop-alapú `to` helyett a `:to="targetRoute"` használata a template-ben. + +### ✅ Eredmény +- Vite build hiba nélkül lefutott (136 modul, 3.23s). +- Nincs több F5 utáni hamis kijelentkezés. +- A logó szervezeti oldalon nem dobja ki a felhasználót a dashboard-ra. + + +## 2026-06-08 - Issue #237: API Endpoints for visual_settings + +### 🎯 Cél +Backend API végpontok felkészítése a `visual_settings` módosítására. Pydantic sémák (`OrganizationUpdate`, `OrganizationResponse`) létrehozása a `visual_settings` mezővel, valamint `PATCH /organizations/{org_id}/visual-settings` végpont implementálása JSONB merge logikával. + +### ✅ Implementáció + +#### 1. Pydantic Sémák +- **`OrganizationUpdate`**: [`backend/app/schemas/organization.py`](backend/app/schemas/organization.py:40) - Új séma `visual_settings: Optional[dict]`, `display_name`, `default_currency`, `language` mezőkkel +- **`OrganizationResponse`**: [`backend/app/schemas/organization.py`](backend/app/schemas/organization.py:49) - Új DTO séma `visual_settings: Optional[dict]` mezővel, `from_attributes=True` konfigurációval + +#### 2. API Végpont +- **`PATCH /organizations/{org_id}/visual-settings`**: [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:207) - Új végpont JSONB merge logikával: + - OWNER/ADMIN jogosultság ellenőrzés + - Mély merge: a meglévő `visual_settings` kulcsok megmaradnak, csak a kapottak frissülnek + - Pl. `{"theme": "dark"}` küldése nem írja felül a `wall_logo_url`-t + - Egyéb mezők (`display_name`, `language`, `default_currency`) is frissíthetők + +#### 3. Meglévő Végpontok Bővítése +- **`GET /organizations/my`**: [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:203) - `visual_settings` mező hozzáadva a response-hoz +- **`PATCH /users/me/preferences`**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:333) - Már támogatja a `visual_settings`-et a generikus `exclude_unset=True` logikával + +#### 4. Verifikáció +- Python syntax check: minden fájl hibátlanul lefordul +- Import teszt: `OrganizationUpdate`, `OrganizationResponse`, `UserUpdate`, `UserResponse` mindegyike tartalmazza a `visual_settings` mezőt + + +## 2026-06-08 - Issue #228: Add visual_settings to Models + +### 🎯 Cél +`visual_settings` JSONB mező hozzáadása a User és Organization modellekhez, a Pydantic sémák frissítése, majd az adatbázis szinkronizálása a sync_engine segítségével. + +### ✅ Implementáció + +#### 1. SQLAlchemy Modellek +- **User modell**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:157) - `visual_settings` JSONB oszlop hozzáadva a `ui_mode` után, `server_default` JSONB default értékkel (`{"theme": "default", "primary_color": null, "wall_logo_url": null}`) +- **Organization modell**: [`backend/app/models/marketplace/organization.py`](backend/app/models/marketplace/organization.py:109) - `visual_settings` JSONB oszlop hozzáadva az `external_integration_config` után, azonos default értékkel + +#### 2. Pydantic Sémák +- **UserResponse**: [`backend/app/schemas/user.py`](backend/app/schemas/user.py:62) - `visual_settings: Optional[dict]` mező hozzáadva default értékkel +- **UserUpdate**: [`backend/app/schemas/user.py`](backend/app/schemas/user.py:107) - `visual_settings: Optional[dict]` mező hozzáadva + +#### 3. Adatbázis Szinkronizálás +- `sync_engine` sikeresen lefuttatva: 2 hiányzó oszlopot észlelt és hozzáadott (`identity.users.visual_settings` és `fleet.organizations.visual_settings`) +- Verifikáció: mindkét oszlop `jsonb` típusként létezik az adatbázisban + + ## 2026-06-04 (B) - Magic Link Implementáció (Auto-Login Email Verification) ### 🎯 Cél @@ -196,3 +270,216 @@ Az [`tests/active/e2e_profile_address.py`](tests/active/e2e_profile_address.py) - Ha be van állítva → `pw.chromium.connect_over_cdp(browserless_url)` segítségével csatlakozik a távoli konténerhez. - Ha nincs beállítva → lokális `pw.chromium.launch()` fallback, a `HEADLESS` env var figyelembevételével. - **Gitea Issue**: #215 - létrehozva, elindítva és lezárva. + +## 2026-06-07 (C) - NAV API UX & Adatbázis Finomhangolás + +### 🎯 Cél +A NAV API működik, de a felhasználói élmény és az adatbázis szigorúsága finomhangolást igényelt. + +### ✅ Implementáció + +#### 1. Cégjegyzékszám (company_registry_number) opcionálissá tétele +- **Modell**: [`backend/app/models/marketplace/organization.py`](backend/app/models/marketplace/organization.py:89) - `reg_number: Mapped[Optional[str]]` már eleve nullable volt +- **Schema**: [`backend/app/schemas/organization.py`](backend/app/schemas/organization.py:17) - `reg_number: Optional[str] = None` már eleve opcionális volt +- **Adatbázis**: A `sync_engine` audit szerint a séma tökéletesen szinkronban van (1017/1017 elem OK) + +#### 2. Duplikáció szűrése a NAV lekérdezés előtt +- **Végpont**: [`backend/app/api/v1/endpoints/organizations.py`](backend/app/api/v1/endpoints/organizations.py:124) - `GET /lookup-tax/{tax_number}` +- Injektált `db: AsyncSession = Depends(get_db)` függőség +- Mielőtt a NAV-hoz fordul, ellenőrzi az adatbázist az adószám első 8 számjegye (törzsszám) alapján +- Ha létezik aktív cég (`is_deleted == False`), azonnal `HTTPException(status_code=409, detail="Ez az adószám már regisztrálva van a rendszerben.")` hibát dob + +#### 3. Rövid név okosítása (filler words filtering) +- **Service**: [`backend/app/services/nav_service.py`](backend/app/services/nav_service.py:105) - `_format_company_names()` metódus +- `filler_words` lista: `["és", "szolgáltató", "kereskedelmi", "üzleti", "tanácsadó", "ipari", "építőipari", "termelő", "fejlesztő", "kivitelező", "általános"]` +- A rövid név generálásakor a név szavakra bontva, csak a nem-töltelék szavak maradnak meg +- A cégforma rövidítés (pl. "Kft.") a legvégére kerül hozzáfűzésre +- Fallback: ha minden szó töltelék volt, az eredeti név marad + +### ✅ Verifikáció +- Python import check: `nav_service.py` és `organizations.py` sikeresen importálódott +- `sync_engine` audit: 1017/1017 elem OK, 0 hiba, teljes szinkron + +## 2026-06-08 - UI/UX Navigációs Hibák Javítása (3 Bugfix) + +### 🎯 Cél +Három UI/UX és navigációs hiba javítása a Vue 3 frontendben: (1) cégnév/"Cégem" gomb navigáció `/company/garage`-ba, (2) Profil oldal bezárása backdrop overlay-re kattintva, (3) Avatar dropdown bezárása kattintáskor az elemen kívül. + +### ✅ Implementáció + +#### 1. ProfileView.vue - Backdrop Overlay +**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:14) +- Teljes oldalt lefedő backdrop overlay (`fixed inset-0 z-10 bg-black/30 backdrop-blur-[2px]`) a profil kártya körül +- `@click="closeProfile"` a backdrop-on → kattintásra `router.push('/dashboard')` +- `@click.stop` a belső kártya `div`-en → megakadályozza a bezáródást a kártyán belüli kattintáskor +- `handleEscapeKey()`: Escape billentyűre bezárja a profilt (vagy a jelszó módosító modalt, ha az nyitva van) +- `onMounted`/`onUnmounted`: `keydown` eseményfigyelő regisztráció/törlés + +#### 2. DashboardHeader.vue - Cégnév Navigáció +**Fájl**: [`frontend/src/components/DashboardHeader.vue`](frontend/src/components/DashboardHeader.vue:157) +- Cégnév (`activeCompanyName`) ` - - - @@ -35,52 +35,49 @@
- -
+

- Digitális Flotta
- Menedzsment + {{ t('landing.heroTitle') }}
+ {{ t('landing.heroTitleAccent') }}

- Valós idejű költségkövetés, automatikus értesítések és gamifikált pontrendszer – minden egy helyen. + {{ t('landing.heroSubtitle') }}

- -
+
- -
+
-
- Statisztika + {{ t('landing.cardStats') }}
-
-
Kezelt járművek
-
1,245+
+
{{ t('landing.cardStatsLabel') }}
+
{{ t('landing.cardStatsValue') }}
- -
+
-
@@ -91,12 +88,11 @@ - Gamifikáció + {{ t('landing.cardGamification') }}
-
-
Havi Pontszám
+
{{ t('landing.cardGamificationLabel') }}
2,840
@@ -107,21 +103,19 @@
- -
+
-
- Költségek + {{ t('landing.cardCosts') }}
-
@@ -132,7 +126,7 @@ - Üzemanyag + {{ t('landing.cardCostsFuel') }}
32.5k
@@ -141,14 +135,14 @@ - Szerviz + {{ t('landing.cardCostsService') }}
48.0k
- Összesen - 80.5k Ft + {{ t('landing.cardCostsTotal') }} + 80.5k {{ t('landing.cardCostsUnit') }}
@@ -169,14 +163,16 @@ + + diff --git a/frontend/src/views/organization/CompanyOnboardingView.vue b/frontend/src/views/organization/CompanyOnboardingView.vue new file mode 100644 index 0000000..060a6d7 --- /dev/null +++ b/frontend/src/views/organization/CompanyOnboardingView.vue @@ -0,0 +1,761 @@ + + + \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index f409a6a..d47de3d 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -21,7 +21,13 @@ export default { 'sf-dark-lighter': '#0a2a40', 'sf-dark-card': 'rgba(255,255,255,0.04)', 'sf-dark-border': 'rgba(255,255,255,0.10)', - 'sf-dark-glass': 'rgba(255,255,255,0.05)' + 'sf-dark-glass': 'rgba(255,255,255,0.05)', + // ── Dynamic theme (White-labeling) ────────────────────────── + // These reference CSS custom properties set by the theme store. + // Fallback values are used when no custom theme is active. + 'theme-primary': 'var(--theme-primary, #10b981)', + 'theme-secondary': 'var(--theme-secondary, #047857)', + 'theme-bg': 'var(--theme-bg, #0f172a)', }, fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'], diff --git a/plans/logic_spec_dashboard_header_restructure.md b/plans/logic_spec_dashboard_header_restructure.md new file mode 100644 index 0000000..d3fdd43 --- /dev/null +++ b/plans/logic_spec_dashboard_header_restructure.md @@ -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 +
+ 👋 + + {{ t('header.welcome') }}, {{ displayName }}! + +
+``` + +**`displayName` computed** - már létezik, elsődlegesen `props.firstName`, fallback email prefix. + +### 3.2 Középső: `{firstName}_garázsa` + +```vue +
+ + {{ centerLabel }} + +
+``` + +**`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!** diff --git a/plans/logic_spec_opten_integration.md b/plans/logic_spec_opten_integration.md new file mode 100644 index 0000000..f9505c2 --- /dev/null +++ b/plans/logic_spec_opten_integration.md @@ -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.