pénzügyi modul továbbfejlesztése (csomagkezelés)
This commit is contained in:
167
docs/UI_STANDARDS.md
Normal file
167
docs/UI_STANDARDS.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 🎨 Service Finder UI Design System v2.1
|
||||
|
||||
> **Audit Date:** 2026-07-28
|
||||
> **Scope:** `frontend_app/` Vue 3 + Tailwind CSS project
|
||||
> **Principle:** Every component must derive its visual properties from this document.
|
||||
> **Toolkit:** Tailwind CSS v3 custom theme + `main.css` utility components.
|
||||
|
||||
---
|
||||
|
||||
## 1. 🖌️ Color Palette
|
||||
|
||||
### 1.1 Brand Identity (from `tailwind.config.js` + SVG logo)
|
||||
|
||||
| Token | Hex | Tailwind Class | Usage |
|
||||
|---|---|---|---|
|
||||
| **sf-blue** | `#004B63` | `bg-sf-blue` / `text-sf-blue` | Primary headers, main CTAs, dashboard cards |
|
||||
| **sf-accent** | `#008CA4` | `bg-sf-accent` / `text-sf-accent` | Hover states, secondary buttons, links |
|
||||
| **sf-green** | `#70BC84` | `bg-sf-green` / `text-[#70BC84]` | Progress bars, success badges, positive states |
|
||||
| **sf-wall** | `#B5DCE3` | `bg-sf-wall` | Light panels, background gradients |
|
||||
| **logo-dark-blue** | `#306081` | `bg-[#306081]` | Plan card headers, logo primary |
|
||||
| **logo-teal** | `#418890` | `bg-[#418890]` | Logo secondary elements |
|
||||
| **logo-green** | `#79B085` | `bg-[#79B085]` | Logo leaf/green elements |
|
||||
|
||||
### 1.2 Semantic Colors
|
||||
|
||||
| Token | Hex | Tailwind Class | Meaning |
|
||||
|---|---|---|---|
|
||||
| **success / emerald** | `#10b981` | `bg-emerald-500` / `text-emerald-600` | Confirmation, active status, savings |
|
||||
| **warning / amber** | `#f59e0b` | `bg-amber-400` / `text-amber-400` | Expiring soon (≤7 days), caution |
|
||||
| **danger / red** | `#ef4444` | `bg-red-500` / `text-red-600` | Expired, full capacity, delete actions |
|
||||
| **info / sky** | `#0284c7` | `bg-sky-600` | Subscription cards, neutral info |
|
||||
|
||||
### 1.3 Neutral & Background
|
||||
|
||||
| Token | Hex | Tailwind Class | Usage |
|
||||
|---|---|---|---|
|
||||
| **dark-bg** | `#04151F` | `bg-[#04151F]` | App body background |
|
||||
| **dark-lighter** | `#0a2a40` | `bg-[#0a2a40]` | Gradient top |
|
||||
| **white-solid** | `#ffffff` | `bg-white` | Content cards (light theme) |
|
||||
| **white-95** | `rgba(255,255,255,0.95)` | `bg-white/95` | Semi-transparent card faces |
|
||||
| **slate-50** | `#f8fafc` | `bg-slate-50` | Card inner sections (stats, inputs) |
|
||||
| **slate-200** | `#e2e8f0` | `border-slate-200` | Card inner borders |
|
||||
| **slate-500** | `#64748b` | `text-slate-500` | Secondary text on light cards |
|
||||
| **slate-700** | `#334155` | `bg-slate-700` / `text-slate-700` | Header bars, primary text on light |
|
||||
| **slate-800** | `#1e293b` | `text-slate-800` | Card body text color |
|
||||
|
||||
### 1.4 Gradient Presets
|
||||
|
||||
| Name | Tailwind | Usage |
|
||||
|---|---|---|
|
||||
| **sf-dark-bg** | `radial-gradient(...)` | `body` global background |
|
||||
| **header-amber** | `bg-gradient-to-r from-amber-600 to-amber-700` | Wallet card header |
|
||||
| **header-sky** | `bg-gradient-to-r from-sky-600 to-sky-700` | Subscription card header |
|
||||
| **header-emerald** | `bg-gradient-to-r from-emerald-600 to-emerald-700` | Transactions card header |
|
||||
| **header-violet** | `bg-gradient-to-r from-violet-600 to-violet-700` | Invoices card header |
|
||||
| **header-green** (add-on) | `bg-gradient-to-r from-emerald-600 to-emerald-700` | Add-on card headers |
|
||||
| **btn-premium** | `bg-gradient-to-r from-[#418890] to-[#70BC84]` | Premium buttons hover state |
|
||||
|
||||
---
|
||||
|
||||
## 2. 📐 Cards
|
||||
|
||||
### 2.1 Standard Card (Light Theme)
|
||||
|
||||
- **Radius:** Always `rounded-2xl` for cards
|
||||
- **Shadow:** `shadow-[0_8px_30px_rgb(0,0,0,0.12)]` by default, `shadow-2xl` on hover
|
||||
- **Hover:** `hover:-translate-y-3 hover:scale-[1.02]` — subtle lift + scale
|
||||
- **Height:** `h-[350px]` fixed height standard; use `h-auto` for list cards
|
||||
- **Background:** `bg-white/95` (semi-transparent white) for light theme cards
|
||||
- **Body text color:** `text-slate-800` inside card bodies
|
||||
- **Body padding:** `p-4` standard; `p-6` for "lg" variant
|
||||
- **Header colors:** Use semantic gradients: amber=wallet, sky=subscription, emerald=transactions, violet=invoices
|
||||
|
||||
### 2.2 Dark / Glass Card
|
||||
|
||||
- Use `bg-white/5` or `bg-white/10` for glass effect
|
||||
- Always add `backdrop-blur-md` for blur
|
||||
- Border: `border-white/10` or `border-white/20`
|
||||
- Text: `text-white/80` or `text-white/60` for secondary
|
||||
|
||||
### 2.3 BaseCard Component
|
||||
|
||||
Prefab component with `header`, default `body`, `footer` slots. See `BaseCard.vue`.
|
||||
|
||||
---
|
||||
|
||||
## 3. 🔘 Buttons
|
||||
|
||||
| Variant | Tailwind Classes |
|
||||
|---|---|
|
||||
| **btn-primary** | `px-4 py-2 bg-sf-blue text-white rounded-lg hover:bg-sf-accent transition-colors` |
|
||||
| **btn-secondary** | `px-4 py-2 bg-sf-wall text-sf-blue rounded-lg hover:bg-sf-green hover:text-white` |
|
||||
| **btn-premium** | `rounded-xl bg-white/10 px-6 py-3 font-bold text-white backdrop-blur-md border border-white/20` |
|
||||
| **btn-card-cta** | `w-full text-center px-4 py-2.5 rounded-xl text-sm font-semibold shadow-md hover:shadow-lg active:scale-[0.98]` |
|
||||
| **Disabled** | `bg-slate-400 cursor-not-allowed` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 📝 Typography
|
||||
|
||||
- **Font:** `Inter, system-ui, sans-serif`
|
||||
- **H1:** `text-3xl font-bold text-white`
|
||||
- **H2:** `text-2xl font-bold text-white`
|
||||
- **H3:** `text-lg font-bold text-slate-900`
|
||||
- **H4:** `text-sm font-semibold text-slate-700 uppercase tracking-wider`
|
||||
- **Body:** `text-sm text-slate-700` (light) / `text-sm text-white/60` (dark)
|
||||
- **Small:** `text-xs text-slate-500`
|
||||
|
||||
---
|
||||
|
||||
## 5. 🪟 Modals
|
||||
|
||||
- Backdrop: `bg-black/60 backdrop-blur-sm`
|
||||
- Close on backdrop click: `@click.self="$emit('close')"`
|
||||
- Max height: `max-h-[90vh]`
|
||||
- Transition: `modal-fade` 0.2s ease opacity
|
||||
- Panel: `bg-white rounded-2xl shadow-2xl overflow-hidden flex flex-col`
|
||||
- Header: colored bar with `text-white font-bold text-lg`
|
||||
- Footer: `bg-slate-50 border-t border-slate-200`
|
||||
|
||||
---
|
||||
|
||||
## 6. 🧩 Add-on Card Specification (Phase 4)
|
||||
|
||||
The add-on cards follow the **same solid card pattern** as base plan cards with a **green-themed header** to visually distinguish them.
|
||||
|
||||
### 6.1 Classes
|
||||
|
||||
| Element | Classes |
|
||||
|---|---|
|
||||
| **Wrapper (selected)** | `bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden border-emerald-500 ring-1 ring-emerald-500/50 transition-all duration-300 hover:-translate-y-2 hover:shadow-2xl` |
|
||||
| **Wrapper (unselected)** | `bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden border-slate-200 transition-all duration-300 hover:border-slate-400 hover:-translate-y-2 hover:shadow-2xl` |
|
||||
| **Header** | `h-12 bg-gradient-to-r from-emerald-600 to-emerald-700 w-full shrink-0 flex items-center px-4` |
|
||||
| **Header text** | `text-white font-bold text-sm tracking-wide` |
|
||||
| **Body** | `p-4 flex flex-col h-full text-slate-800` |
|
||||
| **Inner stat badges** | `bg-slate-50 text-slate-700` (match base plan stat boxes) |
|
||||
| **Price (main)** | `text-xl font-bold text-slate-900` |
|
||||
| **Price (small)** | `text-slate-500 text-xs` |
|
||||
| **Toggle (selected)** | `w-full py-2 rounded-xl text-sm font-semibold bg-emerald-600 hover:bg-emerald-700 text-white` |
|
||||
| **Toggle (unselected)** | `w-full py-2 rounded-xl text-sm font-semibold bg-slate-100 hover:bg-emerald-50 text-slate-700 hover:text-emerald-700 border border-slate-200 hover:border-emerald-300` |
|
||||
|
||||
### 6.2 What Changes from Current Glass Style
|
||||
|
||||
| Element | Current (broken) | Fix (target) |
|
||||
|---|---|---|
|
||||
| **Wrapper bg** | `bg-emerald-900/20` (glass, transparent on garage-bg) | `bg-white/95` (solid white, readable) |
|
||||
| **Wrapper border** | `border-slate-700` (invisible) | `border-slate-200` (visible) |
|
||||
| **Text color** | `text-white` (washes out on bg) | `text-slate-800` (solid dark on white) |
|
||||
| **Secondary text** | `text-white/40` (illegible) | `text-slate-500` (readable) |
|
||||
| **Toggle (unselected)** | `bg-white/10 text-white/80` (glass) | `bg-slate-100 text-slate-700 border border-slate-200` (solid) |
|
||||
| **Header** | None (no header bar) | Green gradient header with add-on name |
|
||||
| **Shadow** | None | `shadow-[0_8px_30px_rgb(0,0,0,0.12)]` |
|
||||
| **Hover** | None | `hover:-translate-y-2 hover:shadow-2xl` |
|
||||
| **Stat badges** | `bg-white/10 text-white/50` (invisible) | `bg-slate-50 text-slate-700` (same as base plans) |
|
||||
|
||||
---
|
||||
|
||||
## 7. ♿ Accessibility Checklist
|
||||
|
||||
- Minimum contrast ratio 4.5:1 for body text
|
||||
- Focus rings on all interactive elements (`focus:ring-2 focus:ring-sf-accent`)
|
||||
- `aria-label` on icon-only buttons
|
||||
- Keyboard navigation for modals (Escape to close)
|
||||
|
||||
---
|
||||
|
||||
*End of UI Design System v2.1*
|
||||
70
docs/finance_flip_card_and_filter_implementation.md
Normal file
70
docs/finance_flip_card_and_filter_implementation.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 🏗️ Finance Flip Card & Context-Aware Filter Implementation
|
||||
|
||||
**Date:** 2026-07-27
|
||||
**Author:** Core Developer (Fast Coder)
|
||||
**Gitea Card:** #424
|
||||
|
||||
---
|
||||
|
||||
## 📋 Summary
|
||||
|
||||
Implementation of two features for the Finance module plus an admin memory fix:
|
||||
|
||||
| # | Feature | Status |
|
||||
|---|---------|--------|
|
||||
| 1 | **CSS 3D Flipping Card** — Card 2 in `FinanceMainView.vue` | ✅ Done |
|
||||
| 2 | **Context-Aware Package Filter** — Enhanced `SubscriptionPlansView.vue` | ✅ Done |
|
||||
| 3 | **Admin Memory Fix** — `NODE_OPTIONS=--max-old-space-size=4096`, cache purge, restart | ✅ Done |
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Backend Changes
|
||||
|
||||
### File: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py)
|
||||
|
||||
1. **`_build_user_response()`** — Added `created_at` field from `User.created_at` (after line 131)
|
||||
2. **`read_users_me()`** — Added `subscription_valid_from` variable initialization (line 260)
|
||||
3. **`read_users_me()`** — Extracted `valid_from` in both org branch (after line 275) and personal branch (after line 290)
|
||||
4. **`read_users_me()`** — New add-on resolution query block (~35 lines) resolving active `OrganizationSubscription` or `UserSubscription` records where `SubscriptionTier.type == 'addon'`
|
||||
5. **`read_users_me()`** — Added `subscription_valid_from` and `active_addons` to response dict (after line 308)
|
||||
|
||||
### Contract: `/auth/me` response now includes:
|
||||
- `created_at` — ISO 8601 datetime of user registration
|
||||
- `subscription_valid_from` — ISO 8601 of current subscription start
|
||||
- `active_addons` — Array of `{tier_id, display_name, valid_from, valid_until, is_active}`
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Frontend Changes
|
||||
|
||||
### File: [`frontend_app/src/stores/auth.ts`](frontend_app/src/stores/auth.ts)
|
||||
- Added `user_registration_date`, `subscription_valid_from`, `active_addons` to `UserProfile` interface
|
||||
|
||||
### File: [`frontend_app/src/views/FinanceMainView.vue`](frontend_app/src/views/FinanceMainView.vue)
|
||||
- **Template:** Card 2 replaced with flip card structure (front face + back face)
|
||||
- **Script:** Added `ref` import, `isFlipped` state, `toggleFlip()` function, computed properties (`userRegistrationDate`, `subscriptionStartDate`, `activeAddons`), helper functions (`formatAddonDate`, `isAddonExpired`)
|
||||
- **CSS:** Added scoped 3D flip CSS with `perspective: 1000px`, `transform-style: preserve-3d`, `backface-visibility: hidden`, `rotateY(180deg)` on `.is-flipped`
|
||||
- **CTA button** uses `@click.stop` to prevent flip trigger
|
||||
|
||||
### File: [`frontend_app/src/views/SubscriptionPlansView.vue`](frontend_app/src/views/SubscriptionPlansView.vue)
|
||||
- Enhanced `filteredPlans` computed to check `plan.rules?.type` JSONB first, then fall back to name prefix matching
|
||||
- Supports: `corporate`, `business`, `private`, `consumer` rules types
|
||||
|
||||
### Files: [`frontend_app/src/i18n/hu.ts`](frontend_app/src/i18n/hu.ts), [`frontend_app/src/i18n/en.ts`](frontend_app/src/i18n/en.ts)
|
||||
- Added 7 new keys under `subscription:` block
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Admin Memory Fix
|
||||
|
||||
- **File:** [`docker-compose.yml:293`](docker-compose.yml:293) — `NODE_OPTIONS=--max-old-space-size=4096` was **already configured**
|
||||
- Purged `.nuxt/` cache via `docker compose exec sf_admin_frontend rm -rf /app/.nuxt/*`
|
||||
- Restarted container: `docker compose restart sf_admin_frontend`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Validation
|
||||
|
||||
- `sync_engine.py` — ✅ 1307 OK, 0 Fixed, 0 Shadow — **rendszer tökéletesen szinkronban van**
|
||||
- Backend changes compile without syntax errors
|
||||
- All i18n keys present in both locales
|
||||
181
docs/subscription_card_upgrade_analysis.md
Normal file
181
docs/subscription_card_upgrade_analysis.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# 📦 Subscription Card Upgrade — Részletes Elemzés
|
||||
|
||||
**Date:** 2026-07-27
|
||||
**Author:** System Architect
|
||||
**Gitea Card:** #423
|
||||
**Blueprint:** [`plans/logic_spec_subscription_card_upgrade.md`](../plans/logic_spec_subscription_card_upgrade.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Jelenlegi Állapot (As-Is)
|
||||
|
||||
### 1.1 A Card 2 a FinanceMainView-ban
|
||||
|
||||
A [`FinanceMainView.vue`](../frontend_app/src/views/FinanceMainView.vue) négy kártyát jelenít meg a `/finance` oldalon. A második kártya ("📦 Csomagok") jelenleg így néz ki:
|
||||
|
||||
- **Adatforrás:** [`authStore.user?.subscription_plan`](../frontend_app/src/stores/auth.ts:62) — egy nyers string, pl. `"private_pro_v1"`
|
||||
- **Megjelenítés:** A nyers slug jelenik meg a kártyán, minden formázás nélkül
|
||||
- **CTA:** Nincs dinamikus gomb, csak a teljes kártya kattintható (átnavigál `/finance/packages`-re)
|
||||
|
||||
```typescript
|
||||
// Jelenlegi adat forrása (FinanceMainView.vue:249-252)
|
||||
const subscriptionPlanName = computed(() => {
|
||||
return authStore.user?.subscription_plan || '—'
|
||||
})
|
||||
```
|
||||
|
||||
### 1.2 A Backend API (/auth/me)
|
||||
|
||||
A [`read_users_me()`](../backend/app/api/v1/endpoints/users.py:147) végpont **már feloldja** a `SubscriptionTier` rekordot a `max_vehicles` és `max_garages` limit meghatározásához (lines 206-253), de:
|
||||
|
||||
- ❌ **Nem** adja vissza a `SubscriptionTier.rules["display_name"]` JSONB mezőt
|
||||
- ❌ **Nem** adja vissza a `UserSubscription.valid_until` / `OrganizationSubscription.valid_until` lejárati dátumot
|
||||
- ❌ **Nem** adja vissza a `subscription_tier_id`-t
|
||||
|
||||
### 1.3 Mi Működik Már Most?
|
||||
|
||||
A [`SubscriptionStatusWidget.vue`](../frontend_app/src/components/dashboard/SubscriptionStatusWidget.vue) és a [`SubscriptionInfoModal.vue`](../frontend_app/src/components/subscription/SubscriptionInfoModal.vue) már rendelkezik a gazdagabb funkcionalitással (járműhasználati progress bar, lejárati dátum), de ezek sötét témájú komponensek, és a FinanceMainView világos témájú Card 2-jében nem használhatók közvetlenül.
|
||||
|
||||
---
|
||||
|
||||
## 2. Adatbázis Oldal (What Exists)
|
||||
|
||||
### 2.1 `system.subscription_tiers`
|
||||
|
||||
A [`SubscriptionTier`](../backend/app/models/core_logic.py:68) modell JSONB `rules` mezője tartalmazza:
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "Privát Pro",
|
||||
"allowances": { "max_vehicles": 3, "max_garages": 1 },
|
||||
"pricing": { "monthly_price": 9.99, "yearly_price": 99.99, "currency": "EUR" },
|
||||
"duration": { "days": 30 },
|
||||
"type": "private",
|
||||
"lifecycle": { "is_public": true },
|
||||
"marketing": { "badge": "Pro", "subtitle": "..." }
|
||||
}
|
||||
```
|
||||
|
||||
A `display_name` mező létezik a JSONB-ben, de jelenleg nem jut el a frontendig a `/auth/me`-n keresztül.
|
||||
|
||||
### 2.2 `finance.user_subscriptions` és `finance.org_subscriptions`
|
||||
|
||||
Mindkét tábla tartalmazza:
|
||||
- `tier_id` → FK a `system.subscription_tiers`-re
|
||||
- `valid_from` → Érvényesség kezdete
|
||||
- `valid_until` → Lejárat dátuma (None = nincs lejárat)
|
||||
- `is_active` → Aktív-e a subscription
|
||||
|
||||
A `SubscriptionService.get_user_subscription_details()` és `get_org_subscription_details()` metódusok már visszaadják ezeket az adatokat, de ezek a `GET /subscriptions/my` végpontokon keresztül érhetők el, nem a `/auth/me`-n.
|
||||
|
||||
---
|
||||
|
||||
## 3. A Terv (To-Be)
|
||||
|
||||
### 3.1 Adatfolyam
|
||||
|
||||
```
|
||||
/auth/me
|
||||
├─ subscription_plan: "private_pro_v1" (már most is)
|
||||
├─ subscription_display_name: "Privát Pro" (ÚJ)
|
||||
├─ subscription_expires_at: "2026-12-31T..." (ÚJ)
|
||||
├─ subscription_tier_id: 14 (ÚJ)
|
||||
├─ max_vehicles: 3 (már most is)
|
||||
└─ max_garages: 1 (már most is)
|
||||
│
|
||||
▼
|
||||
authStore.user
|
||||
│
|
||||
▼
|
||||
FinanceMainView Card 2
|
||||
┌─────────────────────────────────┐
|
||||
│ 📦 Előfizetési Csomagok [Aktív] │
|
||||
│ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Aktív │ │
|
||||
│ │ Privát Pro │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ │
|
||||
│ Lejárat: 2026.12.31 │
|
||||
│ │
|
||||
│ 🚗 jármű: ████████░░ 3 / 5 │
|
||||
│ │
|
||||
│ [ Csomag Kezelése ] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 Backend Változások (1 fájl)
|
||||
|
||||
**Fájl:** [`users.py`](../backend/app/api/v1/endpoints/users.py) — `read_users_me()` függvény
|
||||
|
||||
A már meglévő `SubscriptionTier` feloldási logika UTÁN (lines 206-253) hozzáadunk egy új blokkot, amely:
|
||||
1. Lekérdezi a `OrganizationSubscription` vagy `UserSubscription` rekordot (attól függően, hogy Corporate vagy Personal módban van a user)
|
||||
2. Kiolvassa a `valid_until` értéket
|
||||
3. Kiolvassa a már korábban feloldott `tier.rules["display_name"]` értéket
|
||||
4. Hozzáadja ezeket a `response_data` dict-hez
|
||||
|
||||
### 3.3 Frontend Változások (4 fájl)
|
||||
|
||||
| Fájl | Változás |
|
||||
|------|----------|
|
||||
| [`auth.ts`](../frontend_app/src/stores/auth.ts) | `UserProfile` interface bővítése `subscription_display_name` és `subscription_tier_id` mezőkkel |
|
||||
| [`FinanceMainView.vue`](../frontend_app/src/views/FinanceMainView.vue) | Card 2 template és computed property-k teljes cseréje |
|
||||
| [`hu.ts`](../frontend_app/src/i18n/hu.ts) | `renewNow`, `upgradePlan` i18n kulcsok |
|
||||
| [`en.ts`](../frontend_app/src/i18n/en.ts) | `renewNow`, `upgradePlan` i18n kulcsok |
|
||||
|
||||
---
|
||||
|
||||
## 4. Kockázatok és Fallback-ek
|
||||
|
||||
| Kockázat | Kezelés |
|
||||
|----------|---------|
|
||||
| A `display_name` null lehet régi tier rekordoknál | Fallback lánc: display_name → slug→human map → "Ingyenes" / "—" |
|
||||
| A `valid_until` None lehet (élettartam előfizetés) | A lejárati blokk csak `v-if="formattedExpiry"` esetén jelenik meg |
|
||||
| A `vehicleStore.vehicles` nincs betöltve | `onMounted`-ban fetch-eljük, ha szükséges |
|
||||
| Corporate vs. Personal mód routing | A `ctaRoute` computed kezeli mindkét esetet |
|
||||
|
||||
---
|
||||
|
||||
## 5. Kapcsolódó Fájlok Térképe
|
||||
|
||||
```
|
||||
backend/
|
||||
app/
|
||||
models/
|
||||
core_logic.py ← SubscriptionTier, UserSubscription, OrganizationSubscription
|
||||
api/v1/endpoints/
|
||||
users.py ← /auth/me — IDE KELL az új mezőket hozzáadni
|
||||
services/
|
||||
subscription_service.py ← SubscriptionService (már van get_user_subscription_details)
|
||||
|
||||
frontend_app/src/
|
||||
views/
|
||||
FinanceMainView.vue ← Card 2 — EZT ÍRJUK ÁT
|
||||
FinancePackagesView.vue ← /finance/packages (SubscriptionStatusWidget-et használ)
|
||||
components/
|
||||
dashboard/
|
||||
SubscriptionStatusWidget.vue ← Sötét témájú, gazdag widget (referencia)
|
||||
subscription/
|
||||
SubscriptionInfoModal.vue ← Modal részletes infóval (referencia)
|
||||
stores/
|
||||
auth.ts ← UserProfile interface bővítése
|
||||
vehicle.ts ← vehicleStore (már importálva más komponensekben)
|
||||
i18n/
|
||||
hu.ts ← Magyar fordítások
|
||||
en.ts ← Angol fordítások
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Ellenőrző Lista a Code Mode számára
|
||||
|
||||
- [ ] `users.py`: `subscription_display_name`, `subscription_expires_at`, `subscription_tier_id` feloldása
|
||||
- [ ] `auth.ts`: `UserProfile` interface bővítése
|
||||
- [ ] `FinanceMainView.vue`: Card 2 template csere (display_name, expiry, vehicle bar, CTA)
|
||||
- [ ] `FinanceMainView.vue`: Computed property-k hozzáadása
|
||||
- [ ] `FinanceMainView.vue`: `useVehicleStore` import és inicializálás
|
||||
- [ ] `hu.ts`: `renewNow`, `upgradePlan` kulcsok
|
||||
- [ ] `en.ts`: `renewNow`, `upgradePlan` kulcsok
|
||||
- [ ] `sync_engine.py` futtatása (nincs séma változás, de ellenőrzés)
|
||||
- [ ] Vizuális teszt: Card 2 helyes megjelenés ellenőrzése
|
||||
- [ ] Funkcionális teszt: CTA gomb navigáció ellenőrzése
|
||||
119
docs/subscription_info_modal_planDisplayName_bug_analysis.md
Normal file
119
docs/subscription_info_modal_planDisplayName_bug_analysis.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# SubscriptionInfoModal planDisplayName Bug Analysis
|
||||
|
||||
**Date:** 2026-07-28
|
||||
**Issue:** #433 - P0: Avatar Menu Subscription Modal - planDisplayName bugfix & DB audit
|
||||
|
||||
---
|
||||
|
||||
## 1. Database Investigation: admin@profibot.hu
|
||||
|
||||
### User Profile
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| `id` | 2 |
|
||||
| `email` | admin@profibot.hu |
|
||||
| `role` | ADMIN |
|
||||
| `subscription_plan` | private_vip_v1 |
|
||||
| `subscription_expires_at` | 2026-08-27 16:36:24+00 |
|
||||
| `is_vip` | true |
|
||||
| `scope_level` | system |
|
||||
| `scope_id` | 67 |
|
||||
| `ui_mode` | personal |
|
||||
|
||||
### Organization Memberships (3 orgs)
|
||||
| org_id | role | name | type | status | base_asset_limit |
|
||||
|--------|------|------|------|--------|-------------------|
|
||||
| 1 | ADMIN | Profibot Tester - Privat Garazs (#28) | individual | active | 100 |
|
||||
| 67 | OWNER | User Admin - Privat Garazs (#2) | individual | active | 100 |
|
||||
| 57 | OWNER | Teszt Autoszerviz Kft. | service_provider | pending_verification | 3 |
|
||||
|
||||
### Vehicle Distribution (vehicle.assets via current_organization_id)
|
||||
| org_id | Count | Sample plates |
|
||||
|--------|-------|---------------|
|
||||
| 1 | 19 | TEST-API-999, TEST-123, DRAFT-456, TEST-999, DRAFT-999, TEST-888, DRAFT-888, TEST-777 |
|
||||
| 67 | 3 | TEST-API-01 (archived), ABB112 (archived), ABC-123 (archived) |
|
||||
| 57 | 0 | No vehicles |
|
||||
|
||||
### Key Finding: No Duplication, No Data Corruption
|
||||
Vehicles are NOT duplicated across organizations. Each org has distinct vehicles. The perception of "azonos adatok" (same data) likely arises because:
|
||||
- Org 1 has 19 test vehicles (TEST-XXX, DRAFT-XXX pattern) making it appear bloated
|
||||
- Org 67 has only 3 archived vehicles
|
||||
- The KEP 3 showing "private_pro_v1" is the UI bug, not a data issue (admin's actual plan is private_vip_v1)
|
||||
|
||||
---
|
||||
|
||||
## 2. Root Cause: planDisplayName in SubscriptionInfoModal.vue
|
||||
|
||||
### Bug Location
|
||||
`SubscriptionInfoModal.vue:175-179` — the `planDisplayName` computed property:
|
||||
|
||||
```typescript
|
||||
const planDisplayName = computed(() => {
|
||||
const plan = authStore.user?.subscription_plan
|
||||
if (!plan || plan === 'FREE') return t('subscription.free')
|
||||
return plan // BUG: returns raw slug like "private_vip_v1"
|
||||
})
|
||||
```
|
||||
|
||||
### Why It's Broken
|
||||
The backend `_resolve_subscription_data()` in `users.py:45-191` already:
|
||||
1. Queries the `SubscriptionTier` assigned to the user/org
|
||||
2. Extracts `display_name` from `tier.rules` JSONB
|
||||
3. Returns it as `subscription_display_name` in `/auth/me` and `/users/me`
|
||||
|
||||
The `UserProfile` TypeScript interface in `auth.ts:68` already declares:
|
||||
```typescript
|
||||
subscription_display_name?: string | null
|
||||
```
|
||||
|
||||
The `SubscriptionInfoModal` ignores this field completely.
|
||||
|
||||
### Reference Fix (Already Implemented in SubscriptionStatusWidget)
|
||||
`SubscriptionStatusWidget.vue:124-150` has the correct 3-level fallback:
|
||||
1. `authStore.user?.subscription_display_name` (backend-resolved human name)
|
||||
2. Org-level `subscription_display_name` (corporate mode)
|
||||
3. Static slug-to-human mapping (fallback)
|
||||
4. Raw plan name (ultimate fallback)
|
||||
|
||||
### Additional Data Binding Verification
|
||||
|
||||
| Computed | Current Code | Status |
|
||||
|----------|-------------|--------|
|
||||
| maxVehicles | `authStore.user?.max_vehicles ?? 1` | CORRECT |
|
||||
| maxGarages | `authStore.user?.max_garages ?? 1` | CORRECT |
|
||||
| vehicleCount | `vehicleStore.vehicles.length` | CORRECT |
|
||||
| garageCount | `authStore.myOrganizations?.length ?? 0` | Context-dependent |
|
||||
| vehiclePercent/garagePercent | `count / max * 100` | CORRECT |
|
||||
|
||||
---
|
||||
|
||||
## 3. Fix Specification
|
||||
|
||||
### Required Change
|
||||
Replace `planDisplayName` computed in `SubscriptionInfoModal.vue:175-179` with the same 3-level fallback logic used by `SubscriptionStatusWidget.vue:124-150`.
|
||||
|
||||
### New Code
|
||||
```typescript
|
||||
const planDisplayName = computed(() => {
|
||||
// Priority 1: Backend-supplied human-readable name
|
||||
const displayName = authStore.user?.subscription_display_name
|
||||
if (displayName) return displayName
|
||||
|
||||
// Priority 2: Slug-to-human mapping fallback
|
||||
const plan = authStore.user?.subscription_plan
|
||||
if (!plan || plan === 'FREE') return t('subscription.free')
|
||||
|
||||
const slugToHuman: Record<string, string> = {
|
||||
'private_basic_v1': 'Privat Alap',
|
||||
'private_pro_v1': 'Privat Pro',
|
||||
'private_vip_v1': 'Privat VIP',
|
||||
'corp_free_v1': 'Ceges Ingyenes',
|
||||
'corp_basic_v1': 'Ceges Alap',
|
||||
'corp_pro_v1': 'Ceges Pro',
|
||||
}
|
||||
if (slugToHuman[plan]) return slugToHuman[plan]
|
||||
|
||||
// Priority 3: Raw plan name as ultimate fallback
|
||||
return plan
|
||||
})
|
||||
```
|
||||
142
docs/subscription_limit_bug_root_cause_analysis.md
Normal file
142
docs/subscription_limit_bug_root_cause_analysis.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 🔍 Subscription Limit Data Binding Bug — Root Cause Analysis
|
||||
|
||||
**Dátum:** 2026-07-28
|
||||
**Issue:** #431 (Gitea)
|
||||
**Súlyosság:** P1 — A felhasználó rossz limiteket lát a frontenden
|
||||
|
||||
---
|
||||
|
||||
## 1. A Felhasználó Által Jelentett Hiba
|
||||
|
||||
A frontend "Előfizetés Információ" modális ablakon (SubscriptionStatusWidget) és a FinanceMainView Card 2-n a következő látszik:
|
||||
- **Jármű Limit:** 1/1
|
||||
- **Garázs Limit:** 2/1
|
||||
|
||||
Egy Pro/VIP csomag esetén a valós limit `max_vehicles=3` (Pro) vagy `max_vehicles=10` (VIP) lenne.
|
||||
|
||||
---
|
||||
|
||||
## 2. Adatbázis Réteg — Valós Állapot
|
||||
|
||||
| Tábla | Rekord | Kulcs mező | Érték |
|
||||
|---|---|---|---|
|
||||
| `identity.users` | id=2 (admin@profibot.hu) | `subscription_plan` | `private_vip_v1` |
|
||||
| `identity.users` | id=2 | `scope_id` | `67` |
|
||||
| `identity.users` | id=2 | `scope_level` | `system` |
|
||||
| `identity.users` | id=2 | `ui_mode` | `personal` |
|
||||
| `finance.user_subscriptions` | id=34 | `tier_id` | **15** (VIP) ✅ AKTÍV |
|
||||
| `finance.user_subscriptions` | id=33 | `tier_id` | 14 (Pro) ❌ inaktív |
|
||||
| `finance.org_subscriptions` | org_id=67 | — | **NINCS REKORD** |
|
||||
| `system.subscription_tiers` | id=14 (private_pro_v1) | `allowances.max_vehicles` | 3 |
|
||||
| `system.subscription_tiers` | id=14 | `allowances.max_garages` | 1 |
|
||||
| `system.subscription_tiers` | id=14 | `display_name` | **NULL** ❌ |
|
||||
| `system.subscription_tiers` | id=15 (private_vip_v1) | `allowances.max_vehicles` | 10 |
|
||||
| `system.subscription_tiers` | id=15 | `allowances.max_garages` | 2 |
|
||||
| `system.subscription_tiers` | id=15 | `display_name` | "Privát VIP" ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 3. ROOT CAUSE #1 (Elsődleges) — Hibás Vezérlési Logika
|
||||
|
||||
**Fájl:** [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py), `read_users_me()` függvény, sorok 212–254.
|
||||
|
||||
### A problémás kódszakasz (pszeudokód):
|
||||
|
||||
```python
|
||||
if active_org_id is not None: # ← scope_id=67 → active_org_id=67 → IGAZ
|
||||
# 1. Org subscription keresés
|
||||
tier = org_sub_query(scalar_one_or_none)
|
||||
if tier and tier.rules: # ← NINCS org subscription (üres finance.org_subscriptions org_id=67-re)
|
||||
allowances = tier.rules.get("allowances", {})
|
||||
max_vehicles = int(allowances.get("max_vehicles", 1))
|
||||
max_garages = int(allowances.get("max_garages", 1))
|
||||
else:
|
||||
# 2. Fallback: Organization.base_asset_limit
|
||||
org = select(Organization).where(id == active_org_id)
|
||||
max_vehicles = org.base_asset_limit or 1 # ← 1-et ad!
|
||||
max_garages = 1 # ← 1-et ad!
|
||||
# ❌ HIBA: Soha nem nézi meg a user_subscriptions táblát!
|
||||
else:
|
||||
# 3. Personal mode: user subscription keresés
|
||||
# ← SOHA NEM FUT LE, mert active_org_id != None
|
||||
tier = user_sub_query(scalar_one_or_none)
|
||||
if tier and tier.rules:
|
||||
allowances = tier.rules.get("allowances", {})
|
||||
max_vehicles = int(allowances.get("max_vehicles", 1)) # ← 10 lenne!
|
||||
max_garages = int(allowances.get("max_garages", 1)) # ← 2 lenne!
|
||||
```
|
||||
|
||||
### Miért hibás?
|
||||
|
||||
A user-nek van `scope_id=67`-e (egy szervezet ID), de az adott szervezethez **nincs org subscription** rekord. A felhasználónak viszont van **aktív user subscription**-e a VIP tier-re (tier_id=15, max_vehicles=10, max_garages=2).
|
||||
|
||||
A kód hibásan feltételezi, hogy ha van `active_org_id`, akkor:
|
||||
1. vagy van org subscription (→ abból olvassa a limiteket),
|
||||
2. vagy nincs (→ `Organization.base_asset_limit` fallback, ami 1).
|
||||
|
||||
**Soha nem esik át a user subscription ágra**, mert `active_org_id is not None`.
|
||||
|
||||
### Hatás
|
||||
|
||||
A `GET /auth/me` válaszban:
|
||||
```json
|
||||
{
|
||||
"max_vehicles": 1, // ← 10 helyett
|
||||
"max_garages": 1, // ← 2 helyett
|
||||
"subscription_display_name": null // ← mert a tier változó None
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. ROOT CAUSE #2 (Másodlagos) — private_pro_v1 display_name NULL
|
||||
|
||||
**Tábla:** `system.subscription_tiers` id=14
|
||||
**Mező:** `rules->>'display_name'` = NULL
|
||||
|
||||
A seederben (`seed_packages.py:131`) a `display_name` "Privát Pro"-ként van definiálva, de az éles adatbázisban NULL. Valószínűleg egy korábbi migráció vagy kézi módosítás írta felül.
|
||||
|
||||
Ez jelenleg nem okoz közvetlen hibát a felhasználónál (mert ő VIP-n van), de a Pro csomagra váltó felhasználóknál a display_name üres lenne.
|
||||
|
||||
---
|
||||
|
||||
## 5. Javítási Terv
|
||||
|
||||
### 5.1 Backend Fix (`users.py`)
|
||||
|
||||
A `read_users_me()` függvényben a fallback láncot ki kell egészíteni:
|
||||
|
||||
```
|
||||
if active_org_id is not None:
|
||||
1. Org subscription keresés (MEGLÉVŐ)
|
||||
2. Ha nincs → User subscription keresés (ÚJ!)
|
||||
3. Ha user subscription sincs → Organization.base_asset_limit (MEGLÉVŐ fallback)
|
||||
else:
|
||||
4. User subscription keresés (MEGLÉVŐ)
|
||||
```
|
||||
|
||||
Ugyanez a logika kell a `subscription_valid_until` / `subscription_tier_id` feloldására is (sorok 267–308), valamint a `subscription_display_name` feloldására (sor 311–312).
|
||||
|
||||
### 5.2 DB Fix
|
||||
|
||||
```sql
|
||||
UPDATE system.subscription_tiers
|
||||
SET rules = jsonb_set(rules, '{display_name}', '"Privát Pro"')
|
||||
WHERE id = 14 AND rules->>'display_name' IS NULL;
|
||||
```
|
||||
|
||||
### 5.3 Frontend
|
||||
|
||||
A frontend komponensek (`FinanceMainView.vue`, `SubscriptionStatusWidget.vue`) már helyesen olvassák az `authStore.user?.max_vehicles` és `authStore.user?.max_garages` értékeket. A backend fix után automatikusan a helyes értékek jelennek meg.
|
||||
|
||||
---
|
||||
|
||||
## 6. Érintett Fájlok
|
||||
|
||||
| Fájl | Változtatás |
|
||||
|---|---|
|
||||
| `backend/app/api/v1/endpoints/users.py` | `read_users_me()`: fallback lánc javítása |
|
||||
| Adatbázis (SQL) | `private_pro_v1` display_name beállítása |
|
||||
| `frontend_app/src/views/FinanceMainView.vue` | Nincs változtatás szükséges (már a helyes mezőket olvassa) |
|
||||
| `frontend_app/src/components/dashboard/SubscriptionStatusWidget.vue` | Nincs változtatás szükséges |
|
||||
|
||||
Reference in New Issue
Block a user