import { useState, useMemo, useEffect } from 'react'; import { Box, Typography, Paper, Button, IconButton, Chip, Table, TableBody, TableCell, TableHead, TableRow, Autocomplete, TextField, Alert, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, LinearProgress, } from '@mui/material'; import { ArrowBack, Add as AddIcon, ShoppingCart as ShoppingCartIcon, } from '@mui/icons-material'; import { useParams, useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import DashboardLayout from '../components/dashboard/DashboardLayout'; import { PageBreadcrumbs } from '../components/common'; import { useNotification } from '../contexts/NotificationContext'; import { usePermissionContext } from '../contexts/PermissionContext'; import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage'; import { bestellungApi } from '../services/bestellung'; import { AUSRUESTUNG_STATUS_LABELS } from '../types/ausruestungsanfrage.types'; import type { AusruestungAnfrage, AusruestungAnfragePosition, CreateOrdersRequest, } from '../types/ausruestungsanfrage.types'; import type { Lieferant } from '../types/bestellung.types'; // ── Helpers ── function formatOrderId(r: AusruestungAnfrage): string { if (r.bestell_jahr && r.bestell_nummer) { return `${r.bestell_jahr}/${String(r.bestell_nummer).padStart(3, '0')}`; } return `#${r.id}`; } interface VendorAssignment { lieferantId: number; lieferantName: string; } interface VendorGroup { lieferantId: number; lieferantName: string; positionen: AusruestungAnfragePosition[]; } // ══════════════════════════════════════════════════════════════════════════════ // Component // ══════════════════════════════════════════════════════════════════════════════ export default function AusruestungsanfrageZuBestellung() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { showSuccess, showError } = useNotification(); const { hasPermission } = usePermissionContext(); const requestId = Number(id); const canManageVendors = hasPermission('bestellungen:manage_vendors'); // ── Data ── const { data: detail, isLoading, isError } = useQuery({ queryKey: ['ausruestungsanfrage', requestId], queryFn: () => ausruestungsanfrageApi.getRequest(requestId), enabled: !!requestId, }); const { data: vendors = [] } = useQuery({ queryKey: ['bestellung-vendors'], queryFn: () => bestellungApi.getVendors(), }); const { data: catalogItems = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'items-for-edit'], queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }), staleTime: 5 * 60 * 1000, }); const anfrage = detail?.anfrage; const positionen: AusruestungAnfragePosition[] = detail?.positionen ?? []; // ── State ── const [assignments, setAssignments] = useState>({}); const [orderNames, setOrderNames] = useState>({}); // New vendor dialog const [newVendorDialog, setNewVendorDialog] = useState(false); const [newVendorName, setNewVendorName] = useState(''); const [newVendorKontakt, setNewVendorKontakt] = useState(''); const [newVendorEmail, setNewVendorEmail] = useState(''); const [newVendorTelefon, setNewVendorTelefon] = useState(''); // Track which position triggered the new-vendor dialog const [newVendorTargetPosId, setNewVendorTargetPosId] = useState(null); // ── Pre-populate vendor assignments from catalog items' preferred vendor ── useEffect(() => { if (positionen.length === 0 || catalogItems.length === 0 || vendors.length === 0) return; setAssignments(prev => { const next = { ...prev }; let changed = false; positionen.forEach(pos => { // Don't overwrite existing manual assignments if (next[pos.id] != null) return; if (!pos.artikel_id) return; const artikel = catalogItems.find(a => a.id === pos.artikel_id); if (!artikel?.bevorzugter_lieferant_id) return; const vendor = vendors.find(v => v.id === artikel.bevorzugter_lieferant_id); if (!vendor) return; next[pos.id] = { lieferantId: vendor.id, lieferantName: vendor.name }; changed = true; }); return changed ? next : prev; }); }, [positionen, catalogItems, vendors]); // ── Derived: vendor groups ── const vendorGroups: VendorGroup[] = useMemo(() => { const map = new Map(); positionen.forEach(pos => { const a = assignments[pos.id]; if (!a) return; if (!map.has(a.lieferantId)) { map.set(a.lieferantId, { lieferantId: a.lieferantId, lieferantName: a.lieferantName, positionen: [] }); } map.get(a.lieferantId)!.positionen.push(pos); }); return [...map.values()]; }, [assignments, positionen]); // ── Auto-fill order names when a new vendor group appears ── useEffect(() => { if (!anfrage) return; const label = formatOrderId(anfrage); vendorGroups.forEach(g => { setOrderNames(prev => { if (prev[g.lieferantId]) return prev; return { ...prev, [g.lieferantId]: `Anfrage ${label} – ${g.lieferantName}` }; }); }); }, [vendorGroups, anfrage]); // ── Derived: progress ── const assignedCount = positionen.filter(p => assignments[p.id] != null).length; const skippedCount = positionen.length - assignedCount; // ── Mutations ── const createOrdersMut = useMutation({ mutationFn: (payload: CreateOrdersRequest) => ausruestungsanfrageApi.createOrders(requestId, payload), onSuccess: (result) => { showSuccess(`${result.created_bestellungen.length} Bestellung(en) erfolgreich erstellt`); queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage', requestId] }); navigate('/bestellungen'); }, onError: () => showError('Bestellungen konnten nicht erstellt werden'), }); const createVendorMut = useMutation({ mutationFn: (data: { name: string; kontakt_name?: string; email?: string; telefon?: string }) => bestellungApi.createVendor(data), onSuccess: (newVendor: Lieferant) => { queryClient.invalidateQueries({ queryKey: ['bestellung-vendors'] }); // Auto-assign to the position that triggered the dialog if (newVendorTargetPosId != null) { setAssignments(prev => ({ ...prev, [newVendorTargetPosId]: { lieferantId: newVendor.id, lieferantName: newVendor.name }, })); } setNewVendorDialog(false); setNewVendorName(''); setNewVendorKontakt(''); setNewVendorEmail(''); setNewVendorTelefon(''); setNewVendorTargetPosId(null); showSuccess(`Lieferant "${newVendor.name}" erstellt`); }, onError: () => showError('Lieferant konnte nicht erstellt werden'), }); // ── Handlers ── const canSubmit = vendorGroups.length > 0 && !createOrdersMut.isPending; const handleSubmit = () => { if (!canSubmit) return; const orders = vendorGroups.map(g => ({ lieferant_id: g.lieferantId, bezeichnung: orderNames[g.lieferantId] || `Anfrage – ${g.lieferantName}`, positionen: g.positionen.map(p => ({ position_id: p.id, bezeichnung: p.bezeichnung, menge: p.menge, einheit: p.einheit, notizen: p.notizen, artikel_id: p.artikel_id, spezifikationen: p.eigenschaften?.length ? p.eigenschaften.map(e => `${e.eigenschaft_name}: ${e.wert}`) : undefined, })), })); createOrdersMut.mutate({ orders }); }; const handleCreateVendor = () => { if (!newVendorName.trim()) return; createVendorMut.mutate({ name: newVendorName.trim(), kontakt_name: newVendorKontakt.trim() || undefined, email: newVendorEmail.trim() || undefined, telefon: newVendorTelefon.trim() || undefined, }); }; // ── Loading / error states ── if (isLoading) { return ( ); } if (isError || !anfrage) { return ( Anfrage konnte nicht geladen werden. ); } // ── Render ── return ( {/* ── Header ── */} navigate(`/ausruestungsanfrage/${requestId}`)} size="small"> Bestellungen erstellen {anfrage.bezeichnung || `Anfrage ${formatOrderId(anfrage)}`} {' · '} 0 ? 'primary' : 'default'} variant="outlined" /> {/* ── Progress bar ── */} 0 ? (assignedCount / positionen.length) * 100 : 0} sx={{ mb: 3, borderRadius: 2, height: 6 }} color="primary" /> {/* ── Items table ── */} Lieferanten zuweisen Artikel Menge Lieferant {positionen.map(pos => { const assignment = assignments[pos.id]; const selectedVendor = assignment ? vendors.find(v => v.id === assignment.lieferantId) ?? null : null; return ( {pos.bezeichnung} {pos.notizen && ( {pos.notizen} )} {pos.menge} {pos.einheit ?? 'Stk'} size="small" sx={{ flex: 1 }} options={vendors} getOptionLabel={o => o.name} value={selectedVendor} onChange={(_, v) => { setAssignments(prev => ({ ...prev, [pos.id]: v ? { lieferantId: v.id, lieferantName: v.name } : null, })); }} renderInput={params => ( )} isOptionEqualToValue={(o, v) => o.id === v.id} /> {canManageVendors && ( { setNewVendorTargetPosId(pos.id); setNewVendorDialog(true); }} > )} ); })}
{/* ── Preview section ── */} {vendorGroups.length > 0 && ( Vorschau — {vendorGroups.length} Bestellung{vendorGroups.length !== 1 ? 'en' : ''} werden erstellt {vendorGroups.map(g => ( {g.lieferantName} {g.positionen.length} Artikel {g.positionen.map(p => ( · {p.bezeichnung} ×{p.menge} ))} setOrderNames(prev => ({ ...prev, [g.lieferantId]: e.target.value })) } /> ))} )} {/* ── Bottom action bar ── */} {skippedCount > 0 && assignedCount > 0 && ( {skippedCount} Artikel {skippedCount === 1 ? 'ist' : 'sind'} auf Lager und {skippedCount === 1 ? 'wird' : 'werden'} nicht bestellt. )} {assignedCount === 0 && positionen.length > 0 && ( Mindestens einem Artikel einen Lieferanten zuweisen, um Bestellungen zu erstellen. )}
{/* ── New vendor dialog ── */} { setNewVendorDialog(false); setNewVendorTargetPosId(null); }} maxWidth="sm" fullWidth > Neuen Lieferanten anlegen setNewVendorName(e.target.value)} autoFocus fullWidth /> setNewVendorKontakt(e.target.value)} fullWidth /> setNewVendorEmail(e.target.value)} fullWidth type="email" /> setNewVendorTelefon(e.target.value)} fullWidth />
); }