Files

13 KiB
Raw Permalink Blame History

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.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)

# 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": true in 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)