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 (
);
}
}