felhasználói felületre pü
This commit is contained in:
572
.roo/history.md
572
.roo/history.md
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user