214 lines
10 KiB
Markdown
214 lines
10 KiB
Markdown
# 🔴 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`:
|
|
```ts
|
|
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:
|
|
```html
|
|
<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:
|
|
```ts
|
|
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:
|
|
```bash
|
|
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
|