513 lines
30 KiB
JSON
513 lines
30 KiB
JSON
{
|
|
"project": "erp-nutzfahrzeuge",
|
|
"version": "1.0",
|
|
"created": "2026-07-12",
|
|
"status": "approved_for_implementation",
|
|
"total_tasks": 8,
|
|
"tasks": [
|
|
{
|
|
"id": "T01",
|
|
"title": "Auth + User Management + RBAC + Base Frontend Layout + i18n Setup",
|
|
"category": "foundation",
|
|
"description": "Komplettes Auth-System: JWT login/refresh, User CRUD (admin only), RBAC Middleware (admin/verkaeufer/buchhaltung). Backend: User Model, auth_service, auth router, users router, JWT utils, RBAC dependency. Frontend: Root layout, Login page, Auth context/hooks, i18n setup (next-intl oder react-i18next), de.json + en.json Basistranslationen, API client (fetch wrapper mit Auth-Header), Base UI components (Button, Input, Card, Table, Modal, Toast). Health endpoint /api/health. Config via Pydantic BaseSettings (DB URL, Redis URL, JWT Secret, OpenRouter Key, mobile.de credentials).",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": [],
|
|
"requirement_ids": ["M6-F1", "M6-F2", "AUTH-1", "AUTH-2", "AUTH-3", "AUTH-4", "I18N-1", "I18N-2"],
|
|
"estimated_effort": "L",
|
|
"estimated_lines": 600,
|
|
"acceptance_criteria": [
|
|
"POST /api/auth/login mit valid credentials → 200 + JWT access_token + refresh_token",
|
|
"POST /api/auth/login mit invalid credentials → 401",
|
|
"POST /api/auth/refresh mit valid refresh_token → 200 + new access_token",
|
|
"GET /api/auth/me mit valid JWT → 200 + user object (email, full_name, role)",
|
|
"GET /api/users ohne admin role → 403",
|
|
"GET /api/users mit admin role → 200 + paginated user list",
|
|
"POST /api/users mit valid data → 201 + user object",
|
|
"DELETE /api/users/:id → 200 + user.is_active=false",
|
|
"GET /api/health → 200 + {status: 'ok'}",
|
|
"Frontend Login page rendert und sendet POST /api/auth/login",
|
|
"Frontend zeigt Toast bei login error",
|
|
"i18n: de.json und en.json existieren mit mindestens 20 keys",
|
|
"Sprachumschaltung DE/EN wechselt UI-Texte live",
|
|
"RBAC: /api/users blocked für verkaeufer role → 403",
|
|
"pytest coverage >= 80% für auth module"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_auth.py tests/test_users.py --cov=app/services/auth_service --cov=app/routers/auth --cov=app/routers/users --cov-report=term-missing -v",
|
|
"cd backend && python -m pytest tests/test_health.py -v",
|
|
"cd frontend && npx vitest run tests/auth.test.tsx tests/i18n.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All auth tests pass (login, refresh, RBAC, user CRUD). Health endpoint returns 200. Frontend login form renders and submits. i18n loads DE/EN translations. Coverage >= 80% backend auth, >= 70% frontend auth.",
|
|
"test_files": [
|
|
"backend/tests/test_auth.py",
|
|
"backend/tests/test_users.py",
|
|
"backend/tests/test_health.py",
|
|
"backend/tests/conftest.py",
|
|
"frontend/tests/auth.test.tsx",
|
|
"frontend/tests/i18n.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/main.py",
|
|
"backend/app/config.py",
|
|
"backend/app/database.py",
|
|
"backend/app/dependencies.py",
|
|
"backend/app/models/user.py",
|
|
"backend/app/schemas/user.py",
|
|
"backend/app/routers/auth.py",
|
|
"backend/app/routers/users.py",
|
|
"backend/app/services/auth_service.py",
|
|
"backend/app/utils/jwt.py",
|
|
"backend/app/utils/i18n.py",
|
|
"backend/tests/test_auth.py",
|
|
"backend/tests/test_users.py",
|
|
"backend/tests/test_health.py",
|
|
"backend/tests/conftest.py",
|
|
"frontend/app/layout.tsx",
|
|
"frontend/app/(auth)/login/page.tsx",
|
|
"frontend/lib/api.ts",
|
|
"frontend/lib/auth.ts",
|
|
"frontend/lib/i18n.ts",
|
|
"frontend/messages/de.json",
|
|
"frontend/messages/en.json",
|
|
"frontend/components/ui/Button.tsx",
|
|
"frontend/components/ui/Input.tsx",
|
|
"frontend/components/ui/Card.tsx",
|
|
"frontend/components/ui/Table.tsx",
|
|
"frontend/components/ui/Modal.tsx",
|
|
"frontend/components/ui/Toast.tsx",
|
|
"frontend/tests/auth.test.tsx",
|
|
"frontend/tests/i18n.test.tsx"
|
|
]
|
|
},
|
|
{
|
|
"id": "T02",
|
|
"title": "Vehicle Management + mobile.de Push + Vehicle UI",
|
|
"category": "vehicle",
|
|
"description": "Komplettes Fahrzeug-Modul: Vehicle Model (LKW, Baumaschine, PKW, Stapler, Transporter), vehicle_service mit CRUD + filter/sort/paginate, vehicles router mit allen Endpoints. mobile.de Seller API Integration (Push only): mobilede_service, mobilede_push background task, Ad-Format Mapping, Retry-Queue via Redis. Frontend: Vehicle List page mit Filter (type, status, brand) + Pagination + Sort, Vehicle Detail page, Vehicle Create/Edit form, mobile.de Push Button + Status indicator. Soft-Delete Implementierung (status='deleted').",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": ["T01"],
|
|
"requirement_ids": ["M1-F1", "M1-F2", "M1-F3", "M1-F4", "M1-F5", "M1-F6", "M1-F7"],
|
|
"estimated_effort": "L",
|
|
"estimated_lines": 700,
|
|
"acceptance_criteria": [
|
|
"GET /api/vehicles → 200 + paginated list (page, per_page, total)",
|
|
"GET /api/vehicles?type=lkw&status=in_stock → 200 + filtered list",
|
|
"GET /api/vehicles?sort=-created_at → 200 + sorted list",
|
|
"GET /api/vehicles/:id → 200 + vehicle detail object",
|
|
"GET /api/vehicles/:nonexistent → 404",
|
|
"POST /api/vehicles mit valid data → 201 + vehicle object",
|
|
"POST /api/vehicles mit missing brand → 422 validation error",
|
|
"PUT /api/vehicles/:id → 200 + updated vehicle",
|
|
"DELETE /api/vehicles/:id → 200 + status='deleted' (soft delete)",
|
|
"POST /api/vehicles/:id/mobilede-push → 202 (async push queued)",
|
|
"GET /api/vehicles/:id/mobilede-status → 200 + {synced: bool, ad_id, synced_at}",
|
|
"mobile.de Push sendet korrektes Ad-Format an Seller API",
|
|
"mobile.de Push Fehler → Retry-Queue (Redis), max 3 retries",
|
|
"Frontend Vehicle List zeigt Tabelle mit Filter + Pagination",
|
|
"Frontend Vehicle Detail zeigt alle Felder + mobile.de Status",
|
|
"Frontend Vehicle Create Form validiert Pflichtfelder",
|
|
"Frontend mobile.de Push Button → zeigt Sync-Status",
|
|
"pytest coverage >= 80% für vehicle module"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_vehicles.py tests/test_mobilede.py --cov=app/services/vehicle_service --cov=app/services/mobilede_service --cov=app/routers/vehicles --cov-report=term-missing -v",
|
|
"cd frontend && npx vitest run tests/vehicles.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All vehicle CRUD tests pass. Filter, sort, pagination work. mobile.de push queues async task. Soft delete sets status=deleted. Frontend list renders with filters. Coverage >= 80% backend, >= 70% frontend.",
|
|
"test_files": [
|
|
"backend/tests/test_vehicles.py",
|
|
"backend/tests/test_mobilede.py",
|
|
"frontend/tests/vehicles.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/models/vehicle.py",
|
|
"backend/app/schemas/vehicle.py",
|
|
"backend/app/routers/vehicles.py",
|
|
"backend/app/services/vehicle_service.py",
|
|
"backend/app/services/mobilede_service.py",
|
|
"backend/app/tasks/mobilede_push.py",
|
|
"backend/app/utils/mobilede_mapping.py",
|
|
"backend/tests/test_vehicles.py",
|
|
"backend/tests/test_mobilede.py",
|
|
"frontend/app/vehicles/page.tsx",
|
|
"frontend/app/vehicles/[id]/page.tsx",
|
|
"frontend/app/vehicles/new/page.tsx",
|
|
"frontend/components/vehicles/VehicleList.tsx",
|
|
"frontend/components/vehicles/VehicleForm.tsx",
|
|
"frontend/components/vehicles/VehicleDetail.tsx",
|
|
"frontend/components/vehicles/MobileDeStatus.tsx",
|
|
"frontend/tests/vehicles.test.tsx"
|
|
]
|
|
},
|
|
{
|
|
"id": "T03",
|
|
"title": "OCR-Erfassung (ZB I/II) via OpenRouter Vision + OCR UI",
|
|
"category": "ocr",
|
|
"status": "completed",
|
|
"description": "Komplettes OCR-Modul: OCRResult Model, ocr_service mit OpenRouter Qwen2.5-VL Integration, OCR router (upload, get results, apply to vehicle). Async OCR processing via Redis Queue. Prompt-Engineering für ZB I/II: structured JSON output (brand, model, vin, first_registration, mileage, power_kw, fuel_type). Confidence-Score Berechnung. Manual Review bei confidence < 0.7. Response-Caching für identische Scans. Frontend: OCR Upload page (drag-and-drop), OCR Results view mit side-by-side Original Scan + Extracted Data, Apply-to-Vehicle Button.",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": ["T01", "T02"],
|
|
"requirement_ids": ["M2-F1", "M2-F2", "M2-F3", "M2-F4", "M2-F5"],
|
|
"estimated_effort": "L",
|
|
"estimated_lines": 600,
|
|
"acceptance_criteria": [
|
|
"POST /api/ocr/upload mit image file → 202 + ocr_result_id (async processing)",
|
|
"POST /api/ocr/upload ohne file → 422",
|
|
"POST /api/ocr/upload mit invalid MIME type → 422",
|
|
"GET /api/ocr/results/:id → 200 + {status, raw_text, structured_data, confidence_score}",
|
|
"GET /api/ocr/results?vehicle_id=X → 200 + list of OCR results for vehicle",
|
|
"POST /api/ocr/results/:id/apply → 200 + vehicle updated with OCR data",
|
|
"OCR processing mit mocked OpenRouter → structured_data enthält brand, model, vin",
|
|
"Confidence score < 0.7 → status='pending' (manual review needed)",
|
|
"Confidence score >= 0.7 → status='completed'",
|
|
"OCR Fehler (OpenRouter unavailable) → status='failed' + error message",
|
|
"Frontend OCR Upload akzeptiert Drag-and-Drop",
|
|
"Frontend OCR Results zeigt Original + Extracted Data side-by-side",
|
|
"Frontend Apply Button → sendet POST /api/ocr/results/:id/apply",
|
|
"pytest coverage >= 80% für ocr module"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_ocr.py --cov=app/services/ocr_service --cov=app/routers/ocr --cov=app/utils/openrouter --cov-report=term-missing -v",
|
|
"cd frontend && npx vitest run tests/ocr.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All OCR tests pass with mocked OpenRouter. Upload queues async task. Results retrievable. Apply updates vehicle. Confidence threshold works. Frontend upload + results render. Coverage >= 80% backend, >= 70% frontend.",
|
|
"test_files": [
|
|
"backend/tests/test_ocr.py",
|
|
"frontend/tests/ocr.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/models/ocr_result.py",
|
|
"backend/app/schemas/ocr.py",
|
|
"backend/app/routers/ocr.py",
|
|
"backend/app/services/ocr_service.py",
|
|
"backend/app/tasks/ocr_processing.py",
|
|
"backend/app/utils/openrouter.py",
|
|
"backend/tests/test_ocr.py",
|
|
"frontend/app/ocr/page.tsx",
|
|
"frontend/components/ocr/OCRUpload.tsx",
|
|
"frontend/components/ocr/OCRResults.tsx",
|
|
"frontend/components/ocr/OCRDetail.tsx",
|
|
"frontend/tests/ocr.test.tsx"
|
|
]
|
|
},
|
|
{
|
|
"id": "T04",
|
|
"title": "Kontakt-/Kundenverwaltung + Contact UI",
|
|
"category": "contact",
|
|
"description": "Komplettes Kontakt-Modul: Contact Model (Kunde/Lieferant/Beide, EU/Inland, USt-IdNr.), contact_service mit CRUD + search + filter (type, eu/inland), contacts router. USt-IdNr.-Feld mit Format-Validierung (DE + EU Formate). Contact-Search (name, company, city, email). Frontend: Contact List mit Search + Filter, Contact Detail page, Contact Create/Edit form. EU/Inland Toggle. USt-IdNr. input mit Format-Validierung frontend-seitig.",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": ["T01"],
|
|
"requirement_ids": ["M3-F1", "M3-F2", "M3-F3", "M3-F4", "M3-F5", "M3-F6"],
|
|
"estimated_effort": "M",
|
|
"estimated_lines": 500,
|
|
"acceptance_criteria": [
|
|
"GET /api/contacts → 200 + paginated list",
|
|
"GET /api/contacts?type=kunde → 200 + filtered list",
|
|
"GET /api/contacts?is_eu=true → 200 + EU contacts only",
|
|
"GET /api/contacts?search=mueller → 200 + matching contacts",
|
|
"GET /api/contacts/:id → 200 + contact detail",
|
|
"GET /api/contacts/:nonexistent → 404",
|
|
"POST /api/contacts mit valid data → 201 + contact object",
|
|
"POST /api/contacts mit invalid ust_id_nr format → 422",
|
|
"PUT /api/contacts/:id → 200 + updated contact",
|
|
"DELETE /api/contacts/:id → 200 (or 404 if not found)",
|
|
"Contact mit contact_type='beide' erscheint in kunde und lieferant filter",
|
|
"Frontend Contact List zeigt Tabelle mit Search + Filter",
|
|
"Frontend Contact Form validiert USt-IdNr. Format",
|
|
"Frontend EU/Inland Toggle ändert Pflichtfelder",
|
|
"pytest coverage >= 80% für contact module"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_contacts.py --cov=app/services/contact_service --cov=app/routers/contacts --cov-report=term-missing -v",
|
|
"cd frontend && npx vitest run tests/contacts.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All contact CRUD tests pass. Search, filter, pagination work. USt-IdNr. validation rejects invalid formats. Frontend list renders with search/filter. Coverage >= 80% backend, >= 70% frontend.",
|
|
"test_files": [
|
|
"backend/tests/test_contacts.py",
|
|
"frontend/tests/contacts.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/models/contact.py",
|
|
"backend/app/schemas/contact.py",
|
|
"backend/app/routers/contacts.py",
|
|
"backend/app/services/contact_service.py",
|
|
"backend/app/utils/ust_validation.py",
|
|
"backend/tests/test_contacts.py",
|
|
"frontend/app/contacts/page.tsx",
|
|
"frontend/app/contacts/[id]/page.tsx",
|
|
"frontend/app/contacts/new/page.tsx",
|
|
"frontend/components/contacts/ContactList.tsx",
|
|
"frontend/components/contacts/ContactForm.tsx",
|
|
"frontend/components/contacts/ContactDetail.tsx",
|
|
"frontend/tests/contacts.test.tsx"
|
|
]
|
|
},
|
|
{
|
|
"id": "T05",
|
|
"title": "Dateiablage pro Fahrzeug + File UI",
|
|
"category": "file",
|
|
"description": "Komplettes Dateiablage-Modul: File Model, file_service mit Upload/Download/Delete, files router. MIME-Type-Validierung (images: jpg/png/webp, documents: pdf/doc/docx, scans: jpg/png). Max 20MB pro Datei. Dateien gespeichert im uploads_data Volume. Thumbnail-Generierung für Images. Frontend: File Upload Component (Drag-and-Drop, multi-file), File List mit Thumbnails, File Download, File Delete mit Confirm. Gallery view für Fahrzeug-Bilder.",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": ["T01", "T02"],
|
|
"requirement_ids": ["M4-F1", "M4-F2", "M4-F3", "M4-F4", "M4-F5"],
|
|
"estimated_effort": "M",
|
|
"estimated_lines": 500,
|
|
"acceptance_criteria": [
|
|
"GET /api/vehicles/:id/files → 200 + list of files",
|
|
"POST /api/vehicles/:id/files mit multipart image → 201 + file object",
|
|
"POST /api/vehicles/:id/files mit >20MB file → 413",
|
|
"POST /api/vehicles/:id/files mit invalid MIME type → 422",
|
|
"GET /api/vehicles/:id/files/:fileId → 200 + file content (download)",
|
|
"DELETE /api/vehicles/:id/files/:fileId → 200 + file deleted",
|
|
"File gespeichert in uploads_data volume, path in DB gespeichert",
|
|
"Image files → Thumbnail generiert (200x200)",
|
|
"Frontend File Upload akzeptiert Drag-and-Drop multi-file",
|
|
"Frontend File List zeigt Thumbnails für images",
|
|
"Frontend File Delete zeigt Confirm Dialog",
|
|
"Frontend Gallery view für Fahrzeug-Bilder",
|
|
"pytest coverage >= 80% für file module"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_files.py --cov=app/services/file_service --cov=app/routers/files --cov-report=term-missing -v",
|
|
"cd frontend && npx vitest run tests/files.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All file CRUD tests pass. MIME validation rejects invalid types. Size limit enforced. Download returns file content. Thumbnails generated for images. Frontend upload + list + gallery render. Coverage >= 80% backend, >= 70% frontend.",
|
|
"test_files": [
|
|
"backend/tests/test_files.py",
|
|
"frontend/tests/files.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/models/file.py",
|
|
"backend/app/schemas/file.py",
|
|
"backend/app/routers/files.py",
|
|
"backend/app/services/file_service.py",
|
|
"backend/app/utils/thumbnails.py",
|
|
"backend/tests/test_files.py",
|
|
"frontend/components/files/FileUpload.tsx",
|
|
"frontend/components/files/FileList.tsx",
|
|
"frontend/components/files/FileGallery.tsx",
|
|
"frontend/components/files/FilePreview.tsx",
|
|
"frontend/tests/files.test.tsx"
|
|
]
|
|
},
|
|
{
|
|
"id": "T06",
|
|
"title": "Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI",
|
|
"category": "sales",
|
|
"description": "Komplettes Verkaufsmodul: Sale Model, sale_service mit CRUD, sales router. Contract PDF Generierung (Verkaufvertrag mit Vehicle + Contact Daten, GwG-Klausel, USt-IdNr. Feld). DATEV-Export: datev_service, DATEV CSV Format (Buchungsstapel: Datum, Konto, Gegenkonto, Betrag, Belegfeld, Buchungstext). GwG-Logik: Sofortabzug bei <= 800EUR (§6 Abs. 2 EStG). USt-IdNr.-Prüfung: BZSt API endpoint (Feature-Flag bzst_api_enabled, aktuell disabled → manual review). DATEVExport Model + router. Frontend: Sales List, Sale Detail mit Contract Preview, Sale Create form (Vehicle select, Contact select, Price, GwG toggle), DATEV Export page (Zeitraum wählen, Download CSV).",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": ["T02", "T04"],
|
|
"requirement_ids": ["M5-F1", "M5-F2", "M5-F3", "M5-F4", "M5-F5", "M5-F6", "M5-F7", "M5-F8", "M5-F9"],
|
|
"estimated_effort": "XL",
|
|
"estimated_lines": 800,
|
|
"acceptance_criteria": [
|
|
"GET /api/sales → 200 + paginated list with filter (date range, status)",
|
|
"GET /api/sales/:id → 200 + sale detail with vehicle + contact nested",
|
|
"POST /api/sales mit valid data → 201 + sale object + contract_pdf_path",
|
|
"POST /api/sales ohne vehicle_id → 422",
|
|
"POST /api/sales ohne buyer_contact_id → 422",
|
|
"PUT /api/sales/:id → 200 + updated sale",
|
|
"DELETE /api/sales/:id → 200 (sale cancelled, vehicle status back to in_stock)",
|
|
"POST /api/sales/:id/contract → 200 + regenerated contract PDF path",
|
|
"GET /api/sales/:id/contract → 200 + PDF content (application/pdf)",
|
|
"Sale mit is_gwg=true und sale_price <= 800 → GwG-Klausel in contract",
|
|
"POST /api/sales/:id/verify-ust-id → 200 + {verified: false, message: 'BZSt API not available'} (feature flag disabled)",
|
|
"POST /api/datev/export mit date range → 201 + DATEVExport object",
|
|
"POST /api/datev/export mit invalid date range → 422",
|
|
"GET /api/datev/exports → 200 + list of exports",
|
|
"GET /api/datev/exports/:id/download → 200 + CSV content (text/csv)",
|
|
"DATEV CSV enthält korrekte Felder: Datum, Konto, Gegenkonto, Betrag, Belegfeld, Buchungstext",
|
|
"Sale creation → Vehicle status='sold'",
|
|
"Sale deletion → Vehicle status='in_stock'",
|
|
"Frontend Sales List zeigt Tabelle mit Date Filter",
|
|
"Frontend Sale Detail zeigt Contract Preview (PDF embed)",
|
|
"Frontend Sale Create Form mit Vehicle + Contact Selectors",
|
|
"Frontend DATEV Export page mit Date Range Picker + Download",
|
|
"pytest coverage >= 80% für sales + datev module"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_sales.py tests/test_datev.py --cov=app/services/sale_service --cov=app/services/datev_service --cov=app/routers/sales --cov=app/routers/datev --cov-report=term-missing -v",
|
|
"cd frontend && npx vitest run tests/sales.test.tsx tests/datev.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All sales CRUD tests pass. Contract PDF generated. DATEV CSV export with correct format. GwG logic applied. Vehicle status changes on sale create/delete. USt-IdNr. endpoint returns feature-flag message. Frontend list + detail + form + DATEV export render. Coverage >= 80% backend, >= 70% frontend.",
|
|
"test_files": [
|
|
"backend/tests/test_sales.py",
|
|
"backend/tests/test_datev.py",
|
|
"frontend/tests/sales.test.tsx",
|
|
"frontend/tests/datev.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/models/sale.py",
|
|
"backend/app/models/datev_export.py",
|
|
"backend/app/schemas/sale.py",
|
|
"backend/app/schemas/datev.py",
|
|
"backend/app/routers/sales.py",
|
|
"backend/app/routers/datev.py",
|
|
"backend/app/services/sale_service.py",
|
|
"backend/app/services/datev_service.py",
|
|
"backend/app/utils/contract_pdf.py",
|
|
"backend/app/utils/datev.py",
|
|
"backend/tests/test_sales.py",
|
|
"backend/tests/test_datev.py",
|
|
"frontend/app/sales/page.tsx",
|
|
"frontend/app/sales/[id]/page.tsx",
|
|
"frontend/app/sales/new/page.tsx",
|
|
"frontend/components/sales/SaleList.tsx",
|
|
"frontend/components/sales/SaleForm.tsx",
|
|
"frontend/components/sales/ContractPreview.tsx",
|
|
"frontend/components/sales/DatevExport.tsx",
|
|
"frontend/tests/sales.test.tsx",
|
|
"frontend/tests/datev.test.tsx"
|
|
]
|
|
},
|
|
{
|
|
"id": "T07",
|
|
"title": "KI-Copilot (Text + Sprache) + Systemsteuerung + Copilot UI",
|
|
"category": "copilot",
|
|
"description": "Komplettes KI-Copilot-Modul: Copilot chat_service mit OpenRouter (Claude/GPT-4), copilot router (chat, voice, history, action). System-Prompt mit ERP-Kontext (verfügbare Aktionen: vehicle search/create, contact search, sale overview). Action-System: Copilot kann strukturierte Actions ausführen (GET /api/vehicles, POST /api/contacts, etc.) via function-calling. Voice Input: STT via Browser Web Speech API → text → chat. Chat History (paginated, pro User). Frontend: Copilot Chat Interface (message list, input, send), Voice Input Button (microphone), Action Preview (zeigt was Copilot tun will vor Ausführung), Chat History sidebar.",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": ["T01", "T02"],
|
|
"requirement_ids": ["M7-F1", "M7-F2", "M7-F3", "M7-F4", "M7-F5", "M7-F6", "M7-F7"],
|
|
"estimated_effort": "L",
|
|
"estimated_lines": 700,
|
|
"acceptance_criteria": [
|
|
"POST /api/copilot/chat mit {message: 'Zeige alle LKWs'} → 200 + {response: '...', actions: [{type: 'search_vehicles', params: {type: 'lkw'}}]}",
|
|
"POST /api/copilot/chat mit empty message → 422",
|
|
"POST /api/copilot/action mit {action: 'search_vehicles', params: {type: 'lkw'}} → 200 + {result: [...vehicles...]}",
|
|
"POST /api/copilot/action mit invalid action → 400",
|
|
"GET /api/copilot/history → 200 + paginated chat history for current user",
|
|
"POST /api/copilot/voice → 200 (accepts audio, transcribes, returns chat response)",
|
|
"Copilot System-Prompt enthält ERP-Kontext und verfügbare Actions",
|
|
"Copilot kann vehicle search action auslösen (mocked OpenRouter)",
|
|
"Copilot kann contact search action auslösen (mocked OpenRouter)",
|
|
"Copilot response enthält action proposal (nicht direkt ausgeführt, User bestätigt)",
|
|
"Frontend Copilot Chat rendert message list + input",
|
|
"Frontend Voice Button startet Web Speech API",
|
|
"Frontend Action Preview zeigt geplante Aktion vor Bestätigung",
|
|
"Frontend Chat History sidebar zeigt vergangene Konversationen",
|
|
"pytest coverage >= 80% für copilot module (mit mocked OpenRouter)"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_copilot.py --cov=app/services/copilot_service --cov=app/routers/copilot --cov-report=term-missing -v",
|
|
"cd frontend && npx vitest run tests/copilot.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All copilot tests pass with mocked OpenRouter. Chat returns response + action proposals. Action endpoint executes approved actions. History paginated. Voice endpoint accepts audio. Frontend chat + voice + action preview render. Coverage >= 80% backend, >= 70% frontend.",
|
|
"test_files": [
|
|
"backend/tests/test_copilot.py",
|
|
"frontend/tests/copilot.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/models/copilot.py",
|
|
"backend/app/schemas/copilot.py",
|
|
"backend/app/routers/copilot.py",
|
|
"backend/app/services/copilot_service.py",
|
|
"backend/app/utils/copilot_actions.py",
|
|
"backend/app/utils/copilot_prompt.py",
|
|
"backend/tests/test_copilot.py",
|
|
"frontend/app/copilot/page.tsx",
|
|
"frontend/components/copilot/ChatInterface.tsx",
|
|
"frontend/components/copilot/MessageList.tsx",
|
|
"frontend/components/copilot/ChatInput.tsx",
|
|
"frontend/components/copilot/VoiceInput.tsx",
|
|
"frontend/components/copilot/ActionPreview.tsx",
|
|
"frontend/components/copilot/ChatHistory.tsx",
|
|
"frontend/tests/copilot.test.tsx"
|
|
]
|
|
},
|
|
{
|
|
"id": "T08",
|
|
"title": "Bildretusche + Preisvergleich (Flux.1-Pro + mobile.de) + Retouch UI",
|
|
"category": "retouch",
|
|
"description": "Komplettes Bildretusche-Modul: Retouch service mit OpenRouter Flux.1-Pro, retouch router (process, get result, price-compare). Bild-Verarbeitung: Upload → Flux.1-Pro Retusche (Hintergrund entfernen/verbessern, Spiegelungen, Farbkorrektur) → retouched image gespeichert. Preisvergleich: mobile.de Listings Scraper/API → comparable vehicles (brand, model, year, price) → Durchschnittspreis berechnet. Frontend: Retouch Upload page (Bild wählen, Retusche starten), Before/After Vergleich (slider), Preisvergleich view (Tabelle mit comparable listings + Durchschnittspreis).",
|
|
"assigned_subagent": "implementation_engineer",
|
|
"dependencies": ["T01", "T02", "T05"],
|
|
"requirement_ids": ["M8-F1", "M8-F2", "M8-F3", "M8-F4", "M8-F5"],
|
|
"estimated_effort": "L",
|
|
"estimated_lines": 600,
|
|
"acceptance_criteria": [
|
|
"POST /api/retouch/process mit image file → 202 + {retouch_id} (async processing)",
|
|
"POST /api/retouch/process ohne image → 422",
|
|
"POST /api/retouch/process mit invalid MIME → 422",
|
|
"GET /api/retouch/results/:id → 200 + {status, original_url, retouched_url}",
|
|
"GET /api/retouch/results/:id vor completion → 200 + {status: 'processing'}",
|
|
"Retouch Fehler (OpenRouter unavailable) → 200 + {status: 'failed', error: '...'}",
|
|
"POST /api/retouch/price-compare mit {vehicle_id} → 200 + {comparable_listings: [...], average_price: X}",
|
|
"POST /api/retouch/price-compare ohne vehicle_id → 422",
|
|
"Price-compare mit no comparable listings → 200 + {comparable_listings: [], average_price: null}",
|
|
"Frontend Retouch Upload → Bild auswählen + Retusche starten",
|
|
"Frontend Before/After Slider → vergleicht Original vs Retuschiert",
|
|
"Frontend Preisvergleich Tabelle → zeigt comparable listings + Durchschnittspreis",
|
|
"pytest coverage >= 80% für retouch module (mit mocked OpenRouter)"
|
|
],
|
|
"test_spec": {
|
|
"commands": [
|
|
"cd backend && python -m pytest tests/test_retouch.py --cov=app/services/retouch_service --cov=app/routers/image_retouch --cov-report=term-missing -v",
|
|
"cd frontend && npx vitest run tests/retouch.test.tsx --coverage"
|
|
],
|
|
"expected_results": "All retouch tests pass with mocked OpenRouter. Process queues async task. Results retrievable with status. Price-compare returns listings + average. Frontend upload + before/after + price comparison render. Coverage >= 80% backend, >= 70% frontend.",
|
|
"test_files": [
|
|
"backend/tests/test_retouch.py",
|
|
"frontend/tests/retouch.test.tsx"
|
|
],
|
|
"coverage_target": 80
|
|
},
|
|
"files_to_create": [
|
|
"backend/app/models/retouch.py",
|
|
"backend/app/schemas/retouch.py",
|
|
"backend/app/routers/image_retouch.py",
|
|
"backend/app/services/retouch_service.py",
|
|
"backend/app/services/price_compare_service.py",
|
|
"backend/app/tasks/retouch_processing.py",
|
|
"backend/tests/test_retouch.py",
|
|
"frontend/app/retouch/page.tsx",
|
|
"frontend/components/retouch/RetouchUpload.tsx",
|
|
"frontend/components/retouch/BeforeAfterSlider.tsx",
|
|
"frontend/components/retouch/PriceComparison.tsx",
|
|
"frontend/tests/retouch.test.tsx"
|
|
]
|
|
}
|
|
],
|
|
"dependency_graph": {
|
|
"T01": [],
|
|
"T02": ["T01"],
|
|
"T03": ["T01", "T02"],
|
|
"T04": ["T01"],
|
|
"T05": ["T01", "T02"],
|
|
"T06": ["T02", "T04"],
|
|
"T07": ["T01", "T02"],
|
|
"T08": ["T01", "T02", "T05"]
|
|
},
|
|
"execution_order": ["T01", "T02", "T04", "T03", "T05", "T06", "T07", "T08"],
|
|
"summary": {
|
|
"total_tasks": 8,
|
|
"tasks_with_test_spec": 8,
|
|
"tasks_with_acceptance_criteria": 8,
|
|
"tasks_with_dependencies": 8,
|
|
"total_requirement_ids": 45,
|
|
"total_acceptance_criteria": 115,
|
|
"total_files_to_create": 120,
|
|
"estimated_total_lines": 5000
|
|
}
|
|
}
|