710 lines
24 KiB
JavaScript
710 lines
24 KiB
JavaScript
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 (
|
||
<Paper
|
||
variant="outlined"
|
||
sx={{
|
||
p: 1.5,
|
||
mb: 2,
|
||
display: 'flex',
|
||
flexWrap: 'wrap',
|
||
gap: 3,
|
||
backgroundColor: '#f8fafc',
|
||
alignItems: 'center',
|
||
}}
|
||
>
|
||
<Box>
|
||
<Typography variant="caption" color="text.secondary" display="block">
|
||
Nummer
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||
{delivery?.number || '–'}
|
||
</Typography>
|
||
</Box>
|
||
|
||
{delivery?.externalNumber && (
|
||
<Box>
|
||
<Typography variant="caption" color="text.secondary" display="block">
|
||
Referenz
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||
{delivery.externalNumber}
|
||
</Typography>
|
||
</Box>
|
||
)}
|
||
|
||
<Box>
|
||
<Typography variant="caption" color="text.secondary" display="block">
|
||
Lieferdatum
|
||
</Typography>
|
||
<Typography variant="body2">
|
||
{formatDate(delivery?.date)}
|
||
</Typography>
|
||
</Box>
|
||
|
||
{partyName && (
|
||
<Box>
|
||
<Typography variant="caption" color="text.secondary" display="block">
|
||
Lieferant
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||
{partyName}
|
||
</Typography>
|
||
</Box>
|
||
)}
|
||
|
||
<Box sx={{ ml: 'auto', display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Typography variant="caption" color="text.secondary">
|
||
Aktueller Status:
|
||
</Typography>
|
||
{summary?.status === 'booked' && (
|
||
<Chip
|
||
size="small"
|
||
icon={<CheckCircleOutlineOutlinedIcon />}
|
||
label="Vollständig gebucht"
|
||
color="success"
|
||
variant="outlined"
|
||
sx={{ fontWeight: 600 }}
|
||
/>
|
||
)}
|
||
{summary?.status === 'partial' && (
|
||
<Chip
|
||
size="small"
|
||
icon={<Inventory2OutlinedIcon />}
|
||
label={`Teilweise gebucht (${summary.bookedPositions}/${summary.totalPositions})`}
|
||
color="warning"
|
||
variant="outlined"
|
||
sx={{ fontWeight: 600 }}
|
||
/>
|
||
)}
|
||
{summary?.status === 'unbooked' && (
|
||
<Chip
|
||
size="small"
|
||
label={`Offen (${summary.totalPositions} Pos.)`}
|
||
variant="outlined"
|
||
sx={{ color: 'text.secondary', borderColor: 'divider' }}
|
||
/>
|
||
)}
|
||
{summary?.status === 'empty' && (
|
||
<Chip size="small" label="Keine Positionen" variant="outlined" />
|
||
)}
|
||
</Box>
|
||
</Paper>
|
||
);
|
||
}
|
||
|
||
renderLinesTable() {
|
||
const { summary, amountsToBook, isBooking } = this.state;
|
||
const lines = summary?.lines || [];
|
||
|
||
if (lines.length === 0) {
|
||
return (
|
||
<Alert severity="info" sx={{ my: 2 }}>
|
||
Diese Lieferung enthält keine Artikelpositionen zum Einbuchen.
|
||
</Alert>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<TableContainer component={Paper} variant="outlined" sx={{ mb: 2 }}>
|
||
<Table size="small">
|
||
<TableHead sx={{ backgroundColor: '#f1f5f9' }}>
|
||
<TableRow>
|
||
<TableCell sx={{ fontWeight: 600 }}>SKU</TableCell>
|
||
<TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>Liefermenge</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>Bereits gebucht</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600, width: 140 }}>Jetzt einbuchen</TableCell>
|
||
</TableRow>
|
||
</TableHead>
|
||
<TableBody>
|
||
{lines.map((line) => {
|
||
const toBook = amountsToBook[line.id] ?? 0;
|
||
const isAlreadyFullyBooked = line.isBooked && line.remainingAmount <= 0;
|
||
|
||
return (
|
||
<TableRow key={line.id} hover>
|
||
<TableCell sx={{ fontFamily: 'monospace', fontSize: '0.85rem' }}>
|
||
{line.sku || '–'}
|
||
</TableCell>
|
||
<TableCell sx={{ maxWidth: 280 }}>
|
||
<Typography variant="body2" sx={{ fontWeight: 500 }} noWrap>
|
||
{line.name || '–'}
|
||
</Typography>
|
||
</TableCell>
|
||
<TableCell align="right">
|
||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||
{line.targetAmount}
|
||
</Typography>
|
||
</TableCell>
|
||
<TableCell align="right">
|
||
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, justifyContent: 'flex-end' }}>
|
||
<Typography
|
||
variant="body2"
|
||
color={line.bookedAmount > 0 ? 'success.main' : 'text.secondary'}
|
||
sx={{ fontWeight: line.bookedAmount > 0 ? 600 : 400 }}
|
||
>
|
||
{line.bookedAmount}
|
||
</Typography>
|
||
{isAlreadyFullyBooked && (
|
||
<CheckCircleOutlineOutlinedIcon color="success" sx={{ fontSize: 16 }} />
|
||
)}
|
||
</Box>
|
||
</TableCell>
|
||
<TableCell align="right">
|
||
{isAlreadyFullyBooked ? (
|
||
<Chip
|
||
size="small"
|
||
label="Gebucht"
|
||
color="success"
|
||
variant="outlined"
|
||
sx={{ fontSize: '0.75rem', height: 24 }}
|
||
/>
|
||
) : (
|
||
<TextField
|
||
size="small"
|
||
type="number"
|
||
value={toBook}
|
||
onChange={(e) => 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 }}
|
||
/>
|
||
)}
|
||
</TableCell>
|
||
</TableRow>
|
||
);
|
||
})}
|
||
</TableBody>
|
||
</Table>
|
||
</TableContainer>
|
||
);
|
||
}
|
||
|
||
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 (
|
||
<Dialog
|
||
open={Boolean(open)}
|
||
onClose={isBooking ? undefined : onClose}
|
||
maxWidth="md"
|
||
fullWidth
|
||
>
|
||
<DialogTitle
|
||
sx={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
pb: 1,
|
||
}}
|
||
>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Inventory2OutlinedIcon color="primary" />
|
||
<Typography variant="h6" component="span" sx={{ fontWeight: 600 }}>
|
||
Lieferung {delivery?.number || ''} ins Lager buchen
|
||
</Typography>
|
||
</Box>
|
||
<IconButton size="small" onClick={onClose} disabled={isBooking}>
|
||
<CloseIcon />
|
||
</IconButton>
|
||
</DialogTitle>
|
||
|
||
<DialogContent dividers sx={{ pt: 2 }}>
|
||
{this.renderHeaderInfo()}
|
||
|
||
{loading ? (
|
||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6, gap: 2, alignItems: 'center' }}>
|
||
<CircularProgress size={24} />
|
||
<Typography color="text.secondary">Lade Positionen und Ledger-Status...</Typography>
|
||
</Box>
|
||
) : error ? (
|
||
<Alert severity="error" sx={{ my: 2 }}>
|
||
{error}
|
||
</Alert>
|
||
) : (
|
||
<>
|
||
{prefillLine && (
|
||
<Alert severity="info" sx={{ mb: 2 }}>
|
||
Vorausgewählt zum Einbuchen: Position{' '}
|
||
<strong>{prefillLine.sku ? `SKU ${prefillLine.sku}` : prefillLine.name}</strong> ({amountsToBook[prefillLine.id] || 0} Stk.). Andere Positionen stehen auf 0.
|
||
</Alert>
|
||
)}
|
||
{this.renderLinesTable()}
|
||
|
||
{!isFullyBooked && lines.length > 0 && (
|
||
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
|
||
<Button
|
||
size="small"
|
||
variant="text"
|
||
onClick={this.handleBookAll}
|
||
disabled={isBooking}
|
||
>
|
||
Alle offenen Mengen übernehmen
|
||
</Button>
|
||
<Button
|
||
size="small"
|
||
variant="text"
|
||
color="inherit"
|
||
onClick={this.handleResetAmounts}
|
||
disabled={isBooking}
|
||
>
|
||
Zurücksetzen
|
||
</Button>
|
||
</Box>
|
||
)}
|
||
|
||
{warehouses.length === 0 && !isFullyBooked && (
|
||
<Alert severity="error" sx={{ mb: 2 }}>
|
||
Es ist noch kein Lager eingerichtet. Bitte legen Sie unter „Artikel → Lager“ zuerst ein Lager an.
|
||
</Alert>
|
||
)}
|
||
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
gap: 2,
|
||
mb: 2,
|
||
flexDirection: { xs: 'column', sm: 'row' },
|
||
}}
|
||
>
|
||
<FormControl
|
||
size="small"
|
||
required
|
||
error={Boolean(warehouseError)}
|
||
disabled={isBooking || isFullyBooked}
|
||
sx={{ minWidth: { sm: 240 }, flex: { sm: '0 0 auto' } }}
|
||
>
|
||
<InputLabel id="warehouse-select-label">Ziellager *</InputLabel>
|
||
<Select
|
||
labelId="warehouse-select-label"
|
||
id="warehouse-select"
|
||
value={selectedWarehouseId ?? ''}
|
||
label="Ziellager *"
|
||
onChange={this.handleWarehouseChange}
|
||
startAdornment={
|
||
<WarehouseIcon
|
||
sx={{ mr: 1, color: 'text.secondary', fontSize: 20 }}
|
||
/>
|
||
}
|
||
>
|
||
{warehouses.length === 0 ? (
|
||
<MenuItem value="" disabled>
|
||
Keine Lager vorhanden
|
||
</MenuItem>
|
||
) : (
|
||
warehouses.map((wh) => (
|
||
<MenuItem key={wh.id} value={wh.id}>
|
||
{wh.name}
|
||
</MenuItem>
|
||
))
|
||
)}
|
||
</Select>
|
||
{warehouseError ? (
|
||
<FormHelperText>{warehouseError}</FormHelperText>
|
||
) : (
|
||
<FormHelperText>Pflichtfeld: Ziellager für die Buchung</FormHelperText>
|
||
)}
|
||
</FormControl>
|
||
|
||
<TextField
|
||
fullWidth
|
||
size="small"
|
||
label="Buchungstext (Ledger-Referenz)"
|
||
value={transactionText}
|
||
onChange={this.handleTransactionTextChange}
|
||
disabled={isBooking || isFullyBooked}
|
||
helperText="Wird als Transaktionstext in das Ledger übernommen"
|
||
/>
|
||
</Box>
|
||
|
||
{isFullyBooked ? (
|
||
<Alert severity="success" icon={<CheckCircleOutlineOutlinedIcon />}>
|
||
Alle Artikel dieser Lieferung sind bereits vollständig im Lager verbucht.
|
||
</Alert>
|
||
) : totalToBook > 0 ? (
|
||
<Alert severity={selectedWarehouseId ? 'info' : 'warning'}>
|
||
{selectedWarehouseId ? (
|
||
<>
|
||
Es werden <strong>{totalToBook} Stück</strong> für{' '}
|
||
<strong>{countLinesToBook} Position{countLinesToBook === 1 ? '' : 'en'}</strong> als Lagerzugang (DELIVERY) in das Lager{' '}
|
||
<strong>„{selectedWarehouse?.name || '–'}“</strong> gebucht.
|
||
</>
|
||
) : (
|
||
<>
|
||
Bitte wählen Sie ein <strong>Ziellager</strong> aus, um die Buchung von{' '}
|
||
<strong>{totalToBook} Stück</strong> durchzuführen.
|
||
</>
|
||
)}
|
||
</Alert>
|
||
) : (
|
||
<Alert severity="warning">
|
||
Wählen Sie mindestens eine Menge größer als 0 aus, um den Lagerzugang zu buchen.
|
||
</Alert>
|
||
)}
|
||
</>
|
||
)}
|
||
</DialogContent>
|
||
|
||
<DialogActions sx={{ px: 3, py: 2 }}>
|
||
<Button onClick={onClose} disabled={isBooking} color="inherit">
|
||
{isFullyBooked ? 'Schließen' : 'Abbrechen'}
|
||
</Button>
|
||
|
||
{!isFullyBooked && lines.length > 0 && (
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
onClick={this.handleConfirmBook}
|
||
disabled={isBooking || totalToBook <= 0 || loading || isWarehouseMissing}
|
||
startIcon={
|
||
isBooking ? (
|
||
<CircularProgress size={16} color="inherit" />
|
||
) : (
|
||
<Inventory2OutlinedIcon />
|
||
)
|
||
}
|
||
sx={{ minWidth: 160, fontWeight: 600 }}
|
||
>
|
||
{isBooking ? 'Wird gebucht...' : 'Jetzt ins Lager buchen'}
|
||
</Button>
|
||
)}
|
||
</DialogActions>
|
||
</Dialog>
|
||
);
|
||
}
|
||
}
|