14 KiB
14 KiB
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, ADRsdocs/task_graph.json– Task breakdown with test specsdocs/requirements.md– Full requirements (8 modules, 23 features)docs/component_inventory.md– UI components and states
2. Build & Test Commands
Backend
# 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
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
# 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.pyprovides:async_db– async SQLAlchemy session (rolled back after each test)client– httpx AsyncClient with appauth_headers– JWT headers for admin/verkaeufer/buchhaltung rolesseed_data– minimal seed data (users, vehicle, contact)
- Mocking: OpenRouter API MUST be mocked in all tests (no real API calls)
- Naming:
test_<feature>_<scenario>.pyortest_<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.tsxin__tests__/folder
Test Spec Compliance
Every task in task_graph.json has a test_spec with:
commands– Exact commands to runexpected_results– What success looks liketest_files– Expected test file pathscoverage_target– Minimum coverage percentage
ALL commands in test_spec.commands MUST pass before a task is considered done.
4. Code Conventions
Python (Backend)
# 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)
// 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 Pythonlogging - ❌ No bare
except:– always catch specific exceptions - ❌ No
# type: ignorewithout 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
anytype – use proper TypeScript types - ❌ No
console.login production – use a logger utility - ❌ No inline styles – use Tailwind classes or CSS modules
- ❌ No direct
fetch()without auth wrapper – useapiClient - ❌ No hardcoded API URLs – use
NEXT_PUBLIC_API_URLenv var - ❌ No hardcoded translation strings – use
t('key')from next-intl - ❌ No prop drilling > 2 levels – use context or state management
- ❌ No
useEffectfor data fetching without loading/error states - ❌ No forms without validation – all inputs must have validation
- ❌ No missing
aria-labelon 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)
// 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_getONLY when you need to modify an existing file (need SHA) - Use
files_createfor new files - Use
files_listto check existence before creating - NEVER copy file contents inline in task descriptions – reference by path
- NEVER read entire files just for reference – use
curl + sedfor snippets - For large reference content (architecture.md, requirements.md): reference by path, don't inline
7. Task Execution Workflow
For implementation_engineer
- Read the task from
task_graph.json(task ID: T0X) - Read architecture.md sections relevant to the task (DB schema, API design, ADRs)
- Read requirements.md for the specific feature IDs listed in
requirement_ids - Implement backend first: Model → Schema → Service → Router → Tests
- Implement frontend: Components → Pages → Tests
- Run ALL test_spec.commands and ensure they pass
- Run lint (ruff for backend, eslint for frontend)
- Run build (npm run build for frontend)
- 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 Coolifyfeature/T0X-<short-name>– Feature branch per taskfix/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
- Create feature branch from main
- Implement + test + lint + build
- Commit with conventional commit format
- Push branch
- Create PR with task ID reference
- CI runs: pytest, ruff, vitest, eslint, build
- 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:
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.pyshared client – never call OpenRouter API directly - Always set timeout to 30 seconds
- Always log interaction to
ai_interactionstable (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