### ✅ Verifikáció 1. **Build ellenőrzés** → `npx vite build` sikeres (7.08s, 0 error) 2. **Chunk generálva**: `CostEntryWizard-C_-R4OzC.js` (29.95 kB) - refaktorált komponens helyesen fordul 3. **ProviderAutocomplete** újrafelhasználva mindhárom komponensben - nincs duplikáció --- ## Fix: 401 Unauthorized on Admin Ledger Endpoint (Gitea #413) **Dátum:** 2026-07-25 ... --- ## Fix: POST /api/v1/expenses/ — 500 Internal Server Error (PG ENUM vs varchar JOIN) **Dátum:** 2026-07-26 ### Probléma A `POST /api/v1/expenses/` végpont 500-as hibát dobott insurance (biztosítás) költség létrehozásakor. ### 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. ### 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 ### Verifikáció - `POST /api/v1/expenses/` → **HTTP 201** ✅ - Biztosítási költség sikeresen létrejött --- ## Fix: 401 Unauthorized on Admin Ledger Endpoint (Gitea #413) **Dátum:** 2026-07-25 ### Probléma ... ### Documentation `docs/p0_expense_500_import_bugfix_2026-07-26.md` **Gitea Issue:** #421 (closed) ## 2026-07-27 — Subscription Card Upgrade (Gitea #423) ### Changes Made **Scope:** FinanceMainView Card 2 upgrade — from raw slug display to functional widget. **Backend (1 file):** - [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py) — Extended `read_users_me()` to resolve and return 3 new fields: `subscription_display_name` (from `SubscriptionTier.rules.display_name`), `subscription_expires_at` (from `UserSubscription`/`OrganizationSubscription.valid_until`), and `subscription_tier_id` (FK to `system.subscription_tiers`). **Frontend (4 files):** - [`frontend_app/src/stores/auth.ts`](frontend_app/src/stores/auth.ts) — Added `subscription_display_name` and `subscription_tier_id` fields to `UserProfile` interface. - [`frontend_app/src/views/FinanceMainView.vue`](frontend_app/src/views/FinanceMainView.vue) — Rewrote Card 2 with: human-readable display name (fallback chain), formatted expiry date (`YYYY.MM.DD`), vehicle usage progress bar (via `vehicleStore`), and dynamic CTA button ("Csomag Kezelése" / "Újítás most"). - [`frontend_app/src/i18n/hu.ts`](frontend_app/src/i18n/hu.ts) — Added `renewNow` and `upgradePlan` keys. - [`frontend_app/src/i18n/en.ts`](frontend_app/src/i18n/en.ts) — Added `renewNow` and `upgradePlan` keys. ### Verification - ✅ `sync_engine` — 1307 items OK, 0 errors - ✅ `/auth/me` returns new fields correctly (`display_name: None` for admin without subscription, `"Céges Prémium"` for org with subscription) - ✅ Syntax checked, AST parse OK --- ## Gitea #424: Finance Flip Card + Context-Aware Filter + Admin Memory Fix **Dátum:** 2026-07-27 ### Changes - [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py) — Added `created_at` to `_build_user_response()`, `subscription_valid_from` init/extraction, active add-ons query block, and response fields. - [`frontend_app/src/stores/auth.ts`](frontend_app/src/stores/auth.ts) — Added `user_registration_date`, `subscription_valid_from`, `active_addons` to `UserProfile` interface. - [`frontend_app/src/views/FinanceMainView.vue`](frontend_app/src/views/FinanceMainView.vue) — Replaced Card 2 with 3D flip card (CSS perspective/rotateY), including `ref` import, `isFlipped` state, computed properties for back face data, and helper functions for add-on dates. - [`frontend_app/src/views/SubscriptionPlansView.vue`](frontend_app/src/views/SubscriptionPlansView.vue) — Enhanced `filteredPlans` to check `rules.type` JSONB before name prefix fallback. - [`frontend_app/src/i18n/hu.ts`](frontend_app/src/i18n/hu.ts) — Added 7 flip card i18n keys. - [`frontend_app/src/i18n/en.ts`](frontend_app/src/i18n/en.ts) — Added 7 flip card i18n keys. - [`docker-compose.yml`](docker-compose.yml) — NODE_OPTIONS already configured; purged `.nuxt/` cache and restarted `sf_admin_frontend`. ### Verification - ✅ `sync_engine` — 1307 items OK, 0 Fixed, 0 Shadow — perfectly in sync - ✅ Backend changes compile without syntax errors - ✅ `.nuxt` cache purged, admin container restarted --- ## Gitea #426: Admin Packages Crash Fix + Phase E Auto-Renewal UI **Dátum:** 2026-07-28 ### Probléma Az admin Packages (Tiers) nézet betöltésekor Pydantic validation crash történt, mert: 1. **Legacy `rules.type = "base"`** — A `SubscriptionRulesModel` `type` mezőjének `pattern=r"^(private|corporate|addon)$"` regex-e elutasította a "base" értéket 2. **Üres `pricing: {}`** — A `PricingModel` `monthly_price` mezője kötelező, de legacy adatoknál előfordul üres dict 3. **Hiányzó/üres JSONB blokkok** — `lifecycle: {}`, `allowances: {}`, `marketing: {}` mind hibát okoztak ### Javítás (2 fájl) **Fix 1 — Pydantic Legacy Data Sanitizer:** [`subscription.py`](dev/service_finder/backend/app/schemas/subscription.py:158-227) - Hozzáadva `model_validator(mode="before")` a `SubscriptionRulesModel`-hez - Automatikusan kezeli: üres dict-ek → None, `type: "base"` → `"private"`, hiányzó mezők → alapértékek - Kiterjesztve: pricing_zones, renewal, allowances sanitization **Fix 2 — Phase E Admin UI:** [`packages/index.vue`](dev/service_finder/frontend_admin/pages/packages/index.vue) - Új "🔄 Auto-Renewal" tab a modalban (4. tab) - Toggle: `renewal_enabled`, `auto_renew_default`, `retry_on_failure` - Input: `renewal_period_days`, `grace_period_days`, `max_retry_count` - Form state, loading (openEditModal), és save payload (rulesPayload) mind kiterjesztve - Renewal mező hozzáadva a `SubscriptionTier` és `PackageForm` interfészekhez ### Verifikáció - ✅ Backend: `from app.schemas.subscription import SubscriptionRulesModel` — OK - ✅ 4 Pydantic teszt: legacy empty dict, missing renewal, modern renewal, partial renewal — ALL PASSED - ✅ `from app.api.v1.endpoints.admin_packages import ...` — OK - ✅ `sync_engine` — 1319 items OK, 0 Fixed, 0 Shadow — perfectly in sync --- ## Gitea #427: Regression Fix — Header Label + Non-public Package Visibility **Datum:** 2026-07-28 ### Regression 1 - Header Label (Frontend) **File:** [`HeaderCompanySwitcher.vue:199-200`](dev/service_finder/frontend_app/src/components/header/HeaderCompanySwitcher.vue:199) **Root Cause:** After Fix 3 of #425, the fallback for individual orgs used `t('header.garageSelector')` which translates to "Garage Selector". **Fix:** Changed to `t('header.privateGarage')` which correctly shows "Private Garage" / "Privat garazs". ### Regression 2 - Non-public Package Visibility (Backend) **File:** [`subscriptions.py:165-173`](dev/service_finder/backend/app/api/v1/endpoints/subscriptions.py:165) **Root Cause:** The GET /subscriptions/public SQL query had NO WHERE clause - all tiers fetched. Python-level filter defaulted is_public to True when lifecycle was missing. **Fix:** Added SQL-level WHERE clause using `~SubscriptionTier.rules["lifecycle"]["is_public"].as_string().in_(["false"])` (same as admin_packages.py:134). ### Verification - Backend import: from app.api.v1.endpoints import subscriptions - OK - sync_engine: 1319 items OK, 0 Fixed, 0 Shadow - Gitea #427: created, started --- ## Gitea #428: Three Bug Fixes - Admin Login Loop, Purchase Simulation, Missing i18n **Datum:** 2026-07-28 ### Fix 1 - Admin Login Infinite Loop (P0) **File:** auth.ts:266-272 **Root Cause:** After login, login() checks isKycComplete and redirects. Admins lack KYC data, causing redirect loop. **Fix:** Added staff role check to skip KYC redirect for staff users. ### Fix 2 - Purchase Simulation to Real API **File:** SubscriptionPlansView.vue:295-325 **Root Cause:** handleOrder() was a console.log simulation stub. **Fix:** Now calls POST /financial-manager/purchase-package with tier_id, org_id, region_code, currency. Refreshes user data on success. ### Fix 3 - Missing i18n Key **Files:** hu.ts:74, en.ts:74 **Root Cause:** PlanDetailsModal.vue:140 referenced t(subscription.features.affiliateCommission) but key was missing. **Fix:** Added affiliateCommission to both locale files. ### Verification - Backend import: financial_manager.purchase_package - OK - sync_engine: 1319 items OK - Gitea #428: created, started, finished --- ## P0: Dashboard Infinite Mount/Unmount Loop Fix (8th Attempt) **Dátum:** 2026-07-28 **Authored by:** Architect (root cause) → Code (implementation) ### Probléma A Dashboard csempék (5-card grid) végtelen mount/unmount ciklusba kerültek `admin@profibot.hu` standard admin felhasználónál. A gridet a `vehicleStore.isLoading` állapot szabályozza `v-if="!vehicleStore.isLoading"` guard-dal. Minden újramountoláskor a gyermek widgetek újraélesztették a reaktív kaszkádot. ### Gyökér-ok (3 rétegű kaszkád) 1. **`authStore.fetchUser()` dedup védelem nélkül** — Két call-site is hívta: `App.vue.onMounted()` + `router.beforeEach()`. Az `init()` guard (`isInitialized`) már későn jött: a második hívás `fetchUser()`-t indított, ami `user.value`-t duplán állította → reaktív kaszkád minden computed property-ben. 2. **`authStore.init()` szintén dedup nélkül** — A `beforeEach` await-eli, az `App.vue` nem, így race condition volt. 3. **`DashboardView.onMounted()` minden alkalommal hívta `fetchMyOrganizations()`-t** — Még akkor is, ha az `init()` már betöltötte. Ez új API hívást → új referencia objektumokat → reaktív újraszámolást okozott. ### Miért csak a standard admin? — A superadmin-nál egyszerűbb a szervezeti struktúra, kevesebb `find()` a computed-ekben, és a referencia egyezés miatt a Vue kihagyta az újrarenderelést. ### Javítások (3 fájlban) | # | Fájl | Módosítás | |---|------|-----------| | 1 | [`stores/auth.ts:296-318`](dev/service_finder/frontend_app/src/stores/auth.ts:296) | `_userFetchInProgress` dedup guard a `fetchUser()`-ben | | 2 | [`stores/auth.ts:443-463`](dev/service_finder/frontend_app/src/stores/auth.ts:443) | `_initInProgress` + `isInitialized` guard az `init()`-ben | | 3 | [`views/DashboardView.vue:365-388`](dev/service_finder/frontend_app/src/views/DashboardView.vue:365) | `fetchMyOrganizations()` csak ha `length === 0` | ### Dokumentáció `docs/dashboard_infinite_loop_root_cause_v8.md` --- ## P0: Dashboard Infinite Loop — Structural Fix (vIf Removal + Local Loading) **Dátum:** 2026-07-28 (2nd pass) **Típus:** Strukturális Vue lifecycle javítás (band-aid → architectural fix) ### Probléma Az előző javítás (dedup guard-ok) csak tüneti kezelés volt. A `v-if="!vehicleStore.isLoading"` guard továbbra is a grid létét kötötte egy globális loading állapothoz. Ha bármikor `isLoading` `true`-ra vált (pl. manuális refresh), a teljes grid elpusztul és újraépül → potenciális loop, még ha a dedup guard-ok ritkítják is. ### Gyökér-ok A [`DashboardView.vue`](dev/service_finder/frontend_app/src/views/DashboardView.vue:42-53) mindkét `v-if` guard-ban használta a `vehicleStore.isLoading`-ot: - `v-if="vehicleStore.isLoading"` — spinner - `v-if="!vehicleStore.isLoading"` — grid Ez azt jelenti, hogy a grid DOM-léte a store globális állapotához volt kötve. ### Javítások (3 fájlban) | # | Fájl | Módosítás | |---|------|-----------| | 1 | [`views/DashboardView.vue:41-55`](dev/service_finder/frontend_app/src/views/DashboardView.vue:41) | **ELTÁVOLÍTVA** mindkét globális `v-if` guard (spinner + grid). A grid most mindig mountolva marad. | | 2 | [`components/dashboard/MyVehiclesCard.vue`](dev/service_finder/frontend_app/src/components/dashboard/MyVehiclesCard.vue) | **HOZZÁADVA** lokális loading spinner: `v-if="vehicleStore.isLoading && vehicles.length === 0"` — mutatja a spinnert, amíg nincs adat, és csak utána az empty state-t. | | 3 | [`components/dashboard/CostsActionsCard.vue`](dev/service_finder/frontend_app/src/components/dashboard/CostsActionsCard.vue) | **HOZZÁADVA** lokális loading spinner + a "No vehicle selected" warning csak akkor jelenik meg, ha az adatok már betöltődtek, de tényleg nincs jármű. | ### Widget audit — ki hogyan kezeli a loading-ot: | Widget | Státusz | |--------|---------| | MyVehiclesCard | ✅ Lokális spinner (most) | | CostsActionsCard | ✅ Lokális spinner (most) | | ServiceFinderCard | 🟢 Statikus UI, nincs adatfüggőség | | GamificationCard | 🟢 Helyi ref-ek, 0 default | | ProfileTrustCard | 🟢 financeStore default 0, elfogadható | | SubscriptionStatusWidget | 🟢 Saját `isLoading` ref + spinner (korábban) | | AdPlacementWidget | 🟢 Saját `isLoading` ref + spinner (korábban) | ### Megjegyzés A dedup guard-ok (`_userFetchInProgress`, `_initInProgress`, feltételes `fetchMyOrganizations`) megtartva — jó, ha megvannak mint extra biztonsági réteg. --- ## Phase 2: Mock Payment Gateway Simulation (Gitea #430) **Dátum:** 2026-07-28 ### Implementált módosítások 1. **`backend/app/services/mock_payment_gateway.py`** — Új `simulate_redirect` mód alapértelmezettként. `create_intent()` amount > 0 esetén `checkout_url`-t és `requires_action` státuszt ad vissza. Logolja a `[MOCK_PAYMENT_REQUEST]` üzenetet. 2. **`backend/app/services/financial_manager.py`** — `PurchaseResult` DTO bővítve `checkout_url` és `payment_status` mezőkkel. A paid-tier flow kezeli a `requires_action` választ: metadata tárolás a PaymentIntent-en, subscription aktiválás deferálása, checkout_url visszaadása a frontendnek. 3. **`backend/app/schemas/financial_manager.py`** — `PurchaseResponse` Pydantic séma bővítve `checkout_url: Optional[str]` és `payment_status: Optional[str]` mezőkkel. 4. **`backend/app/api/v1/endpoints/billing.py`** — Két új végpont: - `GET /mock-payment/checkout/{intent_id}` — Styled HTML fizetési oldal "Pay Now" gombbal - `POST /mock-payment/callback` — Webhook callback: PaymentIntent COMPLETED-re állítása, subscription aktiválása, redirect a frontendre 5. **`backend/app/api/v1/endpoints/financial_manager.py`** — `get_financial_manager()` dependency most `simulate_redirect` módot használ. ### Verifikáció - ✅ AST syntax check: minden fájl OK - ✅ Docker containerben minden import sikeres - ✅ Mock checkout + callback route-ok regisztrálva (`/billing/mock-payment/checkout/`, `/billing/mock-payment/callback`) - ✅ Sync Engine: 1323 elem, tökéletes szinkron --- ## Fix: Subscription Limit Data Binding Bug (Gitea #431) **Dátum:** 2026-07-28 ### Probléma A frontenden a SubscriptionStatusWidget és a FinanceMainView Card 2 (Flip Card) rossz limitértékeket mutatott: `max_vehicles=1`, `max_garages=1`, `subscription_plan` a raw slug-ot mutatta. ### Root Cause **Backend** ([`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py), `read_users_me()`, sorok 207-312): A subscription limit feloldás két párhuzamos, egymástól független kódrészben történt (`max_vehicles`/`max_garages` külön, `subscription_valid_until`/`tier_id` külön). Amikor a user-nek volt `active_org_id`-je (scope_id=67) de NEM volt org subscription rekordja, a kód a fallback `Organization.base_asset_limit`-et használta (=1), és SOHA nem esett át a user subscription ágra. **Adatbázis** ([`system.subscription_tiers`](system.subscription_tiers), id=14): A `private_pro_v1` rules JSONB-ben a `display_name` mező NULL volt. ### Javítás 1. **Backend**: Unifikált subscription resolution — egyesített subscription_record lookup, ami először org subscription-t keres, ha nincs, user subscription-t, majd mindkét értéket (limits + metadata) ugyanabból a rekordból nyeri ki. 2. **Adatbázis**: `UPDATE system.subscription_tiers SET rules = jsonb_set(rules, '{display_name}', '"Privát Pro"') WHERE id=14` ### Verifikáció - ✅ `GET /api/v1/users/me` admin user-re: `max_vehicles=10`, `max_garages=2`, `subscription_display_name="Privát VIP"`, `subscription_tier_id=15`, `subscription_expires_at=2026-08-27` - ✅ Frontend i18n kulcsok (`renewNow`, `upgradePlan`, `clickToFlipBack`) megtalálhatóak mind a `hu.ts`-ben, mind az `en.ts`-ben - ✅ Sync Engine: 1323 elem, tökéletes szinkron - ✅ private_pro_v1 display_name javítva "Privát Pro"-ra --- ## Fix: Context-Switching Data Leak — Dashboard Not Refreshing on Garage Switch **Dátum:** 2026-07-28 ### Probléma When the user switched between private garages (e.g., "Profibot Tester" ↔ "User Admin") using the `HeaderCompanySwitcher` dropdown, the dashboard continued to display stale data: 1. **Vehicle list** (MyVehiclesCard) showed the same vehicles regardless of which private garage was active 2. **Wallet balance** (ProfileTrustCard) showed the same 705 000 balance 3. **Statistics** didn't update ### Root Cause Analysis **Frontend — 3 layers of broken communication:** 1. **Cache staleness:** `switchOrganization()` in `auth.ts` correctly called the backend `PATCH /users/me/active-organization`, received a new JWT token, and updated `user.value`. However, neither `vehicleStore` nor `financeStore` were notified to refresh their data. Both stores had cached data from the initial `onMounted()` call. 2. **Mount-once issue:** `DashboardView.onMounted()` only runs once because the Vue Router reuses the same component instance when the route path doesn't change (`/dashboard` → `/dashboard`). The `watch` on `authStore.contextVersion` didn't exist — there was no reactive trigger to re-fetch dashboard data after context switch. 3. **Deduplication guard blocking re-fetch:** Even if `fetchVehicles()` was called manually after context switch, the `_vehicleFetchInProgress` dedup guard would return the stale cached data. The guard was designed to prevent duplicate calls during initial mount, but it also blocked legitimate re-fetches after context change. **Backend — partially correct:** - The vehicles endpoint (`GET /assets/vehicles`) already uses `current_user.scope_id` to filter vehicles by organization — this was correct. - The wallet balance endpoint (`GET /billing/wallet/balance`) uses `current_user.id` only, meaning it returns the user's global wallet regardless of context — this is by-design since wallet is user-scoped, not org-scoped. However, the frontend wasn't even calling it again after context switch. **Data Ownership — why two private garages appear:** The user sees two private garages because they are a member of two individual organizations. This is expected behavior for users who have created multiple private garages or were added to multiple. The issue is not why they appear, but that switching between them doesn't refresh dashboard data. ### Javítás (3 files changed) **1. `frontend_app/src/stores/auth.ts`:** - Added `contextVersion` ref — a reactive version counter that increments on every successful `switchOrganization()` call - Exposed `contextVersion` in the return block so DashboardView can watch it **2. `frontend_app/src/stores/vehicle.ts`:** - Added `clearCache()` method that resets `_vehicleFetchInProgress`, clears `vehicles.value`, and resets `error` — this breaks the dedup guard so a subsequent `fetchVehicles()` actually executes a new API call **3. `frontend_app/src/views/DashboardView.vue`:** - Added a `watch(() => authStore.contextVersion, ...)` that triggers when `contextVersion` increments - On context change: calls `vehicleStore.clearCache()`, `vehicleStore.fetchVehicles()`, `financeStore.fetchWalletBalance()`, and `authStore.fetchMyOrganizations()` - Skips the first trigger (`contextVersion === 0`) because `onMounted()` already fetches initial data ### Verifikáció - ✅ `authStore.contextVersion` incremented on every `switchOrganization()` call - ✅ `vehicleStore.clearCache()` resets dedup guard before re-fetch - ✅ DashboardView watches `contextVersion` and re-fetches all context-scoped data on change - ✅ Wallet balance re-fetched after context switch - ✅ Organizations list refreshed for header switcher sync --- ## P0: Avatar Menu Subscription Modal - planDisplayName bugfix & DB audit (Gitea #433) **Date:** 2026-07-28 ### DB Investigation: admin@profibot.hu - Found user id=2 with 3 org memberships: 2 individual garages (org 1: 19 vehicles, org 67: 3 archived) + 1 service_provider (org 57: 0 vehicles) - **No data duplication** — each org has distinct vehicles via `vehicle.assets.current_organization_id` - The "azonos adat" perception was caused by TEST-XXX/DRAFT-XXX pattern vehicles in org 1 ### Fix: SubscriptionInfoModal.vue planDisplayName - **Root cause:** `planDisplayName` returned raw `subscription_plan` slug (e.g. `private_vip_v1`) instead of the backend-supplied `subscription_display_name` - **Fix:** Replaced simple `return plan` with a 3-level fallback matching `SubscriptionStatusWidget.vue:124-150`: 1. `authStore.user?.subscription_display_name` (from `_resolve_subscription_data()`) 2. Static slug→human mapping (`private_vip_v1` → `Privát VIP`) 3. Raw plan name as ultimate fallback - `max_vehicles`/`max_garages`/progress bars were already correctly bound to backend data — no changes needed ### Files changed: - `frontend_app/src/components/subscription/SubscriptionInfoModal.vue` (line 174-201) - `docs/subscription_info_modal_planDisplayName_bug_analysis.md` (new)