Files
service-finder/.roo/history.md

22 KiB

Verifikáció

  1. Build ellenőrzésnpx 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 cacheModuleNotFoundError: 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ésoperator 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:

  • 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 — 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):

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

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 blokkoklifecycle: {}, 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") 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

  • Ú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 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)

  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 _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" — 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 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

  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.pyPurchaseResult 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.pyPurchaseResponse 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.pyget_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, 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

  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_v1Privá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)