Files

413 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md ERP Nutzfahrzeuge Implementation Guide
**Version:** 1.0.0
**Datum:** 2026-07-12
---
## 1. Project Overview
ERP-System für Nutzfahrzeug-/Baumaschinen-Handel (~10 Nutzer).
- **Backend:** Python 3.12 / FastAPI / SQLAlchemy 2.0 async / PostgreSQL 16 / Redis 7
- **Frontend:** Next.js 14 (App Router) / React 18 / TypeScript / Tailwind CSS / next-intl
- **Hosting:** Coolify (Docker Compose) auf coolify-01 (46.225.91.159)
- **KI:** OpenRouter (Qwen2.5-VL OCR, Flux.1-Pro Bild, Claude/GPT-4 Copilot)
- **External:** mobile.de Seller API (Push-Only)
### Architecture Reference
- `docs/architecture.md` Complete architecture, DB schema, API design, ADRs
- `docs/task_graph.json` Task breakdown with test specs
- `docs/requirements.md` Full requirements (8 modules, 23 features)
- `docs/component_inventory.md` UI components and states
---
## 2. Build & Test Commands
### Backend
```bash
# Install dependencies
cd backend
pip install -r requirements.txt
# Run dev server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Run all tests
python -m pytest tests/ -v --cov=app --cov-report=term-missing --cov-report=html
# Run specific test file
python -m pytest tests/test_vehicles.py -v
# Run with coverage for specific module
python -m pytest tests/test_auth.py -v --cov=app/services/auth_service --cov-report=term-missing
# Lint
ruff check app/
ruff format app/ --check
# Database migrations
alembic revision --autogenerate -m "description"
alembic upgrade head
alembic downgrade -1
# Type check (optional but recommended)
mypy app/ --ignore-missing-imports
```
### Frontend
```bash
cd frontend
npm install
# Run dev server
npm run dev
# Run all tests
npx vitest run --coverage
# Run specific test
npx vitest run src/components/vehicles --coverage
# Lint
npx eslint src/ --max-warnings 0
npx prettier --check src/
# Build
npm run build
# Type check
npx tsc --noEmit
```
### Docker
```bash
# Build and start all services
docker-compose up -d --build
# View logs
docker-compose logs -f backend
docker-compose logs -f frontend
# Run database migration in container
docker-compose exec backend alembic upgrade head
# Run tests in container
docker-compose exec backend python -m pytest tests/ -v
```
---
## 3. Test Rules (MANDATORY)
### TDD Principle
- **Write tests first or alongside implementation** never after
- Every PR must include tests
- Coverage target: **≥80%** per module
### Do NOT Modify Existing Tests
- Tests are written by the task spec and must not be modified to make them pass
- If a test fails, fix the **implementation**, not the test
- Exception: test data fixtures can be extended, but existing assertions must not be weakened
### Backend Test Conventions
- **Framework:** pytest + pytest-asyncio + httpx.AsyncClient
- **Test DB:** Separate PostgreSQL database `erp_test` (NOT SQLite)
- **Fixtures:** `conftest.py` provides:
- `async_db` async SQLAlchemy session (rolled back after each test)
- `client` httpx AsyncClient with app
- `auth_headers` JWT headers for admin/verkaeufer/buchhaltung roles
- `seed_data` minimal seed data (users, vehicle, contact)
- **Mocking:** OpenRouter API MUST be mocked in all tests (no real API calls)
- **Naming:** `test_<feature>_<scenario>.py` or `test_<module>.py`
### Frontend Test Conventions
- **Framework:** Vitest + React Testing Library
- **E2E:** Playwright for critical paths (login, vehicle create, sale wizard)
- **Mocking:** API calls mocked via `vi.mock()` or MSW (Mock Service Worker)
- **Naming:** `ComponentName.test.tsx` in `__tests__/` folder
### Test Spec Compliance
Every task in `task_graph.json` has a `test_spec` with:
- `commands` Exact commands to run
- `expected_results` What success looks like
- `test_files` Expected test file paths
- `coverage_target` Minimum coverage percentage
**ALL commands in test_spec.commands MUST pass before a task is considered done.**
---
## 4. Code Conventions
### Python (Backend)
```python
# Naming
snake_case for variables, functions, methods, modules
PascalCase for classes (Models, Schemas, Services)
UPPER_SNAKE_CASE for constants
# Imports (ruff isort)
# Standard library
import os
from datetime import datetime
# Third party
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
# Local
from app.deps import get_db, get_current_user
from app.models.vehicle import Vehicle
from app.schemas.vehicle import VehicleCreate, VehicleResponse
# Type hints (mandatory)
async def get_vehicle(vehicle_id: UUID, db: AsyncSession) -> Vehicle:
...
# Async everywhere (SQLAlchemy 2.0 async)
result = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
vehicle = result.scalar_one_or_none()
# Error handling
if vehicle is None:
raise HTTPException(status_code=404, detail={"error": {"code": "NOT_FOUND", "message": "Vehicle not found"}})
```
### TypeScript (Frontend)
```typescript
// Naming
camelCase for variables, functions, hooks
PascalCase for components, types, interfaces
UPPER_SNAKE_CASE for constants
// Imports order
// 1. React/Next
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
// 2. Third party
import { useTranslations } from 'next-intl';
// 3. Local
import { Button } from '@/components/ui/Button';
import { apiClient } from '@/lib/api-client';
// Components: function declaration with explicit return type
export function FahrzeugListe(): JSX.Element {
...
}
// Types: interface for props
type FahrzeugListeProps = {
vehicles: Vehicle[];
isLoading: boolean;
};
```
### File Organization
- **One model per file** in `models/`
- **One router per module** in `routers/`
- **One service per domain** in `services/`
- **One schema set per module** in `schemas/`
- **Components grouped by feature** in `components/<feature>/`
---
## 5. Forbidden Patterns
### Backend
-**No raw SQL queries** always use SQLAlchemy ORM
-**No synchronous database calls** always async (`await db.execute(...)`)
-**No secrets in code** use environment variables / pydantic-settings
-**No `print()` in production code** use Python `logging`
-**No bare `except:`** always catch specific exceptions
-**No `# type: ignore`** without comment explaining why
-**No circular imports** use dependency injection
-**No business logic in routers** routers only validate + call service
-**No direct model access from routers** always go through service layer
-**No unencrypted PII** Ausweisdaten MUST be AES-256 encrypted
-**No real OpenRouter API calls in tests** always mock
-**No `commit()` in services without explicit reason** let caller control transactions
### Frontend
-**No `any` type** use proper TypeScript types
-**No `console.log` in production** use a logger utility
-**No inline styles** use Tailwind classes or CSS modules
-**No direct `fetch()` without auth wrapper** use `apiClient`
-**No hardcoded API URLs** use `NEXT_PUBLIC_API_URL` env var
-**No hardcoded translation strings** use `t('key')` from next-intl
-**No prop drilling > 2 levels** use context or state management
-**No `useEffect` for data fetching without loading/error states**
-**No forms without validation** all inputs must have validation
-**No missing `aria-label`** on icon-only buttons
### General
-**No commits to main without passing tests**
-**No large PRs** max 800 lines per task
-**No TODO comments without ticket reference**
-**No commented-out code in PRs**
-**No files > 500 lines** split into modules
---
## 6. Token Rule for File Operations
When working with files (reading, creating, updating):
### Backend (Forgejo API)
```json
// READ file (only for MODIFY, not for reference)
{"tool_name":"forgejo","tool_args":{"action":"files_get","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/vehicle.py"}}
// CREATE new file
{"tool_name":"forgejo","tool_args":{"action":"files_create","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/vehicle.py","content":"<content>","message":"Add vehicle model","branch":"main"}}
// UPDATE existing file (requires SHA from files_get)
{"tool_name":"forgejo","tool_args":{"action":"files_update","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/vehicle.py","content":"<updated content>","sha":"<SHA>","message":"Update vehicle model","branch":"main"}}
// CHECK file existence
{"tool_name":"forgejo","tool_args":{"action":"files_list","owner":"Leopoldadmin","repo":"erp-nutzfahrzeuge","path":"backend/app/models/"}}
```
### Rules
- Use `files_get` ONLY when you need to modify an existing file (need SHA)
- Use `files_create` for new files
- Use `files_list` to check existence before creating
- **NEVER** copy file contents inline in task descriptions reference by path
- **NEVER** read entire files just for reference use `curl + sed` for snippets
- For large reference content (architecture.md, requirements.md): reference by path, don't inline
---
## 7. Task Execution Workflow
### For implementation_engineer
1. **Read the task** from `task_graph.json` (task ID: T0X)
2. **Read architecture.md** sections relevant to the task (DB schema, API design, ADRs)
3. **Read requirements.md** for the specific feature IDs listed in `requirement_ids`
4. **Implement backend first:** Model → Schema → Service → Router → Tests
5. **Implement frontend:** Components → Pages → Tests
6. **Run ALL test_spec.commands** and ensure they pass
7. **Run lint** (ruff for backend, eslint for frontend)
8. **Run build** (npm run build for frontend)
9. **Report results** with test output evidence (not just "done")
### Evidence Requirements (MANDATORY)
- Test command output showing pass/fail counts
- Coverage report showing ≥80%
- Lint output showing 0 errors
- Build output showing success
- "File written" is NOT evidence. "Commit made" is NOT evidence.
- Test output with pass counts IS evidence.
### Task Completion Checklist
- [ ] All acceptance_criteria from task_graph.json verified
- [ ] All test_spec.commands pass
- [ ] Coverage ≥ coverage_target for covered modules
- [ ] Ruff lint clean (backend)
- [ ] ESLint clean (frontend)
- [ ] Build succeeds (frontend)
- [ ] No forbidden patterns introduced
- [ ] Test evidence provided in report
---
## 8. Environment Variables (Names Only)
### Backend
```
DATABASE_URL=postgresql+asyncpg://erp_user:${DB_PASSWORD}@postgres:5432/erp_db
REDIS_URL=redis://redis:6379/0
JWT_SECRET=${JWT_SECRET}
JWT_ALGORITHM=HS256
JWT_ACCESS_TTL_MINUTES=15
JWT_REFRESH_TTL_DAYS=7
ENCRYPTION_KEY=${ENCRYPTION_KEY}
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
MOBILE_DE_API_KEY=${MOBILE_DE_API_KEY}
MOBILE_DE_SELLER_ID=${MOBILE_DE_SELLER_ID}
UPLOAD_DIR=/data/uploads
MAX_FILE_SIZE_MB=50
CORS_ORIGINS=https://erp.domain.tld
```
### Frontend
```
NEXT_PUBLIC_API_URL=http://backend:8000/api/v1
NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
```
---
## 9. Git Workflow
### Branches
- `main` Production branch, deployed via Coolify
- `feature/T0X-<short-name>` Feature branch per task
- `fix/T0X-<short-name>` Fix branch
### Commit Messages
```
feat(T02): add vehicle CRUD with type-specific fields
fix(T02): correct FIN validation for 17-char check
test(T02): add mobile.de batch push tests
refactor(T02): extract field mapping to separate function
docs(T02): update vehicle API documentation
```
### PR Process
1. Create feature branch from main
2. Implement + test + lint + build
3. Commit with conventional commit format
4. Push branch
5. Create PR with task ID reference
6. CI runs: pytest, ruff, vitest, eslint, build
7. Merge after CI passes
---
## 10. Database Migration Rules
- **Always use Alembic** for schema changes
- **Never** modify database directly (psql, pgadmin)
- **One migration per schema change** don't bundle multiple changes
- **Test migration up AND down** before committing
- **Seed data:** Use a separate seed script (`scripts/seed.py`), not migrations
- **Cleanup after migration tasks:**
```bash
alembic upgrade head # Apply
# Verify all tables created
python -c "from app.database import engine; from sqlalchemy import inspect; insp = inspect(engine); print(insp.get_table_names())"
# Run seed data
python scripts/seed.py
# Verify foreign keys
docker-compose exec postgres psql -U erp_user -d erp_db -c "\d vehicles"
```
---
## 11. OpenRouter Integration Rules
- **Always use** `openrouter_client.py` shared client never call OpenRouter API directly
- **Always set timeout** to 30 seconds
- **Always log** interaction to `ai_interactions` table (type, model, tokens, metadata)
- **Never send** Ausweisdaten or personal customer data to OpenRouter
- **Mock OpenRouter** in all automated tests
- **Handle errors gracefully:** API error → 502, timeout → 504, rate limit → 429
- **Model selection** via config, not hardcoded in service
---
## 12. Security Checklist
- [ ] JWT authentication on all endpoints (except /auth/login, /auth/refresh, /health)
- [ ] RBAC enforced (require_role decorator on every router)
- [ ] Input validation via Pydantic schemas on all endpoints
- [ ] SQL injection protection via SQLAlchemy ORM (no raw SQL)
- [ ] File upload: MIME-type allowlist + size limit
- [ ] Ausweisdaten: AES-256-GCM encryption
- [ ] CORS: only configured origins
- [ ] Audit log: all create/update/delete actions logged
- [ ] No secrets in code or git
- [ ] HTTPS via Traefik/Let's Encrypt
- [ ] DSGVO: no PII to OpenRouter, soft-delete with retention concept