13 KiB
AGENTS.md – ERP Nutzfahrzeuge
Build & Test Commands, Test Rules, Conventions für Implementierung
1. Build & Test Commands
1.1 Backend (Python/FastAPI)
# 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)
# 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)
# 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
# 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.skipoderit.skipnur 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)
# 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
# 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)
{
"error": {
"code": "VEHICLE_NOT_FOUND",
"message": "Vehicle with ID abc123 not found",
"details": {}
}
}
SQLAlchemy Model Pattern
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": truein tsconfig.json - No any:
"noImplicitAny": true - Import: absolute imports via
@/prefix
Next.js Patterns
// 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)
{
"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
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= JWT_ACCESS_EXPIRE_MINUTES=15 JWT_REFRESH_EXPIRE_DAYS=7 OPENROUTER_API_KEY= MOBILEDE_SELLER_API_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)