feat(T06): Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI

This commit is contained in:
2026-07-17 01:58:34 +02:00
parent a38d340ddc
commit 6603e411e9
33 changed files with 3481 additions and 107 deletions
+42 -27
View File
@@ -1,37 +1,52 @@
# Current Status
## T05: Dateiablage pro Fahrzeug + File UI
## T06: Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI
**Status:** COMPLETED
### Backend
-File model (app/models/file.py) - UUID PK, vehicle_id FK, all fields
-File schemas (app/schemas/file.py) - FileUploadResponse, FileResponse, FileListResponse, FileDeleteResponse
-File service (app/services/file_service.py) - upload, get, list, delete, validate_mime_type, validate_file_size
-Thumbnail utils (app/utils/thumbnails.py) - 200x200 thumbnails via Pillow for jpg/png/webp
-Files router (app/routers/files.py) - GET/POST/GET/DELETE endpoints registered in main.py
-Backend tests (tests/test_files.py) - 43 tests, all passing, 82% coverage
-Sale model (app/models/sale.py) - UUID PK, vehicle_id FK, buyer_contact_id FK, seller_contact_id FK nullable, sale_price DECIMAL, sale_date, status enum, is_gwg BOOLEAN, contract_pdf_path
-DATEVExport model (app/models/datev_export.py) - UUID PK, start_date, end_date, file_path, total_amount, created_at
-Sale schemas (app/schemas/sale.py) - SaleCreate, SaleUpdate, SaleResponse, SaleListResponse, ContractResponse, UstIdVerifyResponse
-DATEV schemas (app/schemas/datev.py) - DATEVExportCreate, DATEVExportResponse, DATEVExportListResponse
-Sale service (app/services/sale_service.py) - create_sale, get_sale, list_sales, update_sale, cancel_sale, delete_sale, regenerate_contract_pdf, verify_ust_id
-DATEV service (app/services/datev_service.py) - create_export, list_exports, get_export_csv
- ✅ Contract PDF utils (app/utils/contract_pdf.py) - HTML template with WeasyPrint, GwG-Klausel, USt-IdNr. field, async via asyncio.to_thread
- ✅ DATEV CSV utils (app/utils/datev.py) - Buchungsstapel format: Datum, Konto, Gegenkonto, Betrag, Belegfeld, Buchungstext
- ✅ Sales router (app/routers/sales.py) - GET/POST/PUT/DELETE /sales, POST/GET /sales/:id/contract, POST /sales/:id/verify-ust-id
- ✅ DATEV router (app/routers/datev.py) - POST /datev/export, GET /datev/exports, GET /datev/exports/:id/download
- ✅ Config updated - BZST_API_ENABLED=False, WEASYPRINT_OUTPUT_DIR
- ✅ main.py updated - sales + datev routers registered
- ✅ requirements.txt - weasyprint>=62.0 added
- ✅ models/__init__.py - all models imported for Base.metadata registration
- ✅ Backend tests (tests/test_sales.py, tests/test_datev.py) - 34 tests, all passing
### Frontend
-Files lib (lib/files.ts) - API functions, types, utilities
-FileUpload component - Drag-and-drop multi-file upload
-FileList component - File list with thumbnails, delete confirm dialog
-FileGallery component - Gallery view for vehicle images
-FilePreview component - File preview modal
-Frontend tests (tests/files.test.tsx) - 25 tests, all passing
-TypeScript build check passes
-Sales lib (lib/sales.ts) - listSales, getSale, createSale, updateSale, deleteSale, regenerateContract, verifyUstId
-DATEV lib (lib/datev.ts) - createDatevExport, listDatevExports, getDatevDownloadUrl
-SaleList component - Sales table with status + date range filters, pagination
-SaleForm component - Sale create form with vehicle select, buyer input, price, GwG toggle
-ContractPreview component - PDF embed preview, regenerate button, download link
-DatevExport component - Date range picker, export list table, CSV download
-Pages: verkauf/page.tsx, verkauf/[id]/page.tsx, verkauf/neu/page.tsx
- ✅ Frontend tests (tests/sales.test.tsx, tests/datev.test.tsx) - 16 tests, all passing
### Acceptance Criteria Met
- ✅ GET /api/v1/vehicles/:id/files → 200 + list of files
-POST /api/v1/vehicles/:id/files mit multipart image → 201 + file object
- ✅ POST /api/v1/vehicles/:id/files mit >20MB file → 413
- ✅ POST /api/v1/vehicles/:id/files mit invalid MIME type → 422
-GET /api/v1/vehicles/:id/files/:fileId → 200 + file content (download)
-DELETE /api/v1/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 (82%)
- ✅ GET /api/v1/sales → 200 + paginated list with filter (date range, status)
-GET /api/v1/sales/:id → 200 + sale detail with vehicle + contact nested
- ✅ POST /api/v1/sales mit valid data → 201 + sale object
- ✅ POST /api/v1/sales ohne vehicle_id → 422
-POST /api/v1/sales ohne buyer_contact_id → 422
-PUT /api/v1/sales/:id → 200 + updated sale
-DELETE /api/v1/sales/:id → 200 (sale cancelled, vehicle status back to available)
-POST /api/v1/sales/:id/contract → 200 + regenerated contract PDF path
-GET /api/v1/sales/:id/contract → 200 + PDF content (application/pdf)
-Sale mit is_gwg=true und sale_price <= 800 → GwG-Klausel in contract
-POST /api/v1/sales/:id/verify-ust-id → 200 + {verified: false, message: 'BZSt API not available'}
-POST /api/v1/datev/export mit date range → 201 + DATEVExport object
-POST /api/v1/datev/export mit invalid date range → 422
- ✅ GET /api/v1/datev/exports → 200 + list of exports
- ✅ GET /api/v1/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='available' (in_stock)
+135 -25
View File
@@ -12,7 +12,16 @@
"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"],
"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": [
@@ -88,8 +97,18 @@
"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"],
"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": [
@@ -152,8 +171,17 @@
"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"],
"dependencies": [
"T01",
"T02"
],
"requirement_ids": [
"M2-F1",
"M2-F2",
"M2-F3",
"M2-F4",
"M2-F5"
],
"estimated_effort": "L",
"estimated_lines": 600,
"acceptance_criteria": [
@@ -205,8 +233,17 @@
"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"],
"dependencies": [
"T01"
],
"requirement_ids": [
"M3-F1",
"M3-F2",
"M3-F3",
"M3-F4",
"M3-F5",
"M3-F6"
],
"estimated_effort": "M",
"estimated_lines": 500,
"acceptance_criteria": [
@@ -260,8 +297,17 @@
"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"],
"dependencies": [
"T01",
"T02"
],
"requirement_ids": [
"M4-F1",
"M4-F2",
"M4-F3",
"M4-F4",
"M4-F5"
],
"estimated_effort": "M",
"estimated_lines": 500,
"acceptance_criteria": [
@@ -311,8 +357,21 @@
"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"],
"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": [
@@ -376,7 +435,8 @@
"frontend/components/sales/DatevExport.tsx",
"frontend/tests/sales.test.tsx",
"frontend/tests/datev.test.tsx"
]
],
"status": "completed"
},
{
"id": "T07",
@@ -384,8 +444,19 @@
"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"],
"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": [
@@ -441,8 +512,18 @@
"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"],
"dependencies": [
"T01",
"T02",
"T05"
],
"requirement_ids": [
"M8-F1",
"M8-F2",
"M8-F3",
"M8-F4",
"M8-F5"
],
"estimated_effort": "L",
"estimated_lines": 600,
"acceptance_criteria": [
@@ -490,15 +571,44 @@
],
"dependency_graph": {
"T01": [],
"T02": ["T01"],
"T03": ["T01", "T02"],
"T04": ["T01"],
"T05": ["T01", "T02"],
"T06": ["T02", "T04"],
"T07": ["T01", "T02"],
"T08": ["T01", "T02", "T05"]
"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"],
"execution_order": [
"T01",
"T02",
"T04",
"T03",
"T05",
"T06",
"T07",
"T08"
],
"summary": {
"total_tasks": 8,
"tasks_with_test_spec": 8,
@@ -509,4 +619,4 @@
"total_files_to_create": 120,
"estimated_total_lines": 5000
}
}
}
+43 -26
View File
@@ -1,38 +1,55 @@
# Worklog
## T05: Dateiablage pro Fahrzeug + File UI - 2026-07-17
## T06: Verkaufsmodul + Rechtsdokumente + DATEV-Export + Sales UI
**Date:** 2026-07-17
**Status:** COMPLETED
### Files Created
**Backend:**
- backend/app/models/file.py - File SQLAlchemy model (UUID, vehicle_id FK, filename, path, mime_type, size, thumbnail_path)
- backend/app/schemas/file.py - Pydantic schemas (FileUploadResponse, FileResponse, FileListResponse, FileDeleteResponse)
- backend/app/services/file_service.py - File service (upload, get, list, delete, validate_mime_type, validate_file_size)
- backend/app/utils/thumbnails.py - Thumbnail generation (200x200) using Pillow for jpg/png/webp
- backend/app/routers/files.py - Files router (GET list, POST upload, GET download, DELETE)
- backend/tests/test_files.py - 43 tests covering all endpoints, validation, thumbnails, service unit tests
#### Backend
- backend/app/models/sale.py - Sale model with UUID PK, vehicle/buyer/seller FKs, sale_price, status, is_gwg, contract_pdf_path
- backend/app/models/datev_export.py - DATEVExport model with start_date, end_date, file_path, total_amount
- backend/app/schemas/sale.py - SaleCreate, SaleUpdate, SaleResponse, SaleListResponse, ContractResponse, UstIdVerifyResponse
- backend/app/schemas/datev.py - DATEVExportCreate, DATEVExportResponse, DATEVExportListResponse
- backend/app/services/sale_service.py - Full CRUD + contract PDF + GwG logic + USt-IdNr. verification
- backend/app/services/datev_service.py - Export creation, listing, CSV download
- backend/app/utils/contract_pdf.py - HTML template with WeasyPrint, GwG-Klausel §6 Abs. 2 EStG, USt-IdNr. field
- backend/app/utils/datev.py - DATEV CSV Buchungsstapel format helper
- backend/app/routers/sales.py - Sales CRUD + contract + USt-IdNr. endpoints
- backend/app/routers/datev.py - DATEV export + list + download endpoints
- backend/tests/test_sales.py - 20 tests covering CRUD, contract PDF, GwG, vehicle status, USt-IdNr.
- backend/tests/test_datev.py - 14 tests covering export API, CSV format, validation
**Frontend:**
- frontend/lib/files.ts - API functions, types, utilities (listFiles, uploadFile, deleteFile, formatFileSize, isImageMime)
- frontend/components/files/FileUpload.tsx - Drag-and-drop multi-file upload with validation
- frontend/components/files/FileList.tsx - File list with thumbnails, download, delete confirm dialog
- frontend/components/files/FileGallery.tsx - Gallery view filtering images only
- frontend/components/files/FilePreview.tsx - File preview modal with metadata and download
- frontend/tests/files.test.tsx - 25 tests covering all components and utilities
#### Frontend
- frontend/lib/sales.ts - Sales API client functions and types
- frontend/lib/datev.ts - DATEV API client functions and types
- frontend/components/sales/SaleList.tsx - Sales table with filters and pagination
- frontend/components/sales/SaleForm.tsx - Sale create form with GwG toggle
- frontend/components/sales/ContractPreview.tsx - PDF preview with regenerate/download
- frontend/components/sales/DatevExport.tsx - DATEV export page with date range picker
- frontend/app/[locale]/verkauf/page.tsx - Sales list page
- frontend/app/[locale]/verkauf/[id]/page.tsx - Sale detail with contract preview
- frontend/app/[locale]/verkauf/neu/page.tsx - Sale create page
- frontend/tests/sales.test.tsx - 11 frontend tests
- frontend/tests/datev.test.tsx - 5 frontend tests
### Files Modified
- backend/app/models/vehicle.py - Added files relationship to Vehicle model
- backend/app/main.py - Registered files router
- backend/requirements.txt - Added Pillow>=10.0.0
- backend/app/config.py - Added BZST_API_ENABLED=False, WEASYPRINT_OUTPUT_DIR
- backend/app/main.py - Registered sales + datev routers
- backend/app/models/__init__.py - Added sale + datev_export imports for Base.metadata registration
- backend/requirements.txt - Added weasyprint>=62.0
### Test Results
- Backend: 43/43 passed, 82% coverage (file_service 95%, thumbnails 88%, files router 55%)
- Frontend: 25/25 passed
- TypeScript: tsc --noEmit passes with no errors
- Backend: 34/34 passed (11.42s)
- Frontend: 16/16 passed (1.85s)
- Total: 50/50 passed
### Smoke Test
- Backend API endpoints fully functional with real PostgreSQL (upload, list, download, delete)
- Thumbnail generation verified for JPEG, PNG, WebP (200x200)
- MIME validation enforces jpg/png/webp/pdf/doc/docx only
- File size limit enforced at 20MB
- Frontend drag-and-drop upload works, delete confirmation dialog works, gallery filters images
- Backend: All endpoints respond correctly (200/201/404/422)
- GwG logic: Clause appears when is_gwg=true AND price <= 800 EUR (§6 Abs. 2 EStG)
- Vehicle status: Sale creation → 'sold', sale cancel → 'available'
- DATEV CSV: Correct headers, comma decimal separator, DD.MM.YYYY date format
- USt-IdNr. verify: Returns 'BZSt API not available' when feature flag disabled
- WeasyPrint: Installed, async PDF generation via asyncio.to_thread
- Frontend: All components render with mocked API, test IDs present