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

201 lines
7.0 KiB
Markdown

# 🔧 Code Mode Hand-off Blueprint: 4-Bug Fix Bundle
**Created:** 2026-07-26 | **Mode:** Architect → Code
**Target Branch:** `main`
---
## 📊 Root Cause Analysis Summary
| # | Bug | Root Cause File | Severity |
|---|-----|----------------|----------|
| 1 | Logo navigation traps user in org scope | HeaderLogo.vue:33 | Medium |
| 2 | GET /wallet/balance → 500 for user 106 | billing_engine.py:625-633 | Critical |
| 3 | "705,000" shown for all users (N/A — no mock found) | N/A — see analysis below | Info |
| 4 | [intlify] Not found 'subscription.vehicles' | SubscriptionStatusWidget.vue:67,81 | Low |
---
## 🐛 Bug 1: Logo Navigation — Always Redirect to /dashboard
### File
`frontend_app/src/components/header/HeaderLogo.vue`
### Current Behavior (Lines 32-38)
```typescript
const targetRoute = computed(() => {
if (route.path.startsWith('/organization/')) {
const orgId = route.params.id
return orgId ? `/organization/${orgId}` : route.path
}
return '/dashboard'
})
```
When user is on `/organization/5/vehicles`, `route.path.startsWith('/organization/')` is `true`, so the logo navigates to `/organization/5` — the org root — NOT to `/dashboard`. This traps the user; there is no way to return to the private garage dashboard via the logo.
### Root Cause
The logo's `targetRoute` logic intentionally scopes the user to the org when on org pages, but this prevents escaping back to `/dashboard`.
### Fix
**Replace the entire `targetRoute` computed with:**
```typescript
const targetRoute = computed(() => '/dashboard')
```
**Rationale:** The `HeaderCompanySwitcher` component already provides organization context switching. The logo should always be a "global home" escape hatch. This follows the web standard: clicking the brand logo always returns to the root dashboard.
### Lines to Change
- **DELETE:** Lines 32-38
- **REPLACE WITH:** Single line `const targetRoute = computed(() => '/dashboard')`
### Verification
1. Log in as any user
2. Navigate to `/organization/:id/vehicles`
3. Click the `HeaderLogo` → must navigate to `/dashboard` (not stay on org pages)
---
## 🐛 Bug 2: Backend 500 Error — `float(None)` on Wallet Balance
### File
`backend/app/services/billing_engine.py``AtomicTransactionManager.get_wallet_summary()`
### Current Behavior (Lines 622-635)
```python
return {
"wallet_id": wallet.id,
"balances": {
"earned": float(wallet.earned_credits), # 💥 CRASHES if None
"purchased": float(wallet.purchased_credits), # 💥 CRASHES if None
"service_coins": float(wallet.service_coins), # 💥 CRASHES if None
"voucher": float(voucher_balance),
"total": float(
wallet.earned_credits + # 💥 CRASHES if None
wallet.purchased_credits +
wallet.service_coins +
voucher_balance
)
},
```
### Root Cause
The `Wallet` model defines credit columns as:
```python
earned_credits: Mapped[float] = mapped_column(Numeric(18, 4), server_default=text("0"))
```
`server_default` only applies at the DB level on INSERT. If a wallet row was created with explicit `None` values, or if the DB column contains `NULL` (e.g., from a migration or manual SQL), then `wallet.earned_credits` will be Python `None`. Calling `float(None)` raises `TypeError`, which falls through to the generic `Exception` handler returning HTTP 500.
Also: if the wallet row does NOT exist at all, `get_wallet_summary()` raises `ValueError(f"Wallet not found for user_id={user_id}")` → which IS caught as 404. So the 500 specifically means: **wallet row EXISTS but has NULL credit columns**.
### Fix
**Option A (Defensive — Preferred): Wrap all `float()` calls with `or 0`**
Change lines 625-634 to:
```python
"earned": float(wallet.earned_credits or 0),
"purchased": float(wallet.purchased_credits or 0),
"service_coins": float(wallet.service_coins or 0),
"voucher": float(voucher_balance or 0),
"total": float(
(wallet.earned_credits or 0) +
(wallet.purchased_credits or 0) +
(wallet.service_coins or 0) +
(voucher_balance or 0)
)
```
**Option B (Root Fix — also recommended): Run a DB repair script to fix NULL columns**
```sql
UPDATE identity.wallets SET earned_credits = 0 WHERE earned_credits IS NULL;
UPDATE identity.wallets SET purchased_credits = 0 WHERE purchased_credits IS NULL;
UPDATE identity.wallets SET service_coins = 0 WHERE service_coins IS NULL;
```
**BOTH Option A and Option B should be applied.**
### Verification
1. Find NULL wallets: `SELECT id, user_id FROM identity.wallets WHERE earned_credits IS NULL OR purchased_credits IS NULL OR service_coins IS NULL;`
2. Apply both fixes
3. GET /api/v1/billing/wallet/balance with JWT for user 106 → must return 200
---
## 🐛 Bug 3: "705,000" Mock Data — NOT a Code Issue
### Finding
**No mock/hardcoded data was found in any frontend file.** Searched all .vue, .ts, and .json files for 705000 — zero results.
The finance store properly fetches from the API and has zero-fallback:
```typescript
const totalCredits = computed(() => balance.value?.total ?? 0)
```
The "705,000" value is from a real database record — likely the admin test user's wallet. User 106 sees an error because their wallet has NULL credits. Once Bug 2 is fixed, user 106 will see 0 (either no wallet → creates one, or NULL columns → returned as 0).
### Action
**No code change needed for Bug 3.** Fixing Bug 2 resolves the 500 error cascade.
---
## 🐛 Bug 4: Missing i18n Keys in SubscriptionStatusWidget
### File
`frontend_app/src/components/dashboard/SubscriptionStatusWidget.vue`
### Missing Keys (Lines 52, 53, 67, 81)
- `subscription.vehicles` (Line 67, 81)
- `subscription.unlimited` (Line 81)
- `subscription.timeRemaining` (Line 52)
- `subscription.days` (Line 53)
None of these exist in `hu.ts` or `en.ts`.
### Fix
Add to BOTH `frontend_app/src/i18n/hu.ts` AND `frontend_app/src/i18n/en.ts` inside the `subscription` block, after `indefinite`:
**hu.ts — add:**
```typescript
vehicles: 'jármű',
unlimited: 'korlátlan',
timeRemaining: 'Hátralévő idő',
days: 'nap',
```
**en.ts — add:**
```typescript
vehicles: 'vehicles',
unlimited: 'unlimited',
timeRemaining: 'Time Remaining',
days: 'days',
```
---
## 📋 Execution Order (Code Mode)
1. **Bug 1** — HeaderLogo.vue: Change targetRoute to always return /dashboard
2. **Bug 4** — hu.ts + en.ts: Add 4 missing i18n keys
3. **Bug 2** — billing_engine.py: Add `or 0` to all float() conversions
4. **Bug 2 (DB)** — Run SQL to NULL→0 on identity.wallets credit columns
5. **Bug 3** — Verify fix, no code change needed
---
## 🔗 Files Summary
| File | Bug | Operation |
|------|-----|-----------|
| frontend_app/src/components/header/HeaderLogo.vue | #1 | Replace lines 32-38 |
| frontend_app/src/i18n/hu.ts | #4 | Add 4 keys after line 83 |
| frontend_app/src/i18n/en.ts | #4 | Add 4 keys after line 83 |
| backend/app/services/billing_engine.py | #2 | Modify lines 625-634 |
| identity.wallets (DB) | #2 | SQL UPDATE NULL→0 |
---
*Blueprint ready for Code Mode hand-off.*