Files
service-finder/plans/finance_module_recovery_blueprint_2026-07-26.md
2026-07-27 08:39:18 +00:00

10 KiB

🔴 Finance Module Recovery Blueprint — Root Cause Analysis & Recovery Plan

Date: 2026-07-26
Author: Architect Mode
Status: Pending Code Mode Execution
Priority: CRITICAL — Multiple systemic bugs across Frontend & Backend


STEP 1: ARCHITECTURAL COMPARISON — Dashboard vs. Finance

1.1 Layout Wrapper Comparison

Aspect DashboardView (Working) FinanceMainView (Broken)
Parent Layout PrivateLayout — provides BaseHeader with hamburger menu FinanceLayout — provides same BaseHeader but with HeaderBackButton
Background Self-contained: fixed inset-0 + bg-[url('@/assets/garage-bg.png')] in the component itself Duplicated in layout: same fixed inset-0 background in FinanceLayout
Header Teleport NO Teleport usage — DashboardView owns its own header via the background div USES Teleport: FinanceMainView teleports <span> into #header-teleport-target
Router Nesting Dash is a child of PrivateLayout Finance is a child of FinanceLayout

1.2 Card Component Comparison

Aspect Working Dashboard Cards Broken Finance Cards
Root Element Single <div> with CSS classes directly on the card (e.g., MyVehiclesCard.vue:2-4) Same pattern: plain <div> with identical CSS
Click Handler Invisible overlay <div> with @click (e.g., MyVehiclesCard.vue:6-9) Same pattern: invisible overlay with @click router push
Data Binding Uses Pinia stores (financeStore, vehicleStore, authStore) Same Pinia stores used
Component Import Imports card components from dashboard/ Standalone views — no card components imported, HTML inline

1.3 Key Architectural Difference

The only structural difference is the Teleport usage. The working DashboardView uses <Teleport to="body"> ONLY for its Flip Modal overlay, which is always available in the DOM. The broken FinanceMainView uses <Teleport to="#header-teleport-target"> — a target that is conditionally rendered inside BaseHeader, which itself is nested inside FinanceLayout.


STEP 2: ROOT CAUSE ANALYSIS OF 4 KNOWN BUGS

🔴 BUG 1: Teleport Freeze — Failed to locate Teleport target with selector "#header-teleport-target"

Root Cause: Vue Router lifecycle timing mismatch between Teleport mount/unmount and DOM availability.

Evidence:

  • FinanceMainView.vue:38-40 uses <Teleport to="#header-teleport-target"> to inject a page title
  • The target #header-teleport-target is defined in BaseHeader.vue:15
  • BaseHeader is rendered inside FinanceLayout.vue:35

Failure scenario:

  1. User navigates FROM /finance TO /dashboard
  2. Vue Router starts unmounting the /finance route tree: FinanceMainView → FinanceLayout → BaseHeader
  3. FinanceMainView tries to unmount its Teleported <span> from #header-teleport-target
  4. BUT BaseHeader (which contains the target) has already been destroyed or is in the process of being destroyed
  5. Vue throws: Failed to locate Teleport target with selector "#header-teleport-target"
  6. The app freezes because the Teleport cleanup throws an unhandled error that halts the transition

The same issue can happen on MOUNT: if FinanceMainView renders before BaseHeader finishes mounting, the Teleport target doesn't exist yet.

Contrast with DashboardView: The working DashboardView:101 teleports to "body" — which always exists in the DOM, regardless of component lifecycle.

🔴 BUG 2: Component Resolution Error — Failed to resolve component: TechDataTab

Root Cause: Missing import statement in VehicleDetailModal.vue.

Evidence:

  • VehicleDetailModal.vue:284 uses <TechDataTab /> in its template
  • The <script setup> section (lines 892-903) imports: VehiclePlateBadge, ProviderAutocomplete, ProviderQuickAddModal — but NOT TechDataTab
  • VehicleDetailsView.vue:112 correctly imports TechDataTab from '../../components/vehicles/tabs/TechDataTab.vue'
  • This is a copy-paste bug: the <TechDataTab /> usage was copied from VehicleDetailsView but the import was not

Failure scenario:

  1. User opens VehicleDetailModal and clicks the "Műszaki adatok" tab
  2. Vue tries to render <TechDataTab /> but cannot resolve the component
  3. Vue emits warning: [Vue warn]: Failed to resolve component: TechDataTab
  4. The tab shows blank/empty content

Note: This bug is NOT caused by the finance module changes — it's a pre-existing bug in VehicleDetailModal.vue.

🟡 BUG 3: Fragment/Emit Warning — Extraneous non-emits event listeners (deleted)

Root Cause: DashboardView.vue:169 listens for @deleted but the current VehicleFormModal.vue does not declare or emit deleted.

Evidence:

  • DashboardView.vue:164-170: <VehicleFormModal @deleted="onVehicleDeleted" />
  • VehicleFormModal.vue:1778-1782:
    const emit = defineEmits<{
      (e: 'close'): void
      (e: 'saved'): void
      // NO "deleted" event declared!
    }>()
    

Failure scenario:

  1. Vue 3 sees @deleted on the component but no matching emit declaration
  2. Vue tries to fall through and treat it as a native DOM event on the root element
  3. Since VehicleFormModal's root is <Teleport> (a fragment-like component), Vue cannot attach native DOM listeners
  4. Vue warns: Extraneous non-emits event listeners (deleted) were passed to component

🔴 BUG 4: Backend API Crash — HTTP 500 on GET /api/v1/providers/categories

Root Cause: The backend endpoint fails with an unhandled exception during ExpertiseTag query or ExpertiseCategoryOut serialization.

Evidence:

  • providers.py:149-183: The /categories endpoint queries ExpertiseTag and constructs ExpertiseCategoryOut objects
  • The endpoint wraps everything in try/except and returns HTTP 500 on any exception
  • Git status shows modified schema files — possible schema mismatch

Required diagnostic: Run docker compose logs --tail 100 sf_api after reproducing the 500 error.


STEP 3: THE RECOVERY BLUEPRINT

Phase 1: Stabilize the Frontend

Fix 1.1: Teleport Target Lifecycle (BUG 1)

  • File: frontend_app/src/views/FinanceMainView.vue
  • Solution: Wrap Teleport in a v-if guard tied to onMounted/onBeforeUnmount lifecycle:
    <Teleport v-if="teleportReady" to="#header-teleport-target">
    
  • Also apply to: FinanceWalletsView, FinancePackagesView, FinanceTransactionsView

Fix 1.2: Missing TechDataTab Import (BUG 2)

  • File: frontend_app/src/components/vehicle/VehicleDetailModal.vue
  • Action: Add import TechDataTab from '../vehicles/tabs/TechDataTab.vue' after line 903

Fix 1.3: Fragment Emit Warning (BUG 3)

  • File: frontend_app/src/components/dashboard/VehicleFormModal.vue
  • Action: Add (e: 'deleted'): void to defineEmits and emit on deletion

Phase 2: Diagnose & Fix the Backend

Fix 2.1: Backend 500 on providers/categories (BUG 4)

  • Restart sf_api container and capture live logs
  • Trigger the endpoint and analyze the Python traceback
  • Fix based on actual error (model/schema/DB issue)

STEP 4: CODE MODE HAND-OFF INSTRUCTIONS

Task 1: Fix Teleport Lifecycle (BUG 1) — CRITICAL

  1. Open: frontend_app/src/views/FinanceMainView.vue
  2. Add ref and onBeforeUnmount to vue imports (line 237)
  3. Add: const teleportReady = ref(false)
  4. In onMounted, add: teleportReady.value = true
  5. Add: onBeforeUnmount(() => { teleportReady.value = false })
  6. Change line 38: <Teleport v-if="teleportReady" to="#header-teleport-target">
  7. Apply same pattern to: FinanceWalletsView.vue, FinancePackagesView.vue, FinanceTransactionsView.vue

Task 2: Fix Missing TechDataTab Import (BUG 2) — HIGH

  1. Open: frontend_app/src/components/vehicle/VehicleDetailModal.vue
  2. After line 903, add:
    import TechDataTab from '../vehicles/tabs/TechDataTab.vue'
    

Task 3: Fix Fragment Emit Warning (BUG 3) — MEDIUM

  1. Open: frontend_app/src/components/dashboard/VehicleFormModal.vue
  2. At line 1778-1782, add (e: 'deleted'): void to defineEmits
  3. Find delete/archive handler, add emit('deleted') after successful operation

Task 4: Diagnose Backend 500 (BUG 4) — HIGH

  1. Run: docker compose restart sf_api
  2. Run diagnostic query:
    docker compose exec sf_api python3 -c "
    import asyncio
    from app.database import async_session
    from sqlalchemy import select, text
    from app.models.data.expertise_tag import ExpertiseTag
    async def main():
        async with async_session() as db:
            stmt = select(ExpertiseTag).order_by(ExpertiseTag.level, ExpertiseTag.id).limit(10)
            result = await db.execute(stmt)
            tags = result.scalars().all()
            print(f'Query OK: {len(tags)} tags')
    asyncio.run(main())
    "
    
  3. Based on output, fix providers.py or the ExpertiseTag model

Task 5: Verify All Fixes — MANDATORY

  1. Frontend build: cd frontend_app && npm run build 2>&1 | tail -20
  2. Backend imports: docker compose exec sf_api python3 -c "from app.main import app; print('OK')"
  3. Navigate /finance → all 4 cards → /dashboard — verify no freeze
  4. VehicleDetailModal → "Műszaki adatok" tab — verify renders

📋 Summary Table

# Bug Severity Root Cause File(s) to Fix
1 Teleport Freeze CRITICAL Teleport target lifecycle mismatch FinanceMainView.vue:38 + 3 sub-views
2 TechDataTab not found HIGH Missing import VehicleDetailModal.vue:284
3 Fragment emit warning MEDIUM Missing emit declaration VehicleFormModal.vue:1778
4 Backend 500 error HIGH Unhandled exception in /providers/categories providers.py:149-183 (needs live diagnosis)

⚠️ DO NOT MODIFY

The following files are working correctly and MUST NOT be changed:

  • DashboardView.vue — working dashboard
  • PrivateLayout.vue — working layout
  • BaseHeader.vue — working header
  • FinancialCard.vue — working card
  • MyVehiclesCard.vue — working card
  • router/index.ts — correct route configuration