felhasználói felületre pü

This commit is contained in:
Roo
2026-07-27 08:39:18 +00:00
parent c9118bf52f
commit 6f28d3e70d
72 changed files with 6311 additions and 749 deletions

View File

@@ -0,0 +1,203 @@
# P0 Root Cause Analysis: POST /api/v1/expenses/ 500 Internal Server Error
**Date:** 2026-07-26
**Gitea Issue:** [#421](https://gitea.kincses.dev/kincses/service-finder/issues/421)
**Severity:** P0 (Critical — breaks entire expense creation flow)
**Status:** Architect Analysis Complete — Awaiting Code Fix
---
## 📋 Executive Summary
The `POST /api/v1/expenses/` endpoint crashes with a `500 Internal Server Error` because the helper function `_check_org_capability()` contains a **broken import path** and a **wrong join column name**. Both defects are located in `backend/app/api/v1/endpoints/expenses.py`.
---
## 🔍 Root Cause Trace
### Traceback (from `docker compose logs sf_api`)
```
File "/app/app/api/v1/endpoints/expenses.py", line 377, in create_expense
can_approve = await _check_org_capability(db, current_user.id, organization_id, "can_approve_expense")
File "/app/app/api/v1/endpoints/expenses.py", line 104, in _check_org_capability
from app.models.fleet.org_role import OrgRole
ModuleNotFoundError: No module named 'app.models.fleet.org_role'
```
### Call Flow
```mermaid
flowchart TD
A["POST /api/v1/expenses/"] --> B["expenses.py: create_expense() line 377"]
B --> C["_check_org_capability() line 93"]
C --> D["line 104: from app.models.fleet.org_role import OrgRole"]
D --> E["ModuleNotFoundError 💥"]
E --> F["500 Internal Server Error"]
```
---
## 🐛 Bug #1: Wrong Import Path (line 104)
### Broken Code
```python
# File: backend/app/api/v1/endpoints/expenses.py, line 104
from app.models.fleet.org_role import OrgRole
```
### Why It Fails
There is **no directory** `backend/app/models/fleet/` and no file `org_role.py` at that path.
### Correct Import
The `OrgRole` model lives in `backend/app/models/marketplace/organization.py`:
```python
from app.models.marketplace.organization import OrgRole
```
### Evidence: Other Files Import Correctly
| File | Import | Status |
|------|--------|--------|
| `backend/app/api/deps.py:13` | `from app.models.marketplace.organization import OrgRole, OrganizationMember` | ✅ Correct |
| `backend/app/api/v1/endpoints/assets.py:14` | `from app.models import ... OrgRole` (via `__init__.py`) | ✅ Correct |
| `backend/app/api/v1/endpoints/organizations.py:23` | `from app.models.marketplace.organization import ... OrgRole` | ✅ Correct |
| `backend/app/api/v1/endpoints/expenses.py:104` | `from app.models.fleet.org_role import OrgRole` | ❌ **BROKEN** |
---
## 🐛 Bug #2: Wrong Join Column (line 108)
### Broken Code
```python
# File: backend/app/api/v1/endpoints/expenses.py, lines 106-114
stmt = (
select(OrgRole.permissions)
.join(OrganizationMember, OrganizationMember.role == OrgRole.name) # ← BUG
.where(
OrganizationMember.user_id == user_id,
OrganizationMember.organization_id == organization_id,
OrganizationMember.status == "active",
)
)
```
### Why It Fails
The `OrgRole` model's column is `name_key` (line 55 of the model), **not** `name`:
```python
class OrgRole(Base):
__tablename__ = "org_roles"
__table_args__ = {"schema": "fleet"}
name_key: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
# ... no 'name' column exists
```
Attempting `OrgRole.name` would raise an `AttributeError` after the import is fixed, causing a second crash.
### Correct Join
```python
.join(OrganizationMember, OrganizationMember.role == OrgRole.name_key)
```
### Evidence: `assets.py` Has the Correct Pattern
The `assets.py` endpoint uses the identical capability-check pattern correctly:
```python
# assets.py line 80-83 — CORRECT pattern
role_stmt = select(OrgRole.permissions).where(
OrgRole.name_key == org_role_name, # ← Uses name_key, not .name
OrgRole.is_active == True
).limit(1)
```
---
## 🧩 Impact Analysis
### What's Broken
- **Entire `POST /api/v1/expenses/` endpoint** — all expense creation requests fail with 500
- The crash occurs at the **RBAC Phase 2 capability check** (JSONB-based `can_approve_expense`)
- This blocks the P0 Smart Expense Workflow (odometer normalization, VAT calculation, gamification hooks, AssetEvent linking)
### What Still Works
- `GET /api/v1/expenses/` — list all expenses (does not call `_check_org_capability`)
- `GET /api/v1/expenses/{asset_id}` — list asset expenses (does not call `_check_org_capability`)
- `PUT /api/v1/expenses/{expense_id}` — update expense (does not call `_check_org_capability`)
### Why This Was Not Caught
1. The function uses a lazy import inside the function body — the import error only surfaces at runtime, not at module load time.
2. No e2e test exercises the `POST /expenses/` path through this function.
3. The `backend/app/tests/e2e/test_expense_flow.py` test targets `POST /api/v1/expenses/add`, which is a **different, legacy endpoint** — not the current `POST /api/v1/expenses/`.
---
## 🔧 Fix Plan (2 Lines, 1 File)
### File: `backend/app/api/v1/endpoints/expenses.py`
**Change 1 — Line 104:** Replace the broken import:
```diff
- from app.models.fleet.org_role import OrgRole
+ from app.models.marketplace.organization import OrgRole
```
**Change 2 — Line 108:** Fix the join column:
```diff
- .join(OrganizationMember, OrganizationMember.role == OrgRole.name)
+ .join(OrganizationMember, OrganizationMember.role == OrgRole.name_key)
```
### Optional: Hoist to top-level imports
Since `OrganizationMember` is already imported at the top of the file (line 46), move `OrgRole` there too:
```python
# line 46 area — add OrgRole
from app.models.marketplace.organization import OrganizationMember, OrgRole
```
Then remove the lazy import body from `_check_org_capability()`.
---
## 🧪 Verification
1. **Unit test:** `POST /api/v1/expenses/` with valid payload → 201 Created
2. **Capability check:** User with `can_approve_expense: true` → expense `APPROVED`
3. **Capability check:** User without `can_approve_expense``PENDING_APPROVAL`
4. **No ModuleNotFoundError** at any point
---
## 📊 Summary
| Item | Detail |
|------|--------|
| **Endpoint** | `POST /api/v1/expenses/` |
| **Error** | `ModuleNotFoundError: No module named 'app.models.fleet.org_role'` |
| **Crash File** | `backend/app/api/v1/endpoints/expenses.py` |
| **Crash Line** | 104 |
| **Root Cause 1** | Wrong import: `app.models.fleet.org_role``app.models.marketplace.organization` |
| **Root Cause 2** | Wrong column: `OrgRole.name``OrgRole.name_key` |
| **Gitea Issue** | #421 |
| **Fix Complexity** | Trivial (2 lines) |
| **Risk** | Minimal |
---
*Analysis by: Service Finder Rendszer-Architect — 2026-07-26*