# 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(path: string, options?: RequestInit): Promise { 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

{t('title')}

; } ``` --- ## 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)