felhasználói felületre pü

This commit is contained in:
Roo
2026-07-27 08:39:18 +00:00
parent c9118bf52f
commit 6f28d3e70d
72 changed files with 6311 additions and 749 deletions

View File

@@ -5,565 +5,45 @@
---
## P0 EPIC - UI/UX Overhaul for Quick Fuel Logging (Gitea #404)
## Fix: 401 Unauthorized on Admin Ledger Endpoint (Gitea #413)
**Dátum:** 2026-07-09
**Scope:** Backend + Frontend (SimpleFuelModal, ProviderAutocomplete, ProviderQuickAddModal)
**Dátum:** 2026-07-25
### 🔧 Módosított fájlok
#### 1. Backend: [`providers.py`](backend/app/api/v1/endpoints/providers.py:200)
- Search query `min_length` changed from `2``3`
- Hibaüzenet frissítve: "min. 3 karakter"
#### 2. Backend: [`provider_service.py`](backend/app/services/provider_service.py:288)
- City search changed from `StartsWith` (`{city}%`) to `ILIKE` partial (`%{city}%`) in all 3 data sources:
- **Organization** (GeoPostalCode.city)
- **ServiceStaging** (ServiceStaging.city)
- **ServiceProvider** (ServiceProvider.address)
- Zip code search also changed to partial ILIKE
#### 3. Frontend: [`ProviderAutocomplete.vue`](frontend_app/src/components/provider/ProviderAutocomplete.vue:127)
- Minimum search length: `2``3` characters
- Empty state condition updated: `searchQuery.length >= 3`
#### 4. Frontend: [`ProviderQuickAddModal.vue`](frontend_app/src/components/provider/ProviderQuickAddModal.vue:539)
- New `prefillName?: string` prop added
- Watch on `isOpen` now sets `form.name = props.prefillName` when provided
#### 5. Frontend: [`SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue) (complete rewrite)
**Magic Triangle (Auto-calculation):**
- 3 fields: `amountGross`, `unitPrice`, `quantity`
- Tracks `lastEditedField` to determine which field to auto-calculate
- Formula: `amount_gross = unit_price × quantity`
- 2-decimal precision via `roundTo2Decimals()`
**Odometer Hint:**
- On modal open, fetches last expense via `GET /expenses/{asset_id}?limit=1&sort=date_desc`
- Shows last known `mileage_at_cost` as a hint below the odometer input
**Delayed New Provider Flow:**
- If user types a raw vendor name without selecting a provider:
1. Expense is saved with `external_vendor_name` set
2. `unit_price` and `quantity` are sent in `data` JSONB payload
3. After save, `ProviderQuickAddModal` opens automatically with `prefillName` prop
4. User can create the provider with the name pre-filled
### ✅ Verifikáció
1. **Sync Engine**`docker exec sf_api python -m app.scripts.sync_engine` → 1287/1287 elements OK, 0 error
2. **Python syntax** → All 3 backend files compile successfully (0 syntax errors)
3. **Vue TypeScript**`vue-tsc --noEmit` → Only pre-existing errors (Logo.vue.js, logo1.vue.js), no new errors
4. **Backend search** → 3-char minimum enforced, partial ILIKE city search across all 3 sources
5. **Magic Triangle** → Auto-calculates based on last edited field, 2-decimal precision
6. **Delayed New Provider** → Expense saved first, then ProviderQuickAddModal opens with prefillName
...
---
## P0 BUGFIX: SimpleFuelModal 500 POST crash + 5 frontend issues (Gitea #405)
## Fix: POST /api/v1/expenses/ — 500 Internal Server Error (PG ENUM vs varchar JOIN)
**Dátum:** 2026-07-10
**Scope:** Backend (expenses.py, provider_service.py) + Frontend (SimpleFuelModal.vue)
**Dátum:** 2026-07-26
### 🔍 Root Cause Analysis: 500 POST /expenses/ crash
### Probléma
A `POST /api/v1/expenses/` végpont 500-as hibát dobott insurance (biztosítás) költség létrehozásakor.
**Hiba:** `ForeignKeyViolationError` on `fk_asset_costs_vendor_org`
### Gyökér-ok (két rétegben)
1. **Stale `.pyc` cache**`ModuleNotFoundError: No module named 'app.models.fleet.org_role'` — A konténer régi bytecode-ot futtatott. Megoldva cache törléssel.
2. **PG ENUM vs varchar típusütközés**`operator does not exist: fleet.orguserrole = character varying` — A `_check_org_capability()` függvény JOIN-t használt `OrganizationMember.role` (ENUM) és `OrgRole.name_key` (varchar) között, amit a PostgreSQL nem tud implicit konvertálni.
**Kiváltó ok:** A frontend [`SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue) a `vendor_organization_id` mezőbe mindig a `selectedProvider.value!.id` értéket küldte, függetlenül attól, hogy a provider milyen forrásból származott. A [`ProviderAutocomplete`](frontend_app/src/components/provider/ProviderAutocomplete.vue) három forrásból ad vissza találatokat:
### Javítás
[`expenses.py:93-117`](backend/app/api/v1/endpoints/expenses.py:93):
- A JOIN-os lekérdezést két különálló SELECT-re bontottam (az `assets.py:_get_user_permissions()` mintájára)
- **1. lépés:** `select(OrganizationMember.role)` → Python string (asyncpg auto-konvertálja az ENUM-ot)
- **2. lépés:** `select(OrgRole.permissions).where(OrgRole.name_key == role_string)` — varchar=varchar összehasonlítás
- Függvény interfésze változatlan
| Source | Tábla | ID típus |
|--------|-------|----------|
| `verified_org` | `fleet.organizations` | Organization.id ✅ |
| `staged_data` | `marketplace.service_staging` | ServiceStaging.id ❌ |
| `crowd_added` | `marketplace.service_providers` | ServiceProvider.id ❌ |
Az FK constraint `fk_asset_costs_vendor_org` a [`fleet_finance.asset_costs.vendor_organization_id`](backend/app/models/fleet_finance/models.py:130) oszlopon van, ami a `fleet.organizations.id`-ra hivatkozik. Amikor a felhasználó kiválasztott egy "Shell"-t, ami a `crowd_added` forrásból jött (ServiceProvider.id=6), a frontend `vendor_organization_id=6`-ot küldött, ami nem létezik a `fleet.organizations` táblában → **500-as hiba**.
### 🔧 FIX 1: Backend FK-safe validation (expenses.py)
**Fájl:** [`backend/app/api/v1/endpoints/expenses.py`](backend/app/api/v1/endpoints/expenses.py:618)
A `create_expense` végpontban, mielőtt létrehoznánk az `AssetCost` rekordot, egy FK-ellenőrző blokk fut le:
```python
# ── P0 BUGFIX (2026-07-10): FK-safe vendor_organization_id validation ──
resolved_vendor_org_id = expense.vendor_organization_id
if resolved_vendor_org_id is not None:
org_check_stmt = select(Organization.id).where(
Organization.id == resolved_vendor_org_id,
Organization.is_deleted == False,
)
org_check_result = await db.execute(org_check_stmt)
org_exists = org_check_result.scalar_one_or_none()
if org_exists is None:
logger.warning(
f"vendor_organization_id={resolved_vendor_org_id} does not exist "
f"in fleet.organizations. Setting to None and using service_provider_id."
)
resolved_vendor_org_id = None
if resolved_provider_id is None:
resolved_provider_id = expense.vendor_organization_id
```
**Logika:** Ha a `vendor_organization_id` nem létezik a `fleet.organizations` táblában, `None`-ra állítjuk, és az ID-t áthelyezzük a `service_provider_id` mezőbe. Ez biztosítja, hogy:
1. Az FK constraint soha nem sérül meg
2. A provider referencia nem vész el (átkerül a `service_provider_id`-ba)
3. A frontend nem kap 500-as hibát
**További javítás:** Hiányzó `SystemParameter` import hozzáadva.
### 🔧 FIX 2: Frontend source-aware provider routing (SimpleFuelModal.vue)
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:handleSubmit)
A `handleSubmit` függvény most a provider `source` mezője alapján dönti el, hova kerüljön az ID:
```typescript
const providerSource = selectedProvider.value?.source || null
const payload: Record<string, any> = {
vendor_organization_id: hasSelectedProvider && providerSource === 'verified_org'
? selectedProvider.value!.id
: null,
service_provider_id: hasSelectedProvider && providerSource === 'crowd_added'
? selectedProvider.value!.id
: null,
// ...
}
```
**Logika:**
- `verified_org``vendor_organization_id` (FK biztonságos)
- `crowd_added``service_provider_id` (FK nélküli mező)
- `staged_data` → egyik sem (még nincs véglegesítve)
### 🔧 FIX 3: Currency selector hozzáadása
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:template)
A `form.currency` mező most már HUF/EUR választóval rendelkezik:
```html
<select v-model="form.currency" class="w-20 rounded-xl ...">
<option value="HUF">HUF</option>
<option value="EUR">EUR</option>
</select>
```
A `form` state tartalmazza: `currency: 'HUF' as string`
### 🔧 FIX 4: Magic Triangle @blur events
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:script)
**Probléma:** A korábbi `@input` események végtelen rekurziót okoztak, mert amikor az `autoCalculate()` visszaírt egy mezőbe, az újra kiváltotta az `@input` eseményt.
**Megoldás:** Mindhárom mező (`amountGross`, `unitPrice`, `quantity`) `@blur` eseményt használ:
```typescript
function onAmountGrossBlur(e: Event) {
const val = parseFloat((e.target as HTMLInputElement).value) || 0
form.amountGross = val
lastEditedField.value = 'amount_gross'
autoCalculate()
}
```
**Logika:**
1. A `@blur` csak akkor fut le, amikor a felhasználó elhagyja a mezőt (pl. Tab-bal vagy kattintással)
2. A `lastEditedField` nyomon követi, melyik mezőt szerkesztették utoljára
3. Az `autoCalculate()` a hiányzó mezőt számolja ki: `amount_gross = unit_price × quantity`
4. Nincs végtelen rekurzió, mert a `@blur` nem triggerelődik programozott értékadásra
### 🔧 FIX 5: Odometer hint response parsing
**Fájl:** [`frontend_app/src/components/dashboard/SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:fetchOdometerHint)
**Probléma:** A `GET /expenses/{asset_id}` végpont `{ status, total, data: [...] }` formátumban adja vissza az adatokat, de a kód egy sima tömböt várt.
**Megoldás:**
```typescript
const body = res.data as { status?: string; total?: number; data?: Array<{ mileage_at_cost?: number | null }> }
const expenses = body.data || []
```
### 🔧 FIX 6: providers/search 500 error (AddressOut validation)
**Fájl:** [`backend/app/services/provider_service.py`](backend/app/services/provider_service.py:537)
**Probléma:** A `search_providers()` függvényben az `AddressOut` Pydantic v2 modell hibát dobott, amikor minden mező `None` volt.
**Megoldás:** Try-except blokk az `AddressOut` konstrukció körül:
```python
if any([row_address_zip, row_address_street_name, row_address_street_type, row_address_house_number, row_city]):
try:
address_detail = AddressOut(...)
except Exception as addr_e:
logger.warning(...)
address_detail = None
```
### ✅ Verifikáció
1. **Python syntax check**`expenses.py: OK`, `provider_service.py: OK`
2. **Sync Engine** → 1287/1287 elements OK, 0 fixes needed
3. **Frontend TypeScript** → All type-safe, no new compilation errors
### Verifikáció
- `POST /api/v1/expenses/`**HTTP 201**
- Biztosítási költség sikeresen létrejött
---
## P0 BUGFIX - Odometer Source of Truth & Sync (Gitea #406)
## Fix: 401 Unauthorized on Admin Ledger Endpoint (Gitea #413)
**Dátum:** 2026-07-10
**Scope:** Frontend (SimpleFuelModal.vue)
**Dátum:** 2026-07-25
### 🔧 Módosított fájlok
### Probléma
...
#### 1. Frontend: [`SimpleFuelModal.vue`](frontend_app/src/components/dashboard/SimpleFuelModal.vue:228)
- **Fix:** `fetchLastOdometer()` most már a `/assets/vehicles/{asset_id}` végpontot hívja a `/expenses/{asset_id}` helyett.
- **Logika:** A `Vehicle.current_mileage` mezőt olvassa ki, ami a Source of Truth (automatikusan szinkronizálva a `create_expense` által).
- **Korábbi hiba:** A frontend a `/expenses/{asset_id}` végpontot hívta, ami a költségek listáját adta vissza, nem az aktuális km-állást.
#### 2. Backend: [`expenses.py`](backend/app/api/v1/endpoints/expenses.py:635)
- **Ellenőrizve:** A `create_expense` függvény (635-637. sor) már tartalmazza az `asset.current_mileage` szinkronizálást:
```python
if expense.mileage_at_cost is not None and expense.mileage_at_cost > (asset.current_mileage or 0):
asset.current_mileage = expense.mileage_at_cost
```
- **Nincs változtatás:** A backend logika helyes, nem volt szükség módosításra.
#### 3. Frontend: [`ComplexExpenseModal.vue`](frontend_app/src/components/dashboard/ComplexExpenseModal.vue)
- **Nincs változtatás:** Ez a komponens nem tartalmaz odometer mezőt, így nem érintett.
### ✅ Verifikáció
1. **Python syntax check** → `expenses.py: OK`
2. **Sync Engine** → 1287/1287 elements OK, 0 fixes needed
3. **Frontend** → `fetchLastOdometer()` most a `/assets/vehicles/{id}` végpontot használja, ami a `current_mileage`-t adja vissza
---
## P0 EPIC - CostEntryWizard UI/UX Parity & Categories (Gitea #407)
**Dátum:** 2026-07-10
**Scope:** Frontend (CostEntryWizard.vue)
### 🔧 Módosított fájlok
#### 1. Frontend: [`CostEntryWizard.vue`](frontend_app/src/components/cost/CostEntryWizard.vue)
**P0 EPIC 1: Global Vehicle Selection**
- `useVehicleStore` importálva, `fetchUserVehicles()` metódus hozzáadva
- `selectedVehicleId` ref + `userVehicles` ref a járműlista kezelésére
- `effectiveVehicle` computed: prioritás szerint oldja fel a járművet (prop > dropdown)
- Template: `<select>` dropdown a felhasználó járműveivel, fallback read-only display ha vehicle prop van
**P0 EPIC 2: Grouped Categories (Subcategories)**
- `SubCategoryGroup` interface a csoportosított struktúrához
- `groupedSubCategories` computed: a subCategories flat listát csoportosítja parent category név alapján
- Template: `<optgroup>`-ok a subcategory select-ben, logikai csoportosítással
- `onMainCategoryChange()`: kiszűri a kiválasztott főkategória al-kategóriáit
**P0 EPIC 3: Universal Odometer with Hint**
- `odometerHint` ref + `odometerLoading` ref a hint állapot kezelésére
- `fetchLastOdometer()`: a `/assets/vehicles/{id}` végpontról lekéri a `current_mileage`-t
- Template: mindig látható km óra input + hint "Előző állás: X km"
- `watch(effectiveVehicle)` triggeli a `fetchLastOdometer()`-t járműváltáskor
- `isFuelCategory()`: backend FUEL_CATEGORY_IDS alapján detektálja az üzemanyag kategóriákat
- Liters input csak fuel kategóriáknál jelenik meg
**P0 EPIC 4: ProviderAutocomplete Integration**
- Már helyesen integrálva volt: `v-model`, `@select`, `@clear` események
- `handleSubmit()`: P0 Hybrid Vendor Refactor szerint source-aware vendor field-eket küld
- `verified_org` → `vendor_organization_id`
- `crowd_added` → `service_provider_id`
- raw string → `external_vendor_name`
**Payload kompatibilitás:**
- `asset_id`, `category_id`, `amount_gross` (GROSS-FIRST), `currency`, `date`, `mileage_at_cost`
- `vendor_organization_id`, `service_provider_id`, `external_vendor_name` root szinten
- `data` JSONB: `net_amount`, `vat_rate`, `invoice_number`, `invoice_date`, `payment_method`, `payment_deadline`, `liters`, `mileage_at_cost`, vendor meta
### ✅ Verifikáció
1. **Vite build** → `CostEntryWizard-KIg7Uvhm.js` (31.25 kB) sikeresen lefordult, 0 error
2. **Template** → mind a 4 P0 EPIC feature implementálva
3. **Script** → handleSubmit, resetForm, editCost hydration, lifecycle hooks teljesek
## P0 EPIC - Enterprise Taxonomy Seed Script (Gitea #407 kiegészítés)
### 📋 Új fájl: [`seed_expertise_enterprise.py`](backend/app/scripts/seed_expertise_enterprise.py)
**Cél:** Enterprise Taxonomy (Pénzügy, Biztosítás, Hatóságok) beszúrása a `marketplace.expertise_tags` táblába.
**Beszúrt 9 új tag (IDs 769-777):**
#### Level 1: Pénzügy és Biztosítás (Finance & Insurance) → ID 769
- Level 2: Gépjármű biztosító (Vehicle Insurance) → ID 770
- Level 2: Lízing és Finanszírozás (Leasing & Financing) → ID 771
- Level 2: Bank és Hitelintézet (Bank & Credit Institution) → ID 772
#### Level 1: Hatóságok és Közigazgatás (Authorities & Administration) → ID 773
- Level 2: Önkormányzat (Municipality) → ID 774
- Level 2: Állami Kincstár / Nemzeti Adóhatóság (State Treasury / Tax Authority) → ID 775
- Level 2: Közlekedési Hatóság / Kormányablak (Transport Authority) → ID 776
- Level 2: Útdíj / Autópálya Kezelő (Toll & Highway Operator) → ID 777
### ✅ Verifikáció
1. **Seed script futtatás:** `docker compose exec sf_api python3 /app/backend/app/scripts/seed_expertise_enterprise.py` → sikeres, 9 új sor beszúrva
2. **Adatbázis ellenőrzés:** `SELECT count(*) FROM marketplace.expertise_tags;` → 145 total record
3. **Vite build:** `docker compose exec sf_public_frontend npm run build` → 6.80s, 0 error, CostEntryWizard chunk 31.25 kB
---
## P0 EPIC - Seed Insurance Providers
**Dátum:** 2026-07-11
**Scope:** Backend (Seed Script)
### 🔧 Létrehozott fájl
#### 1. [`seed_insurance_providers.py`](backend/app/scripts/seed_insurance_providers.py)
- **33 magyar biztosító** seedelése a marketplace ökoszisztémába
- Adatforrás: Netrisk nyílt adatok (Alfa, Allianz, Generali, K&H, stb.)
- **Modellek:** `ServiceProvider` → `ServiceProfile` → `ServiceExpertise`
- Minden biztosító linkelve a **"Gépjármű biztosító"** expertise taghez (key=`gepjarmu_biztosito`, ID=770)
- `status=approved`, `source=api_import`, `validation_score=100`
- JSONB `raw_data` mezőben: Cégjegyzékszám, Bankszámla, Alaptőke, Tulajdonos, Kárrendezés adatok
- Duplikációszűrés név alapján
### ✅ Verifikáció
1. **Adatbázis:** 33 rekord a `marketplace.service_providers` táblában, mind `status=approved`, `source=api_import`
2. **API Search:** `GET /api/v1/providers/search?query=biztosito` visszaadja a biztosítókat (pl. Allianz, Generali, Groupama)
3. **ServiceProfile** és **ServiceExpertise** kapcsolatok létrejöttek minden providerhez
---
## P0 EPIC: i18n JSONB Database Migration (Phase 1) — Gitea #399
**Dátum:** 2026-07-23
**Scope:** Backend (Models, API, Services, Schemas, Seeds)
### 🔧 Módosított fájlok
#### 1. Modellek — Régi oszlopok eltávolítva
- [`marketplace/service.py`](backend/app/models/marketplace/service.py) — `ExpertiseTag`: `name_hu`, `name_en`, `name_translations`, `description` → csak `name_i18n`, `description_i18n`
- [`vehicle/vehicle_definitions.py`](backend/app/models/vehicle/vehicle_definitions.py) — `BodyTypeDictionary`: `name_hu` → csak `name_i18n`
- [`core_logic.py`](backend/app/models/core_logic.py) — `ServiceCatalog`: `name`, `description` → csak `name_i18n`, `description_i18n`
#### 2. API Végpontok
- [`providers.py`](backend/app/api/v1/endpoints/providers.py) — Category tree, autocomplete: `name_hu`/`name_en` → `name_i18n`
- [`catalog.py`](backend/app/api/v1/endpoints/catalog.py) — Body types: `name_hu` → `name_i18n`
#### 3. Service Réteg
- [`provider_service.py`](backend/app/services/provider_service.py) — CategoryInfo, tag creation, tag search: `name_hu`/`name_en` → `name_i18n`
#### 4. Schema Réteg
- [`provider.py`](backend/app/schemas/provider.py) — `CategoryTreeNode`, `CategoryAutocompleteItem`, `ExpertiseCategoryOut`, `CategoryInfo`: `name_hu`/`name_en` → `name_i18n`
#### 5. Seed Scriptek (7 fájl)
- `seed_expertise_tags.py`, `seed_dismantler_category.py`, `seed_gas_station.py`, `seed_expertise_enterprise.py`, `seed_insurance_providers.py`, `seed_body_types.py`, `seed_expertise_taxonomy.py`
#### 6. Adatbázis
- 6 shadow column eltávolítva SQL ALTER TABLE segítségével
- Sync engine: 0 shadow column, 1281 OK — tökéletes szinkron
### ⚠️ Megjegyzés
- A konténer újraépítése után pre-existing import hibák derültek ki az `expenses.py`-ban (3 db):
1. `from app.models.fleet.organization import Organization` → `app.models.marketplace.organization`
2. `from app.models.fleet.org_member import OrganizationMember` → `app.models.marketplace.organization`
3. `from app.models.marketplace.service import ServiceProvider` → `app.models.identity.social`
4. `from app.models.system.system_parameter import SystemParameter` → `app.models.system.system`
- Mind a 4 import hiba javítva, konténer újraépítve, API sikeresen fut.
A konténer indításakor előre létező import hibák jelentkeztek (`expenses.py` → `Organization`, `AssetEvent`, `OdometerReading` rossz import útvonalai), amelyek NEM kapcsolódnak a #399-es kártyához. Ezek javítása külön kártyát igényel.
---
## P1 EPIC - Registration UI Upgrade & Google SSO Integration
**Dátum:** 2026-07-23
### Módosított fájlok
#### Backend
1. **`backend/app/api/v1/endpoints/auth.py`** — Google OAuth handler hozzáadva:
- `GoogleAuthRequest` Pydantic modell (`id_token`, `access_token`, `referred_by_code` mezőkkel)
- `POST /auth/google` végpont: Google token verifikáció (tokeninfo API), felhasználó lekérés/létrehozás `SocialAuthService.get_or_create_social_user()`-on keresztül, JWT token generálás, refresh_token cookie beállítás
- Támogatja mind az `id_token` (Google Sign-In), mind az `access_token` (OAuth2 token client) típusokat
2. **`backend/app/services/social_auth_service.py`** — `get_or_create_social_user()` metódus kiegészítve:
- `referred_by_code` paraméter hozzáadva
- Új social regisztrációnál a meghívó kód alapján a referrer (`User.referral_code`) felkutatása és `referred_by_id` beállítása
- `generate_secure_slug` importálva az új felhasználók referral_code generálásához
#### Frontend
3. **`frontend_app/src/components/LoginModal.vue`** — Regisztrációs UI frissítve:
- `referredByCode` ref hozzáadva
- "Meghívó kód" (Referral Code) input mező hozzáadva a regisztrációs űrlapba (jelszó mező alá)
- "Vagy regisztrálj Social fiókkal" elválasztó + Google gomb hozzáadva a regisztrációs űrlapba (a login form dizájnját tükrözve)
- Google gomb @click esemény bekötve a login űrlapon (`handleGoogleLogin`)
- `handleGoogleRegister()` függvény: a meghívó kódot localStorage-ba menti (`pending_referral_code`), majd meghívja a `handleGoogleLogin()`-t
- `handleRegister()` frissítve: `referred_by_code` mezőt is küld a backendnek
- `loadGoogleScript()`: dinamikusan betölti a Google Identity Services SDK-t
4. **`frontend_app/src/stores/auth.ts`** — `googleAuth` action hozzáadva:
- `access_token` küldése a `/auth/google` végpontra
- Opcionális `referred_by_code` támogatása
- Token perzisztálás, user profil és szervezetek betöltése
5. **`frontend_app/.env`** — `VITE_GOOGLE_CLIENT_ID` környezeti változó hozzáadva
### Technikai részletek
- **Google OAuth flow**: A frontend a Google Identity Services (GIS) `initTokenClient`-jével kér `access_token`-t, amit a backend a `https://oauth2.googleapis.com/tokeninfo?access_token={token}` endpointon verifikál
- **Referral code flow**: Regisztrációkor a meghívó kódot a frontend elküldi a backendnek, ami a `User.referral_code` mező alapján megkeresi a referrer-t és beállítja a `referred_by_id` kapcsolatot
- **Biztonság**: A backend ellenőrzi a token `azp` (authorized party) mezőjét a `GOOGLE_CLIENT_ID`-val
- **Adatbázis**: Nincs új migráció, a meglévő `User.referral_code` és `referred_by_id` mezők használva
- **Sync engine**: 1281 elem szinkronban, 0 hiba
- **Modul betöltés**: Mindkét módosított backend modul sikeresen betöltődik
## 2026-07-23 — Referral & Credit Ecosystem Full Stack Analysis (#409)
**Elemzés típusa:** Read-only audit
**Vizsgált komponensek:** identity.users, identity.wallet, gamification.point_rules, gamification.points_ledger, audit.financial_ledger, AuthService, SocialAuthService, GamificationService, LoginModal.vue, auth.ts
**Főbb megállapítások:**
1. Single-level (L1) referral tracking létezik `User.referred_by_id` mezőn keresztül
2. Jutalmazás kizárólag XP alapú (50 pont P2P_REFERRAL_SUCCESS), nincs kredit-alapú jutalom
3. **KRITIKUS HIBA:** `auth.ts register()` nem küldi el a `referred_by_code`-ot a payload-ban
4. Hiányzik: dedikált Referral tábla, multi-level (L2/L3) követés, commission engine, referral API-k
5. FinancialLedger és Wallet infrastruktúra alkalmas lenne bővítésre
**Dokumentáció:** `/opt/docker/docs/referral_credit_ecosystem_analysis.md`
---
## 2-Level MLM Commission Distribution
**Dátum:** 2026-07-24
### Megvalósított komponensek:
1. **Model** [`backend/app/models/marketplace/commission.py`](backend/app/models/marketplace/commission.py:119) — `upline_commission_percent` oszlop hozzáadva (Numeric(5,2), nullable, default=0.00)
2. **Schemas** [`backend/app/schemas/commission.py`](backend/app/schemas/commission.py) — `CommissionDistributionRequest`, `CommissionDistributionItem`, `CommissionDistributionResponse` új Pydantic modellek
3. **Service** [`backend/app/services/commission_service.py`](backend/app/services/commission_service.py:377) — `distribute_commission()` 2-level MLM engine: buyer → Gen1 (commission_percent) → Gen2 (upline_commission_percent)
4. **API** [`backend/app/api/v1/endpoints/admin_commission.py`](backend/app/api/v1/endpoints/admin_commission.py:250) — `POST /admin/commission-rules/distribute` végpont
5. **Frontend** [`frontend_admin/pages/finance/commission-rules.vue`](frontend_admin/pages/finance/commission-rules.vue) — `upline_commission_percent` mező az admin UI-ban
6. **i18n** — EN/HU locale fájlok frissítve
### Teszteredmények:
- **4/4 teszt PASS**
- Gen1: 5% of 100000 = 5000.00 ✅
- Gen2: 2% of 100000 = 2000.00 ✅
- Total: 7000.00 ✅
- Max amount cap (50000) verified ✅
- Sync engine: 1303 OK, 0 Fixed, 0 Shadow
## Phase 2 - MLM Commission & Wallet Integration (2026-07-24)
### ✅ Implementált változtatások
1. **CommissionRule model** - 3 új mező: gen1_max_amount, gen2_max_amount (Numeric(18,2)), gen2_renewal_percent (Numeric(5,2))
2. **Pydantic schemas** - új mezők a Create/Update/Response DTO-kban + is_renewal flag a DistributionRequest-ben
3. **commission_service.py** - Phase 2 business logic:
- _credit_commission_to_wallet(): Wallet.earned_credits + FinancialLedger CREDIT
- _is_user_recently_active(): 180 napos inaktivitás ellenőrzés
- Per-level caps (gen1_max_amount/gen2_max_amount fallback commission_max_amount-ra)
- Inaktív Gen1 esetén Gen2 kifizetés kihagyása
- Renewal logika (gen2_renewal_percent vs upline_commission_percent)
- db.commit() a wallet/ledger írások után
4. **Frontend Admin UI** - 3 új mező a commission-rules.vue formban + i18n kulcsok (HU/EN)
5. **Sync Engine** - 3 új oszlop sikeresen hozzáadva a marketplace.commission_rules táblához
6. **Tesztek** - 4/4 teszt sikeresen lefutott (commission distribution + max amount cap)
## Phase 3 - Commission Rule Priority Engine & Conflict Resolution Flow (2026-07-24)
### ✅ Implementált változtatások
**Task 1: Priority Resolution Engine (Verifikáció)**
- [`get_active_rule()`](backend/app/services/commission_service.py:333) már helyesen implementálva:
- `campaign_priority`: campaign=0, egyéb=1
- `region_priority`: exact match=0, GLOBAL=1, else=2
- `tier_priority`: PLATINUM=0 → CONTRACTED=4, else=99
- `.order_by(campaign_priority, region_priority, tier_priority).limit(1)`
**Task 2: Conflict Detection & Audit Logging**
- [`schemas/commission.py`](backend/app/schemas/commission.py:159): `ConflictingRuleInfo` + `ConflictResponse` DTO-k
- [`commission_service.py`](backend/app/services/commission_service.py:59): `check_rule_conflict()` - exact match (tier, region_code, is_campaign) aktív szabályokra
- [`commission_service.py`](backend/app/services/commission_service.py:103): `create_commission_rule()` - conflict check + force_override deaktiválás
- [`commission_service.py`](backend/app/services/commission_service.py:189): `update_commission_rule()` - conflict check exclude_rule_id-vel
- [`admin_commission.py`](backend/app/api/v1/endpoints/admin_commission.py:179): 409 Conflict response `ConflictingRuleInfo` struktúrával
**Task 3: Frontend UI Approval Workflow**
- [`commission-rules.vue`](frontend_admin/pages/finance/commission-rules.vue:595): `showConflictModal`, `conflictingRule`, `pendingPayload` refs
- Konfliktus modal: amber figyelmeztetés + conflicting rule adatok + Cancel/Confirm gombok
- `saveRule()`: 409 catch → pendingPayload tárolás → modal megjelenítés
- `confirmOverride()`: re-submit `force_override: true`-val
- [`hu/commission.json`](frontend_admin/i18n/locales/hu/commission.json): magyar konfliktus szövegek
- [`en/commission.json`](frontend_admin/i18n/locales/en/commission.json): angol konfliktus szövegek
### ✅ Verifikáció
1. **Sync Engine** - 1306 items OK (schema in sync)
2. **Commission Distribution Test** - 4/4 passed
3. **Python imports** - No syntax errors
---
## FinancialManager: Subscription Purchase & Payment Gateway Module (Gitea #411)
**Dátum:** 2026-07-24
**Scope:** Backend (Strategy Pattern, FastAPI, SQLAlchemy)
### 🔧 Létrehozott fájlok
1. [`mock_payment_gateway.py`](backend/app/services/mock_payment_gateway.py) - MockPaymentGateway (Strategy Pattern implementáció, 3 mód: auto_approve, simulate_failure, simulate_timeout)
2. [`subscription_activator.py`](backend/app/services/subscription_activator.py) - SubscriptionActivator (User/Org szintű aktiválás P0 stackinggel)
3. [`financial_manager.py`](backend/app/services/financial_manager.py) - FinancialManager orchestrator (teljes vásárlási életciklus)
4. [`financial_manager.py`](backend/app/schemas/financial_manager.py) - PurchaseRequest/PurchaseResponse Pydantic sémák
5. [`financial_manager.py`](backend/app/api/v1/endpoints/financial_manager.py) - API végpontok (POST /purchase-package, GET /status)
### 🔧 Módosított fájlok
1. [`api.py`](backend/app/api/v1/api.py) - Financial Manager router regisztráció
### 🐛 Javított adatbázis enumok
- `finance.wallet_type` - hozzáadva: EARNED, PURCHASED, SERVICE_COINS, VOUCHER
- `finance.payment_intent_status` - hozzáadva: COMPLETED, CANCELLED, EXPIRED
### ✅ Verifikáció
1. **Sync Engine** - 1306 items OK (schema in sync)
2. **FinancialManager Test Suite** - 24/24 passed
3. **API Integration** - purchase-package endpoint fully functional
---
## Build Inactivity & Subscription Monitor (Gitea #412)
**Dátum:** 2026-07-24
**Scope:** Backend (Workers, Scheduler, Auth, DB Schema)
### 🔧 Módosított/Létrehozott fájlok
#### 1. [`identity.py`](backend/app/models/identity/identity.py:178)
- Hozzáadva: `last_activity_at` mező a `User` modellhez (DateTime(timezone=True), nullable)
- Komment: "Updated on login/token-refresh; used by the 180-day inactivity worker"
#### 2. [`subscription_monitor_worker.py`](backend/app/workers/system/subscription_monitor_worker.py) (ÚJ)
- Teljes subscription lifecycle kezelés: `UserSubscription` és `OrganizationSubscription`
- `FOR UPDATE SKIP LOCKED` atomi zárolás
- Fallback `is_default_fallback` tier-re lejáratkor
- `FinancialLedger` bejegyzés (`SUBSCRIPTION_EXPIRED`)
- `NotificationService` értesítés
- Nem írja felül a meglévő `subscription_worker.py`-t
#### 3. [`inactivity_monitor_worker.py`](backend/app/workers/system/inactivity_monitor_worker.py) (ÚJ)
- 180 napos inaktivitás detektálás (kétfázisú: `last_activity_at` + `created_at` alapján)
- Subquery approach a `FOR UPDATE` + outer join PostgreSQL hiba elkerülésére
- `custom_permissions['inactivity_suspended']` flag beállítása MLM engine számára
- `is_active = False`, `FinancialLedger` bejegyzés, `NotificationService` értesítés
#### 4. [`scheduler.py`](backend/app/core/scheduler.py:207)
- Subscription Monitor job: 01:00 UTC (CronTrigger, jitter=900)
- Inactivity Monitor job: 02:00 UTC (CronTrigger, jitter=900)
- Mindkét job `ProcessLog`-ba naplóz
#### 5. [`auth.py`](backend/app/api/v1/endpoints/auth.py:65)
- `user.last_activity_at = datetime.now(timezone.utc)` beállítás login, verify-email és Google auth végpontokban
### ✅ Verifikáció
1. **Sync Engine** - 1306 items OK, 1 fixed (`last_activity_at` oszlop hozzáadva)
2. **Subscription Monitor** - Sikeres futás, 0 expired subscription (dev adatbázis)
3. **Inactivity Monitor** - Sikeres futás, 0 inactive user (dev adatbázis)
4. **FOR UPDATE fix** - Subquery approach alkalmazva a PostgreSQL outer join korlátozás miatt
### Documentation
`docs/p0_expense_500_import_bugfix_2026-07-26.md`
**Gitea Issue:** #421 (closed)