import { PureComponent } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import IconButton from '@mui/material/IconButton'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import Chip from '@mui/material/Chip'; import Alert from '@mui/material/Alert'; import Paper from '@mui/material/Paper'; import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; import FormHelperText from '@mui/material/FormHelperText'; import CloseIcon from '@mui/icons-material/Close'; import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; import WarehouseIcon from '@mui/icons-material/Warehouse'; import { StorageManagerContext } from '../../api/storageManagerContext'; import { enqueueSnackbar } from 'notistack'; import { notifyError } from '../../utils/notify'; import { formatDate } from './manifestConfig'; import { fetchDeliveryLedgerSummary, bookDeliveryLines, } from '../../utils/deliveryLedger'; import warehouseAutomat from '../../automats/warehouseAutomat'; export default class BookDeliveryDialog extends PureComponent { static contextType = StorageManagerContext; constructor(props) { super(props); let initialAmounts = {}; if (Array.isArray(props.summary?.lines)) { for (const line of props.summary.lines) { if (props.prefillLineId != null) { initialAmounts[line.id] = line.id === props.prefillLineId ? line.remainingAmount > 0 ? line.remainingAmount : 0 : 0; } else { initialAmounts[line.id] = line.remainingAmount > 0 ? line.remainingAmount : 0; } } } const initialWarehouses = warehouseAutomat.state.warehouses || []; this.state = { summary: props.summary ?? null, loading: !props.summary, error: null, amountsToBook: initialAmounts, transactionText: props.delivery?.number ? `Lieferung ${props.delivery.number}` : 'Lieferung Wareneingang', isBooking: false, warehouses: initialWarehouses, selectedWarehouseId: initialWarehouses.length === 1 ? initialWarehouses[0].id : '', warehouseError: null, }; this.handleAmountChange = this.handleAmountChange.bind(this); this.handleWarehouseChange = this.handleWarehouseChange.bind(this); this.handleTransactionTextChange = this.handleTransactionTextChange.bind(this); this.handleBookAll = this.handleBookAll.bind(this); this.handleResetAmounts = this.handleResetAmounts.bind(this); this.handleConfirmBook = this.handleConfirmBook.bind(this); } componentDidMount() { this.unsubWarehouse = warehouseAutomat.subscribe((state) => { const warehouses = state.warehouses || []; this.setState((prev) => { const selectedId = prev.selectedWarehouseId || (warehouses.length === 1 ? warehouses[0].id : ''); return { warehouses, selectedWarehouseId: selectedId, }; }); }); void warehouseAutomat.actions.load().catch(() => {}); if (this.props.open) { void this.loadSummary(); } } componentWillUnmount() { this.unsubWarehouse?.(); } componentDidUpdate(prevProps) { if (this.props.open && !prevProps.open) { const currentWarehouses = this.state.warehouses || warehouseAutomat.state.warehouses || []; this.setState( { summary: this.props.summary ?? null, loading: !this.props.summary, error: null, warehouseError: null, selectedWarehouseId: this.state.selectedWarehouseId || (currentWarehouses.length === 1 ? currentWarehouses[0].id : ''), transactionText: this.props.delivery?.number ? `Lieferung ${this.props.delivery.number}` : 'Lieferung Wareneingang', isBooking: false, }, () => { void warehouseAutomat.actions.load().catch(() => {}); if (this.props.summary) { this.initAmountsFromSummary(this.props.summary, this.props.prefillLineId); } else { void this.loadSummary(); } } ); } else if ( this.props.open && (this.props.prefillLineId !== prevProps.prefillLineId || (this.props.summary && this.props.summary !== prevProps.summary)) && !this.state.isBooking ) { const activeSummary = this.state.summary || this.props.summary; if (activeSummary) { this.initAmountsFromSummary(activeSummary, this.props.prefillLineId); } } } handleWarehouseChange(event) { const value = event.target.value; this.setState({ selectedWarehouseId: value ? Number(value) : '', warehouseError: null, }); } async loadSummary() { const { delivery, prefillLineId } = this.props; if (!delivery?.id) return; this.setState({ loading: true, error: null }); try { const ledgerApi = this.context?.ledger; const deliveryApi = this.context?.delivery; const summary = await fetchDeliveryLedgerSummary(delivery.id, { ledgerApi, deliveryApi, }); this.initAmountsFromSummary(summary, prefillLineId); this.setState({ summary, loading: false }); } catch (err) { console.error('[BookDeliveryDialog] Failed to load ledger summary:', err); this.setState({ error: err.message || 'Laden fehlgeschlagen', loading: false }); } } initAmountsFromSummary(summary, prefillLineId = this.props.prefillLineId) { const amounts = {}; if (Array.isArray(summary?.lines)) { for (const line of summary.lines) { if (prefillLineId != null) { amounts[line.id] = line.id === prefillLineId ? line.remainingAmount > 0 ? line.remainingAmount : 0 : 0; } else { amounts[line.id] = line.remainingAmount > 0 ? line.remainingAmount : 0; } } } this.setState({ amountsToBook: amounts }); } handleAmountChange(lineId, value) { const num = Math.max(0, Number(value) || 0); this.setState((prev) => ({ amountsToBook: { ...prev.amountsToBook, [lineId]: num, }, })); } handleTransactionTextChange(e) { this.setState({ transactionText: e.target.value }); } handleBookAll() { const { summary } = this.state; if (!summary?.lines) return; const amounts = {}; for (const line of summary.lines) { amounts[line.id] = line.remainingAmount > 0 ? line.remainingAmount : 0; } this.setState({ amountsToBook: amounts }); } handleResetAmounts() { const { summary } = this.state; if (!summary?.lines) return; const amounts = {}; for (const line of summary.lines) { amounts[line.id] = 0; } this.setState({ amountsToBook: amounts }); } async handleConfirmBook() { const { delivery, onBookSuccess, onClose } = this.props; const { summary, amountsToBook, transactionText } = this.state; if (!summary?.lines) return; const linesToBook = summary.lines .map((line) => ({ manifestarticleId: line.id, sku: line.sku, name: line.name, amount: Number(amountsToBook[line.id] || 0), })) .filter((l) => l.amount > 0); if (linesToBook.length === 0) { enqueueSnackbar('Keine Mengen zum Buchen angegeben.', { variant: 'info' }); return; } const { selectedWarehouseId } = this.state; if (!selectedWarehouseId) { this.setState({ warehouseError: 'Bitte wählen Sie ein Ziellager aus.' }); enqueueSnackbar('Bitte wählen Sie ein Ziellager aus.', { variant: 'warning' }); return; } this.setState({ isBooking: true, error: null, warehouseError: null }); try { const ledgerApi = this.context?.ledger; const deliveryApi = this.context?.delivery; await bookDeliveryLines({ delivery: summary.delivery || delivery, linesToBook, warehouseId: selectedWarehouseId, ledgerApi, transactionText, }); const totalBookedQty = linesToBook.reduce((sum, l) => sum + l.amount, 0); enqueueSnackbar( `${linesToBook.length} Position${linesToBook.length === 1 ? '' : 'en'} (${totalBookedQty} Stk.) erfolgreich ins Lager gebucht.`, { variant: 'success' } ); // Refresh summary const updatedSummary = await fetchDeliveryLedgerSummary(delivery.id, { ledgerApi, deliveryApi, }); if (onBookSuccess) { onBookSuccess(updatedSummary); } if (onClose) { onClose(); } } catch (err) { console.error('[BookDeliveryDialog] Booking failed:', err); notifyError(err); this.setState({ error: err.message || 'Buchung fehlgeschlagen', isBooking: false, }); } } renderHeaderInfo() { const { delivery, partyName } = this.props; const { summary } = this.state; return ( Nummer {delivery?.number || '–'} {delivery?.externalNumber && ( Referenz {delivery.externalNumber} )} Lieferdatum {formatDate(delivery?.date)} {partyName && ( Lieferant {partyName} )} Aktueller Status: {summary?.status === 'booked' && ( } label="Vollständig gebucht" color="success" variant="outlined" sx={{ fontWeight: 600 }} /> )} {summary?.status === 'partial' && ( } label={`Teilweise gebucht (${summary.bookedPositions}/${summary.totalPositions})`} color="warning" variant="outlined" sx={{ fontWeight: 600 }} /> )} {summary?.status === 'unbooked' && ( )} {summary?.status === 'empty' && ( )} ); } renderLinesTable() { const { summary, amountsToBook, isBooking } = this.state; const lines = summary?.lines || []; if (lines.length === 0) { return ( Diese Lieferung enthält keine Artikelpositionen zum Einbuchen. ); } return ( SKU Artikel Liefermenge Bereits gebucht Jetzt einbuchen {lines.map((line) => { const toBook = amountsToBook[line.id] ?? 0; const isAlreadyFullyBooked = line.isBooked && line.remainingAmount <= 0; return ( {line.sku || '–'} {line.name || '–'} {line.targetAmount} 0 ? 'success.main' : 'text.secondary'} sx={{ fontWeight: line.bookedAmount > 0 ? 600 : 400 }} > {line.bookedAmount} {isAlreadyFullyBooked && ( )} {isAlreadyFullyBooked ? ( ) : ( this.handleAmountChange(line.id, e.target.value)} disabled={isBooking} slotProps={{ input: { inputProps: { min: 0, step: 1, style: { textAlign: 'right', padding: '4px 8px' }, }, }, }} sx={{ width: 90 }} /> )} ); })}
); } render() { const { open, onClose, delivery } = this.props; const { summary, loading, error, amountsToBook, transactionText, isBooking, warehouses, selectedWarehouseId, warehouseError, } = this.state; const lines = summary?.lines || []; const prefillLine = this.props.prefillLineId != null ? lines.find((l) => l.id === this.props.prefillLineId) : null; const totalToBook = lines.reduce( (sum, l) => sum + (Number(amountsToBook[l.id]) || 0), 0 ); const countLinesToBook = lines.filter( (l) => Number(amountsToBook[l.id]) > 0 ).length; const isFullyBooked = summary?.status === 'booked'; const selectedWarehouse = warehouses.find((w) => w.id === selectedWarehouseId); const isWarehouseMissing = !selectedWarehouseId; return ( Lieferung {delivery?.number || ''} ins Lager buchen {this.renderHeaderInfo()} {loading ? ( Lade Positionen und Ledger-Status... ) : error ? ( {error} ) : ( <> {prefillLine && ( Vorausgewählt zum Einbuchen: Position{' '} {prefillLine.sku ? `SKU ${prefillLine.sku}` : prefillLine.name} ({amountsToBook[prefillLine.id] || 0} Stk.). Andere Positionen stehen auf 0. )} {this.renderLinesTable()} {!isFullyBooked && lines.length > 0 && ( )} {warehouses.length === 0 && !isFullyBooked && ( Es ist noch kein Lager eingerichtet. Bitte legen Sie unter „Artikel → Lager“ zuerst ein Lager an. )} Ziellager * {warehouseError ? ( {warehouseError} ) : ( Pflichtfeld: Ziellager für die Buchung )} {isFullyBooked ? ( }> Alle Artikel dieser Lieferung sind bereits vollständig im Lager verbucht. ) : totalToBook > 0 ? ( {selectedWarehouseId ? ( <> Es werden {totalToBook} Stück für{' '} {countLinesToBook} Position{countLinesToBook === 1 ? '' : 'en'} als Lagerzugang (DELIVERY) in das Lager{' '} „{selectedWarehouse?.name || '–'}“ gebucht. ) : ( <> Bitte wählen Sie ein Ziellager aus, um die Buchung von{' '} {totalToBook} Stück durchzuführen. )} ) : ( Wählen Sie mindestens eine Menge größer als 0 aus, um den Lagerzugang zu buchen. )} )} {!isFullyBooked && lines.length > 0 && ( )} ); } }