diff --git a/backend/src/services/bestellung.service.ts b/backend/src/services/bestellung.service.ts index de52aec..5264650 100644 --- a/backend/src/services/bestellung.service.ts +++ b/backend/src/services/bestellung.service.ts @@ -190,6 +190,7 @@ async function getOrderById(id: number) { l.adresse AS lieferant_adresse, COALESCE(u.name, u.preferred_username, u.email) AS besteller_name, u.email AS besteller_email, + COALESCE(mp.telefon_mobil, mp.telefon_privat) AS besteller_telefon, mp.dienstgrad AS besteller_dienstgrad FROM bestellungen b LEFT JOIN lieferanten l ON l.id = b.lieferant_id diff --git a/frontend/src/pages/AusruestungsanfrageDetail.tsx b/frontend/src/pages/AusruestungsanfrageDetail.tsx index 6eb7cc7..6a1b08a 100644 --- a/frontend/src/pages/AusruestungsanfrageDetail.tsx +++ b/frontend/src/pages/AusruestungsanfrageDetail.tsx @@ -429,6 +429,15 @@ export default function AusruestungsanfrageDetail() { {p.geliefert && detail?.im_haus && ( )} + {p.geliefert && p.zuweisung_typ === 'keine' && ( + + )} + {p.geliefert && p.zuweisung_typ === 'persoenlich' && ( + + )} + {p.geliefert && p.zuweisung_typ === 'ausruestung' && ( + + )} {p.eigenschaften && p.eigenschaften.length > 0 && p.eigenschaften.map(e => ( ))} diff --git a/frontend/src/pages/AusruestungsanfrageZuBestellung.tsx b/frontend/src/pages/AusruestungsanfrageZuBestellung.tsx index e306fae..ca37ffd 100644 --- a/frontend/src/pages/AusruestungsanfrageZuBestellung.tsx +++ b/frontend/src/pages/AusruestungsanfrageZuBestellung.tsx @@ -4,7 +4,7 @@ import { Table, TableBody, TableCell, TableHead, TableRow, Autocomplete, TextField, Alert, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, - LinearProgress, + LinearProgress, FormControlLabel, Switch, } from '@mui/material'; import { ArrowBack, @@ -85,6 +85,7 @@ export default function AusruestungsanfrageZuBestellung() { const [assignments, setAssignments] = useState>({}); const [orderNames, setOrderNames] = useState>({}); const [quantities, setQuantities] = useState>({}); + const [istErsatzOverrides, setIstErsatzOverrides] = useState>({}); // New vendor dialog const [newVendorDialog, setNewVendorDialog] = useState(false); @@ -301,6 +302,24 @@ export default function AusruestungsanfrageZuBestellung() { {pos.notizen && ( {pos.notizen} )} + {pos.eigenschaften && pos.eigenschaften.length > 0 && ( + + {pos.eigenschaften.map(e => ( + + ))} + + )} + setIstErsatzOverrides(prev => ({ ...prev, [pos.id]: e.target.checked }))} + /> + } + label={Ersatzbeschaffung} + /> {pos.menge} {pos.einheit ?? 'Stk'} @@ -385,7 +404,10 @@ export default function AusruestungsanfrageZuBestellung() { {g.positionen.map(p => ( - · {p.bezeichnung} ×{p.menge} + · {p.bezeichnung} ×{quantities[p.id] ?? p.menge} + {(istErsatzOverrides[p.id] ?? p.ist_ersatz) && ( + + )} ))} diff --git a/frontend/src/pages/BestellungDetail.tsx b/frontend/src/pages/BestellungDetail.tsx index 6ecf1fa..901d458 100644 --- a/frontend/src/pages/BestellungDetail.tsx +++ b/frontend/src/pages/BestellungDetail.tsx @@ -203,6 +203,7 @@ export default function BestellungDetail() { const canExport = hasPermission('bestellungen:export'); const validTransitions = bestellung ? STATUS_TRANSITIONS[bestellung.status] : []; const allCostsEntered = positionen.length === 0 || positionen.every(p => p.einzelpreis != null && Number(p.einzelpreis) > 0); + const allItemsReceived = positionen.length === 0 || positionen.every(p => Number(p.erhalten_menge) >= Number(p.menge)); // All statuses except current, for force override const ALL_STATUSES: BestellungStatus[] = ['entwurf', 'wartet_auf_genehmigung', 'bereit_zur_bestellung', 'bestellt', 'teillieferung', 'lieferung_pruefen', 'abgeschlossen']; @@ -475,6 +476,7 @@ export default function BestellungDetail() { : bestellung.besteller_name; row('Name', nameWithRank); if (bestellung.besteller_email) row('E-Mail', bestellung.besteller_email); + if (bestellung.besteller_telefon) row('Telefon', bestellung.besteller_telefon); curY += 3; } @@ -563,6 +565,17 @@ export default function BestellungDetail() { data.cell.y, ); } + // Line at bottom of last row + if (data.row.index === rows.length - 1) { + data.doc.setDrawColor(200, 200, 200); + data.doc.setLineWidth(0.2); + data.doc.line( + data.settings.margin.left, + data.cell.y + data.cell.height, + data.doc.internal.pageSize.width - data.settings.margin.right, + data.cell.y + data.cell.height, + ); + } } }, foot: [ @@ -616,6 +629,17 @@ export default function BestellungDetail() { data.cell.y, ); } + // Line at bottom of last row + if (data.row.index === rows.length - 1) { + data.doc.setDrawColor(200, 200, 200); + data.doc.setLineWidth(0.2); + data.doc.line( + data.settings.margin.left, + data.cell.y + data.cell.height, + data.doc.internal.pageSize.width - data.settings.margin.right, + data.cell.y + data.cell.height, + ); + } } }, didDrawPage: addPdfFooter(doc, settings), @@ -786,6 +810,9 @@ export default function BestellungDetail() { {validTransitions.includes('abgeschlossen' as BestellungStatus) && !allCostsEntered && ( Nicht alle Positionen haben Kosten. Bitte Einzelpreise eintragen, bevor die Bestellung abgeschlossen wird. )} + {validTransitions.includes('abgeschlossen' as BestellungStatus) && !allItemsReceived && ( + Nicht alle Positionen wurden vollständig empfangen. Bitte Eingangsmenge prüfen, bevor die Bestellung abgeschlossen wird. + )} {validTransitions .filter((s) => { @@ -813,7 +840,7 @@ export default function BestellungDetail() { key={s} variant="contained" color={color as 'success' | 'error' | 'primary'} - disabled={isAbgeschlossen && !allCostsEntered} + disabled={isAbgeschlossen && (!allCostsEntered || !allItemsReceived)} onClick={() => { setStatusForce(false); setStatusConfirmTarget(s); }} > {label} diff --git a/frontend/src/types/bestellung.types.ts b/frontend/src/types/bestellung.types.ts index 612e627..61bc65f 100644 --- a/frontend/src/types/bestellung.types.ts +++ b/frontend/src/types/bestellung.types.ts @@ -62,6 +62,7 @@ export interface Bestellung { besteller_id?: string; besteller_name?: string; besteller_email?: string; + besteller_telefon?: string; besteller_dienstgrad?: string; status: BestellungStatus; budget?: number;