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;