491 lines
13 KiB
Markdown
491 lines
13 KiB
Markdown
# AGENTS.md – ERP Nutzfahrzeuge
|
||
|
||
> Build & Test Commands, Test Rules, Conventions für Implementierung
|
||
|
||
---
|
||
|
||
## 1. Build & Test Commands
|
||
|
||
### 1.1 Backend (Python/FastAPI)
|
||
|
||
```bash
|
||
# Dependencies installieren
|
||
cd backend
|
||
pip install -r requirements.txt
|
||
|
||
# DB Migration (Alembic)
|
||
alembic upgrade head
|
||
|
||
# Dev Server starten
|
||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||
|
||
# Tests ausführen (alle)
|
||
pytest --cov=app --cov-report=term-missing -v
|
||
|
||
# Tests für spezifisches Modul
|
||
pytest tests/test_vehicles.py --cov=app/services/vehicle_service --cov=app/routers/vehicles --cov-report=term-missing -v
|
||
|
||
# Type checking
|
||
mypy app/ --ignore-missing-imports
|
||
|
||
# Linting
|
||
ruff check app/
|
||
|
||
# Format check
|
||
black --check app/
|
||
```
|
||
|
||
### 1.2 Frontend (Next.js)
|
||
|
||
```bash
|
||
# Dependencies installieren
|
||
cd frontend
|
||
npm install
|
||
|
||
# Dev Server starten
|
||
npm run dev
|
||
|
||
# Build (production)
|
||
npm run build
|
||
|
||
# Tests ausführen (Unit)
|
||
npx vitest run --coverage
|
||
|
||
# E2E Tests
|
||
npx playwright test
|
||
|
||
# Type checking
|
||
npx tsc --noEmit
|
||
|
||
# Linting
|
||
npm run lint
|
||
```
|
||
|
||
### 1.3 Docker (Full Stack)
|
||
|
||
```bash
|
||
# Alle Container starten
|
||
docker compose up -d
|
||
|
||
# Logs anzeigen
|
||
docker compose logs -f backend
|
||
docker compose logs -f frontend
|
||
|
||
# Rebuild
|
||
docker compose up -d --build
|
||
|
||
# Stop
|
||
docker compose down
|
||
|
||
# Test environment
|
||
docker compose -f docker-compose.test.yml up --abort-on-container-exit
|
||
```
|
||
|
||
### 1.4 Database
|
||
|
||
```bash
|
||
# PostgreSQL Shell
|
||
docker compose exec postgres psql -U erp_user -d erp_db
|
||
|
||
# Backup erstellen
|
||
docker compose exec postgres pg_dump -U erp_user erp_db > backup.sql
|
||
|
||
# Restore
|
||
docker compose exec -T postgres psql -U erp_user -d erp_db < backup.sql
|
||
|
||
# Migration erstellen
|
||
cd backend
|
||
alembic revision --autogenerate -m "description"
|
||
alembic upgrade head
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Test Rules (MANDATORY)
|
||
|
||
### 2.1 TDD – Test Driven Development
|
||
- **Tests werden NICHT modifiziert** – Tests sind die Spec, Code muss sich anpassen
|
||
- **Kein 'done' ohne Test-Evidence** – Build, Test, Smoke-Test müssen durchlaufen
|
||
- **Coverage Target**: ≥ 80% Backend, ≥ 70% Frontend
|
||
- **Test-First**: Tests schreiben → Code implementieren → Tests grün
|
||
- **Kein Skip**: `pytest.skip` oder `it.skip` nur mit Begründung als Kommentar
|
||
|
||
### 2.2 Test-Struktur Backend
|
||
|
||
```
|
||
tests/
|
||
├── conftest.py # Fixtures: test client, DB session, mock OpenRouter
|
||
├── test_auth.py # Auth + JWT + RBAC tests
|
||
├── test_users.py # User CRUD tests
|
||
├── test_health.py # Health endpoint test
|
||
├── test_vehicles.py # Vehicle CRUD + filter + pagination tests
|
||
├── test_mobilede.py # mobile.de push + retry queue tests
|
||
├── test_ocr.py # OCR upload + processing + apply tests
|
||
├── test_contacts.py # Contact CRUD + search + USt-IdNr. validation tests
|
||
├── test_files.py # File upload + download + MIME validation tests
|
||
├── test_sales.py # Sale CRUD + contract PDF + GwG tests
|
||
├── test_datev.py # DATEV export + CSV format tests
|
||
├── test_copilot.py # Copilot chat + action + history tests
|
||
└── test_retouch.py # Retouch + price comparison tests
|
||
```
|
||
|
||
### 2.3 Test-Struktur Frontend
|
||
|
||
```
|
||
tests/
|
||
├── auth.test.tsx # Login form, auth context, token refresh
|
||
├── i18n.test.tsx # Translation loading, language switch
|
||
├── vehicles.test.tsx # Vehicle list, form, detail, mobile.de status
|
||
├── ocr.test.tsx # OCR upload, results, apply
|
||
├── contacts.test.tsx # Contact list, form, USt-IdNr. validation
|
||
├── files.test.tsx # File upload, list, gallery
|
||
├── sales.test.tsx # Sale list, form, contract preview
|
||
├── datev.test.tsx # DATEV export, download
|
||
├── copilot.test.tsx # Chat interface, voice input, action preview
|
||
└── retouch.test.tsx # Retouch upload, before/after, price comparison
|
||
```
|
||
|
||
### 2.4 Test-Fixtures (conftest.py)
|
||
|
||
```python
|
||
# Pflicht-Fixtures in conftest.py:
|
||
# - test_client: httpx.AsyncClient mit Test-App
|
||
# - db_session: async SQLAlchemy Session mit rollback
|
||
# - mock_openrouter: Mock für OpenRouter API (OCR, Copilot, Retouch)
|
||
# - mock_mobilede: Mock für mobile.de Seller API
|
||
# - auth_headers: JWT headers für admin/verkaeufer/buchhaltung roles
|
||
# - test_vehicle: Pre-created vehicle for tests
|
||
# - test_contact: Pre-created contact for tests
|
||
```
|
||
|
||
### 2.5 Mocking-Regeln
|
||
- **OpenRouter API**: IMMER mocken in Tests (kein realer API-Call)
|
||
- **mobile.de API**: IMMER mocken in Tests
|
||
- **BZSt API**: IMMER mocken (Feature-Flag disabled in tests)
|
||
- **Redis**: Test mit fakeredis oder in-memory mock
|
||
- **PostgreSQL**: Test-DB mit SQLite (async) oder testcontainers
|
||
- **File Storage**: Temp-Verzeichnis (tmp_path fixture)
|
||
|
||
---
|
||
|
||
## 3. Code-Konventionen
|
||
|
||
### 3.1 Backend
|
||
|
||
#### Python Style
|
||
- **Formatter**: black (line-length=100)
|
||
- **Linter**: ruff
|
||
- **Type Checker**: mypy (strict für services)
|
||
- **Import Order**: stdlib → third-party → local (isort)
|
||
|
||
#### FastAPI Patterns
|
||
```python
|
||
# Router-Struktur (Pflicht):
|
||
router = APIRouter(prefix="/api/vehicles", tags=["vehicles"])
|
||
|
||
# Async für alle DB-Operationen
|
||
async def get_vehicle(vehicle_id: UUID, db: AsyncSession) -> Vehicle:
|
||
...
|
||
|
||
# Pydantic Schema für Request/Response
|
||
class VehicleCreate(BaseModel):
|
||
brand: str = Field(..., min_length=1, max_length=100)
|
||
model: str = Field(..., min_length=1, max_length=150)
|
||
...
|
||
|
||
# Dependency Injection für Auth
|
||
async def get_current_user(
|
||
token: str = Depends(oauth2_scheme),
|
||
db: AsyncSession = Depends(get_db)
|
||
) -> User:
|
||
...
|
||
|
||
# RBAC Dependency
|
||
def require_role(*roles: str):
|
||
async def role_checker(user: User = Depends(get_current_user)) -> User:
|
||
if user.role not in roles:
|
||
raise HTTPException(status_code=403, detail="Forbidden")
|
||
return user
|
||
return role_checker
|
||
```
|
||
|
||
#### Error-Format (Pflicht)
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "VEHICLE_NOT_FOUND",
|
||
"message": "Vehicle with ID abc123 not found",
|
||
"details": {}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### SQLAlchemy Model Pattern
|
||
```python
|
||
class Vehicle(Base):
|
||
__tablename__ = "vehicles"
|
||
|
||
id: Mapped[UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||
brand: Mapped[str] = mapped_column(String(100), nullable=False)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
TIMESTAMPTZ, server_default=func.now()
|
||
)
|
||
```
|
||
|
||
### 3.2 Frontend
|
||
|
||
#### TypeScript Style
|
||
- **Strict Mode**: `"strict": true` in tsconfig.json
|
||
- **No any**: `"noImplicitAny": true`
|
||
- **Import**: absolute imports via `@/` prefix
|
||
|
||
#### Next.js Patterns
|
||
```tsx
|
||
// Server Component (default)
|
||
export default async function VehicleListPage() {
|
||
const vehicles = await fetch(`${API_URL}/api/vehicles`, { cache: 'no-store' });
|
||
...
|
||
}
|
||
|
||
// Client Component (use 'use client' directive)
|
||
'use client'
|
||
export function VehicleForm() {
|
||
const [brand, setBrand] = useState('');
|
||
...
|
||
}
|
||
|
||
// API Client (zentral in lib/api.ts)
|
||
export async function apiFetch<T>(path: string, options?: RequestInit): Promise<T> {
|
||
const token = getAuthToken();
|
||
const res = await fetch(`${API_URL}${path}`, {
|
||
...options,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||
...options?.headers,
|
||
},
|
||
});
|
||
if (!res.ok) throw new ApiError(res.status, await res.json());
|
||
return res.json();
|
||
}
|
||
```
|
||
|
||
#### Tailwind CSS
|
||
- **Kein CSS-in-JS** (keine styled-components, emotion)
|
||
- **Utility-First**: Tailwind-Klassen direkt in JSX
|
||
- **Responsive**: mobile-first (`sm:`, `md:`, `lg:`)
|
||
- **Dark Mode**: `dark:` prefix (geplant für v2)
|
||
|
||
### 3.3 i18n
|
||
|
||
#### Translation File Format (JSON)
|
||
```json
|
||
{
|
||
"vehicles": {
|
||
"title": "Fahrzeugbestand",
|
||
"create": "Neues Fahrzeug",
|
||
"brand": "Marke",
|
||
"model": "Modell",
|
||
"status": {
|
||
"in_stock": "Auf Lager",
|
||
"reserved": "Reserviert",
|
||
"sold": "Verkauft"
|
||
}
|
||
},
|
||
"common": {
|
||
"save": "Speichern",
|
||
"cancel": "Abbrechen",
|
||
"delete": "Löschen",
|
||
"confirm": "Bestätigen"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### i18n Usage in Components
|
||
```tsx
|
||
import { useTranslations } from 'next-intl';
|
||
|
||
function VehicleList() {
|
||
const t = useTranslations('vehicles');
|
||
return <h1>{t('title')}</h1>;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Git-Konventionen
|
||
|
||
### 4.1 Commit Messages
|
||
``nfeat: add vehicle CRUD with mobile.de push
|
||
fix: correct OCR confidence threshold logic
|
||
docs: update architecture ADR-004
|
||
test: add contact USt-IdNr. validation tests
|
||
refactor: extract OpenRouter client to utils
|
||
```
|
||
|
||
### 4.2 Branch Naming
|
||
```
|
||
feature/T01-auth-foundation
|
||
feature/T02-vehicle-module
|
||
fix/ocr-confidence-threshold
|
||
hotfix/datev-csv-format
|
||
```
|
||
|
||
### 4.3 PR Rules
|
||
- PRs erforderlich für `main` Branch
|
||
- Mindestens 1 Reviewer
|
||
- Alle CI Checks müssen grün sein
|
||
- Coverage darf nicht sinken
|
||
|
||
---
|
||
|
||
## 5. Environment Variables
|
||
|
||
### 5.1 Backend (.env)
|
||
```
|
||
DATABASE_URL=postgresql+asyncpg://erp_user:erp_pass@postgres:5432/erp_db
|
||
REDIS_URL=redis://redis:6379/0
|
||
JWT_SECRET=<secure-random-string>
|
||
JWT_ACCESS_EXPIRE_MINUTES=15
|
||
JWT_REFRESH_EXPIRE_DAYS=7
|
||
OPENROUTER_API_KEY=<key>
|
||
MOBILEDE_SELLER_API_KEY=<key>
|
||
MOBILEDE_SELLER_API_URL=https://api.mobile.de/seller/v1
|
||
BZST_API_ENABLED=false
|
||
UPLOAD_DIR=/data/uploads
|
||
MAX_FILE_SIZE_MB=20
|
||
CORS_ORIGINS=https://erp.media-on.de
|
||
```
|
||
|
||
### 5.2 Frontend (.env.local)
|
||
```
|
||
NEXT_PUBLIC_API_URL=https://erp.media-on.de/api
|
||
NEXT_PUBLIC_DEFAULT_LOCALE=de
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Task-Ausführungs-Regeln
|
||
|
||
### 6.1 Task-Reihenfolge (Dependency-basiert)
|
||
```
|
||
T01 (Auth+i18n) → T02 (Vehicle) → T04 (Contacts) → T03 (OCR) → T05 (Files) → T06 (Sales) → T07 (Copilot) → T08 (Retouch)
|
||
```
|
||
|
||
### 6.2 Pro Task
|
||
1. Lese task_graph.json für Task-Details
|
||
2. Implementiere alle `files_to_create` für den Task
|
||
3. Schreibe Tests zuerst (TDD)
|
||
4. Führe `test_spec.commands` aus
|
||
5. Verifiziere `acceptance_criteria`
|
||
6. Coverage >= `coverage_target` muss erreicht sein
|
||
7. Keine Dateien aus anderen Tasks erstellen
|
||
|
||
### 6.3 Verboten
|
||
- **Keine Micro-Tasks**: Ein Task = ein komplettes Modul
|
||
- **Keine Tests modifizieren**: Tests sind die Spec
|
||
- **Kein 'done' ohne Evidence**: Test-Output als Beweis
|
||
- **Keine Secrets im Code**: Nur Environment Variables
|
||
- **Keine Hardcoded URLs**: Config via BaseSettings
|
||
- **Kein sync Code für DB**: async/await Pflicht
|
||
- **Kein `any` in TypeScript**: strict mode
|
||
|
||
---
|
||
|
||
## 7. Quality Gates
|
||
|
||
| Gate | Kriterium | Tool |
|
||
|---|---|---|
|
||
| Lint | ruff check, eslint | ruff, eslint |
|
||
| Format | black --check, prettier --check | black, prettier |
|
||
| Types | mypy, tsc --noEmit | mypy, tsc |
|
||
| Tests | pytest, vitest | pytest, vitest |
|
||
| Coverage | >= 80% backend, >= 70% frontend | pytest-cov, vitest coverage |
|
||
| Build | docker compose build | docker |
|
||
| Security | pip-audit, npm audit | pip-audit, npm audit |
|
||
|
||
---
|
||
|
||
## 8. Deployment (Coolify)
|
||
|
||
### 8.1 docker-compose.yml (Produktion)
|
||
```yaml
|
||
version: '3.8'
|
||
services:
|
||
frontend:
|
||
build: ./frontend
|
||
ports:
|
||
- "3000:3000"
|
||
environment:
|
||
- NEXT_PUBLIC_API_URL=https://erp.media-on.de/api
|
||
depends_on:
|
||
- backend
|
||
restart: unless-stopped
|
||
|
||
backend:
|
||
build: ./backend
|
||
ports:
|
||
- "8000:8000"
|
||
environment:
|
||
- DATABASE_URL=postgresql+asyncpg://erp_user:${POSTGRES_PASSWORD}@postgres:5432/erp_db
|
||
- REDIS_URL=redis://redis:6379/0
|
||
- JWT_SECRET=${JWT_SECRET}
|
||
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||
depends_on:
|
||
- postgres
|
||
- redis
|
||
restart: unless-stopped
|
||
|
||
postgres:
|
||
image: postgres:16-alpine
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
environment:
|
||
- POSTGRES_USER=erp_user
|
||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
- POSTGRES_DB=erp_db
|
||
restart: unless-stopped
|
||
|
||
redis:
|
||
image: redis:7-alpine
|
||
volumes:
|
||
- redis_data:/data
|
||
restart: unless-stopped
|
||
|
||
volumes:
|
||
postgres_data:
|
||
redis_data:
|
||
uploads_data:
|
||
```
|
||
|
||
### 8.2 Health Endpoints
|
||
- Backend: `GET /api/health` → `{"status": "ok"}`
|
||
- Frontend: `GET /` → HTTP 200
|
||
|
||
### 8.3 Backup
|
||
- PostgreSQL: Daily dump via Coolify cron
|
||
- Uploads: Volume backup
|
||
- Redis: Optional (cache can be rebuilt)
|
||
|
||
---
|
||
|
||
## 9. Open Questions / TODOs
|
||
|
||
| Frage | Status | Verantwortlich |
|
||
|---|---|---|
|
||
| BZSt API Zugang beantragen | Offen (Feature-Flag disabled) | Admin |
|
||
| mobile.de Seller API Credentials | Benötigt (in Coolify env vars) | Admin |
|
||
| OpenRouter API Key | Benötigt (in Coolify env vars) | Admin |
|
||
| Domain erp.media-on.de DNS | Zu konfigurieren | DevOps |
|
||
| DATEV Berater-Nummer | Zu klären mit Buchhaltung | Buchhaltung |
|
||
| Contract PDF Template | Zu definieren (Rechtstexte) | Admin + Rechtsanwalt |
|
||
|
||
---
|
||
|
||
## 10. Handoff Summary
|
||
|
||
- **architecture.md**: ✅ Complete (Stack, Module, Data Model, API, ADRs, Test Strategy)
|
||
- **task_graph.json**: ✅ Complete (8 Tasks, 115 Acceptance Criteria, alle mit test_spec)
|
||
- **AGENTS.md**: ✅ Complete (Build Commands, Test Rules, Conventions)
|
||
- **Ready for Implementation**: ✅ Yes (nach Plan Mode Transition zu implementation_allowed)
|