u
This commit is contained in:
@@ -25,6 +25,8 @@ import { enqueueSnackbar } from 'notistack';
|
||||
import AppDialog from '../AppDialog';
|
||||
import { order as orderApi } from '../../api';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
import { formatStock } from '../article/searchFormat';
|
||||
import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
|
||||
import { todayInputValue } from './manifestConfig';
|
||||
|
||||
// MARK: helper to sum already shipped quantities per SKU across order shipments
|
||||
@@ -80,6 +82,23 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
this.handleToggleAll = this.handleToggleAll.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleShipAll = this.handleShipAll.bind(this);
|
||||
|
||||
if (props.open) {
|
||||
this.ensureArticlesLoaded(props.lines);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubArticle = artikelAutomat.subscribe(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
if (this.props.open) {
|
||||
this.ensureArticlesLoaded(this.props.lines);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubArticle?.();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -93,6 +112,18 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
saving: false,
|
||||
error: null,
|
||||
});
|
||||
this.ensureArticlesLoaded(this.props.lines);
|
||||
} else if (this.props.open && prevProps.lines !== this.props.lines) {
|
||||
this.ensureArticlesLoaded(this.props.lines);
|
||||
}
|
||||
}
|
||||
|
||||
ensureArticlesLoaded(lines) {
|
||||
if (!Array.isArray(lines)) return;
|
||||
for (const l of lines) {
|
||||
if (l?.sku && !getArticle(l.sku)) {
|
||||
artikelAutomat.actions.loadArticle(l.sku).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,12 +275,13 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
// #endregion
|
||||
|
||||
render() {
|
||||
const { open, onClose, orderNumber, lines = [], shipments = [] } = this.props;
|
||||
const { open, onClose, orderId, orderNumber, lines = [], shipments = [] } = this.props;
|
||||
const { date, externalNumber, lineAmounts, selectedSkus, saving, error } = this.state;
|
||||
const nextShipmentNumber = getNextShipmentNumber(orderNumber, shipments);
|
||||
|
||||
const shippedBySku = computeShippedBySku(shipments);
|
||||
const activeLines = lines.filter((l) => Boolean(l.sku));
|
||||
const currentOrderId = orderId != null ? Number(orderId) : null;
|
||||
|
||||
const items = activeLines.map((line) => {
|
||||
const ordered = Number(line.amount) || 0;
|
||||
@@ -257,6 +289,40 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
const remaining = Math.max(0, ordered - shipped);
|
||||
const isSelected = selectedSkus.has(line.sku);
|
||||
const shipAmount = lineAmounts[line.sku] ?? (remaining > 0 ? String(remaining) : '0');
|
||||
|
||||
// Stock & reservation details from artikelAutomat
|
||||
const cached = getArticle(line.sku);
|
||||
const rawWarehouses = Array.isArray(cached?.warehouses) ? cached.warehouses : [];
|
||||
const isTracked = cached ? (cached.track_stock ?? cached.trackStock ?? (rawWarehouses.length > 0)) : null;
|
||||
const stockVal = cached == null || isTracked === false
|
||||
? null
|
||||
: (cached.stock != null && !Number.isNaN(Number(cached.stock))
|
||||
? Number(cached.stock)
|
||||
: (rawWarehouses.length > 0 || isTracked
|
||||
? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0)
|
||||
: null));
|
||||
|
||||
const undeliveredOrders = Array.isArray(cached?.undeliveredOrders) ? cached.undeliveredOrders : [];
|
||||
const otherOrders = currentOrderId != null
|
||||
? undeliveredOrders.filter((ord) => Number(ord.orderId) !== currentOrderId)
|
||||
: undeliveredOrders;
|
||||
const reservedInOtherOrders = otherOrders.reduce((sum, ord) => sum + (Number(ord.amount) || 0), 0);
|
||||
const availableVal = cached == null || isTracked === false
|
||||
? null
|
||||
: (stockVal != null ? stockVal - reservedInOtherOrders : null);
|
||||
|
||||
const reservedTitle = isTracked !== false && reservedInOtherOrders > 0
|
||||
? `Bereits in anderen Aufträgen reserviert:\n${otherOrders
|
||||
.map((ord) => `• ${ord.orderNumber || (ord.orderId ? `Auftrag #${ord.orderId}` : 'Auftrag')}: ${formatStock(ord.amount)}${ord.customerName ? ` (${ord.customerName})` : ''}`)
|
||||
.join('\n')}`
|
||||
: undefined;
|
||||
|
||||
const stockTitle = isTracked !== false && rawWarehouses.length > 1
|
||||
? `Lagerbestände:\n${rawWarehouses
|
||||
.map((wh) => `• ${wh.name || 'Lager'}: ${formatStock(wh.stock)}`)
|
||||
.join('\n')}`
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...line,
|
||||
ordered,
|
||||
@@ -264,6 +330,13 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
remaining,
|
||||
isSelected,
|
||||
shipAmount,
|
||||
cached,
|
||||
isTracked,
|
||||
stockVal,
|
||||
availableVal,
|
||||
reservedInOtherOrders,
|
||||
reservedTitle,
|
||||
stockTitle,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -273,7 +346,7 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
const selectedCount = openItems.filter((it) => it.isSelected).length;
|
||||
|
||||
return (
|
||||
<AppDialog open={open} onClose={saving ? undefined : onClose} maxWidth="md" fullWidth>
|
||||
<AppDialog open={open} onClose={saving ? undefined : onClose} maxWidth="lg" fullWidth>
|
||||
<DialogTitle sx={{ m: 0, p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}>
|
||||
<LocalShippingOutlinedIcon color="primary" />
|
||||
@@ -364,13 +437,15 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Bestellt</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Geliefert</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Offen</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Bestand</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Verfügbar</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 600, width: 130 }}>Liefermenge</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{items.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} align="center" sx={{ py: 3, color: 'text.secondary' }}>
|
||||
<TableCell colSpan={9} align="center" sx={{ py: 3, color: 'text.secondary' }}>
|
||||
Keine Artikel im Auftrag vorhanden.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -411,6 +486,38 @@ export default class OrderShipmentDialog extends PureComponent {
|
||||
>
|
||||
{item.remaining}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
title={item.stockTitle}
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
color:
|
||||
!item.cached || item.stockVal == null
|
||||
? 'text.disabled'
|
||||
: item.stockVal <= 0
|
||||
? 'error.main'
|
||||
: 'text.secondary',
|
||||
}}
|
||||
>
|
||||
{!item.cached ? '…' : item.isTracked === false ? '∞' : formatStock(item.stockVal)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
title={item.reservedTitle}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
color:
|
||||
!item.cached || item.availableVal == null
|
||||
? 'text.disabled'
|
||||
: item.availableVal <= 0
|
||||
? 'error.main'
|
||||
: item.remaining > item.availableVal
|
||||
? 'warning.main'
|
||||
: 'text.secondary',
|
||||
}}
|
||||
>
|
||||
{!item.cached ? '…' : item.isTracked === false ? '∞' : formatStock(item.availableVal)}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{isClosed ? (
|
||||
<Chip size="small" label="Geliefert" color="success" variant="outlined" sx={{ height: 22, fontSize: '0.7rem' }} />
|
||||
|
||||
Reference in New Issue
Block a user