22 KiB
✅ Verifikáció
- Build ellenőrzés →
npx vite buildsikeres (7.08s, 0 error) - Chunk generálva:
CostEntryWizard-C_-R4OzC.js(29.95 kB) - refaktorált komponens helyesen fordul - 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)
- Stale
.pyccache →ModuleNotFoundError: No module named 'app.models.fleet.org_role'— A konténer régi bytecode-ot futtatott. Megoldva cache törléssel. - 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áltOrganizationMember.role(ENUM) ésOrgRole.name_key(varchar) között, amit a PostgreSQL nem tud implicit konvertálni.
Javítás
- 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— Extendedread_users_me()to resolve and return 3 new fields:subscription_display_name(fromSubscriptionTier.rules.display_name),subscription_expires_at(fromUserSubscription/OrganizationSubscription.valid_until), andsubscription_tier_id(FK tosystem.subscription_tiers).
Frontend (4 files):
frontend_app/src/stores/auth.ts— Addedsubscription_display_nameandsubscription_tier_idfields toUserProfileinterface.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 (viavehicleStore), and dynamic CTA button ("Csomag Kezelése" / "Újítás most").frontend_app/src/i18n/hu.ts— AddedrenewNowandupgradePlankeys.frontend_app/src/i18n/en.ts— AddedrenewNowandupgradePlankeys.
Verification
- ✅
sync_engine— 1307 items OK, 0 errors - ✅
/auth/mereturns new fields correctly (display_name: Nonefor 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— Addedcreated_atto_build_user_response(),subscription_valid_frominit/extraction, active add-ons query block, and response fields.frontend_app/src/stores/auth.ts— Addeduser_registration_date,subscription_valid_from,active_addonstoUserProfileinterface.frontend_app/src/views/FinanceMainView.vue— Replaced Card 2 with 3D flip card (CSS perspective/rotateY), includingrefimport,isFlippedstate, computed properties for back face data, and helper functions for add-on dates.frontend_app/src/views/SubscriptionPlansView.vue— EnhancedfilteredPlansto checkrules.typeJSONB before name prefix fallback.frontend_app/src/i18n/hu.ts— Added 7 flip card i18n keys.frontend_app/src/i18n/en.ts— Added 7 flip card i18n keys.docker-compose.yml— NODE_OPTIONS already configured; purged.nuxt/cache and restartedsf_admin_frontend.
Verification
- ✅
sync_engine— 1307 items OK, 0 Fixed, 0 Shadow — perfectly in sync - ✅ Backend changes compile without syntax errors
- ✅
.nuxtcache 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:
- Legacy
rules.type = "base"— ASubscriptionRulesModeltypemezőjénekpattern=r"^(private|corporate|addon)$"regex-e elutasította a "base" értéket - Üres
pricing: {}— APricingModelmonthly_pricemezője kötelező, de legacy adatoknál előfordul üres dict - 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
- Hozzáadva
model_validator(mode="before")aSubscriptionRulesModel-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
- Ú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ésPackageForminterfé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
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
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)
-
authStore.fetchUser()dedup védelem nélkül — Két call-site is hívta:App.vue.onMounted()+router.beforeEach(). Azinit()guard (isInitialized) már későn jött: a második hívásfetchUser()-t indított, amiuser.value-t duplán állította → reaktív kaszkád minden computed property-ben. -
authStore.init()szintén dedup nélkül — AbeforeEachawait-eli, azApp.vuenem, így race condition volt. -
DashboardView.onMounted()minden alkalommal hívtafetchMyOrganizations()-t — Még akkor is, ha azinit()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 |
_userFetchInProgress dedup guard a fetchUser()-ben |
| 2 | stores/auth.ts:443-463 |
_initInProgress + isInitialized guard az init()-ben |
| 3 | views/DashboardView.vue:365-388 |
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 mindkét v-if guard-ban használta a vehicleStore.isLoading-ot:
v-if="vehicleStore.isLoading"— spinnerv-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 |
ELTÁVOLÍTVA mindkét globális v-if guard (spinner + grid). A grid most mindig mountolva marad. |
| 2 | 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 |
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
-
backend/app/services/mock_payment_gateway.py— Újsimulate_redirectmód alapértelmezettként.create_intent()amount > 0 eseténcheckout_url-t ésrequires_actionstátuszt ad vissza. Logolja a[MOCK_PAYMENT_REQUEST]üzenetet. -
backend/app/services/financial_manager.py—PurchaseResultDTO bővítvecheckout_urléspayment_statusmezőkkel. A paid-tier flow kezeli arequires_actionválaszt: metadata tárolás a PaymentIntent-en, subscription aktiválás deferálása, checkout_url visszaadása a frontendnek. -
backend/app/schemas/financial_manager.py—PurchaseResponsePydantic séma bővítvecheckout_url: Optional[str]éspayment_status: Optional[str]mezőkkel. -
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" gombbalPOST /mock-payment/callback— Webhook callback: PaymentIntent COMPLETED-re állítása, subscription aktiválása, redirect a frontendre
-
backend/app/api/v1/endpoints/financial_manager.py—get_financial_manager()dependency mostsimulate_redirectmó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, 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, id=14): A private_pro_v1 rules JSONB-ben a display_name mező NULL volt.
Javítás
- 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.
- 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/meadmin 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 ahu.ts-ben, mind azen.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:
- Vehicle list (MyVehiclesCard) showed the same vehicles regardless of which private garage was active
- Wallet balance (ProfileTrustCard) showed the same 705 000 balance
- Statistics didn't update
Root Cause Analysis
Frontend — 3 layers of broken communication:
-
Cache staleness:
switchOrganization()inauth.tscorrectly called the backendPATCH /users/me/active-organization, received a new JWT token, and updateduser.value. However, neithervehicleStorenorfinanceStorewere notified to refresh their data. Both stores had cached data from the initialonMounted()call. -
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). ThewatchonauthStore.contextVersiondidn't exist — there was no reactive trigger to re-fetch dashboard data after context switch. -
Deduplication guard blocking re-fetch: Even if
fetchVehicles()was called manually after context switch, the_vehicleFetchInProgressdedup 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 usescurrent_user.scope_idto filter vehicles by organization — this was correct. - The wallet balance endpoint (
GET /billing/wallet/balance) usescurrent_user.idonly, 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
contextVersionref — a reactive version counter that increments on every successfulswitchOrganization()call - Exposed
contextVersionin the return block so DashboardView can watch it
2. frontend_app/src/stores/vehicle.ts:
- Added
clearCache()method that resets_vehicleFetchInProgress, clearsvehicles.value, and resetserror— this breaks the dedup guard so a subsequentfetchVehicles()actually executes a new API call
3. frontend_app/src/views/DashboardView.vue:
- Added a
watch(() => authStore.contextVersion, ...)that triggers whencontextVersionincrements - On context change: calls
vehicleStore.clearCache(),vehicleStore.fetchVehicles(),financeStore.fetchWalletBalance(), andauthStore.fetchMyOrganizations() - Skips the first trigger (
contextVersion === 0) becauseonMounted()already fetches initial data
Verifikáció
- ✅
authStore.contextVersionincremented on everyswitchOrganization()call - ✅
vehicleStore.clearCache()resets dedup guard before re-fetch - ✅ DashboardView watches
contextVersionand 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:
planDisplayNamereturned rawsubscription_planslug (e.g.private_vip_v1) instead of the backend-suppliedsubscription_display_name - Fix: Replaced simple
return planwith a 3-level fallback matchingSubscriptionStatusWidget.vue:124-150:authStore.user?.subscription_display_name(from_resolve_subscription_data())- Static slug→human mapping (
private_vip_v1→Privát VIP) - 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)