201 lines
7.0 KiB
Markdown
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.*
|