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 CloseIcon from '@mui/icons-material/Close'; import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; import { StorageManagerContext } from '../../api/storageManagerContext'; import { enqueueSnackbar } from 'notistack'; import { notifyError } from '../../utils/notify'; import { formatDate } from './manifestConfig'; import { fetchDeliveryLedgerSummary, bookDeliveryLines, } from '../../utils/deliveryLedger'; 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; } } } 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, }; this.handleAmountChange = this.handleAmountChange.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() { if (this.props.open) { void this.loadSummary(); } } componentDidUpdate(prevProps) { if (this.props.open && !prevProps.open) { this.setState( { summary: this.props.summary ?? null, loading: !this.props.summary, error: null, transactionText: this.props.delivery?.number ? `Lieferung ${this.props.delivery.number}` : 'Lieferung Wareneingang', isBooking: false, }, () => { 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); } } } 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; } this.setState({ isBooking: true, error: null }); try { const ledgerApi = this.context?.ledger; const deliveryApi = this.context?.delivery; await bookDeliveryLines({ delivery: summary.delivery || delivery, linesToBook, 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, } = 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'; 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 && ( )} {isFullyBooked ? ( }> Alle Artikel dieser Lieferung sind bereits vollständig im Lager verbucht. ) : totalToBook > 0 ? ( Es werden {totalToBook} Stück für{' '} {countLinesToBook} Position{countLinesToBook === 1 ? '' : 'en'} als Lagerzugang (DELIVERY) im Ledger gebucht. ) : ( Wählen Sie mindestens eine Menge größer als 0 aus, um den Lagerzugang zu buchen. )} )} {!isFullyBooked && lines.length > 0 && ( )} ); } }