u
This commit is contained in:
@@ -131,7 +131,7 @@ Strip supplierName?: str from T11 (resolve via entityId in cached supplier list)
|
|||||||
|
|
||||||
# Dash API Routes Overview (All)
|
# Dash API Routes Overview (All)
|
||||||
|
|
||||||
Overview of all 263 API endpoints across all modules.
|
Overview of all 264 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -784,7 +784,7 @@ _none_
|
|||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { shipments: T3[] }`
|
- Returns: `200 { shipments: T3[] }`
|
||||||
#### Create shipment for an order — `POST /order/{id}/shipment`
|
#### Create shipment for an order — `POST /order/{id}/shipment`
|
||||||
- Params: `id: int, body?: { date?: str, externalNumber?: str, lines?: { sku: str, amount?: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[] }`
|
- Params: `id: int, body: { lines: { sku: str, amount?: num, name?: str, pu?: num, net?: num, rebate?: num, taxrate?: num, comment?: str }[], date?: str, warehouseId?: int, externalNumber?: str }`
|
||||||
- Returns: `201 { shipment: T3 }`
|
- Returns: `201 { shipment: T3 }`
|
||||||
#### List shippings of an order — `GET /order/{id}/shipping`
|
#### List shippings of an order — `GET /order/{id}/shipping`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
@@ -936,6 +936,9 @@ _none_
|
|||||||
|
|
||||||
### Section: /shipment
|
### Section: /shipment
|
||||||
|
|
||||||
|
#### List shipments — `GET /shipment`
|
||||||
|
- Params: `query: offset?: int, limit?: int, search?: str, state?: str, seekMonths?: int`
|
||||||
|
- Returns: `200 { shipments: { id: int, number: str, type: str, date: str, positions: int, totalQuantity: num, packageCount: int, state: str, entityId?: int|null, externalNumber?: str|null, createdAt?: str|null, orderId?: int|null, orderNumber?: str|null, customerName?: str|null, carrier?: str|null, trackingCode?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Delete shipment — `DELETE /shipment/{id}`
|
#### Delete shipment — `DELETE /shipment/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import { enqueueSnackbar } from 'notistack';
|
|||||||
import AppDialog from '../AppDialog';
|
import AppDialog from '../AppDialog';
|
||||||
import { order as orderApi } from '../../api';
|
import { order as orderApi } from '../../api';
|
||||||
import { notifyError } from '../../utils/notify';
|
import { notifyError } from '../../utils/notify';
|
||||||
|
import { formatStock } from '../article/searchFormat';
|
||||||
|
import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
|
||||||
import { todayInputValue } from './manifestConfig';
|
import { todayInputValue } from './manifestConfig';
|
||||||
|
|
||||||
// MARK: helper to sum already shipped quantities per SKU across order shipments
|
// 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.handleToggleAll = this.handleToggleAll.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
this.handleShipAll = this.handleShipAll.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) {
|
componentDidUpdate(prevProps) {
|
||||||
@@ -93,6 +112,18 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
saving: false,
|
saving: false,
|
||||||
error: null,
|
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
|
// #endregion
|
||||||
|
|
||||||
render() {
|
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 { date, externalNumber, lineAmounts, selectedSkus, saving, error } = this.state;
|
||||||
const nextShipmentNumber = getNextShipmentNumber(orderNumber, shipments);
|
const nextShipmentNumber = getNextShipmentNumber(orderNumber, shipments);
|
||||||
|
|
||||||
const shippedBySku = computeShippedBySku(shipments);
|
const shippedBySku = computeShippedBySku(shipments);
|
||||||
const activeLines = lines.filter((l) => Boolean(l.sku));
|
const activeLines = lines.filter((l) => Boolean(l.sku));
|
||||||
|
const currentOrderId = orderId != null ? Number(orderId) : null;
|
||||||
|
|
||||||
const items = activeLines.map((line) => {
|
const items = activeLines.map((line) => {
|
||||||
const ordered = Number(line.amount) || 0;
|
const ordered = Number(line.amount) || 0;
|
||||||
@@ -257,6 +289,40 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
const remaining = Math.max(0, ordered - shipped);
|
const remaining = Math.max(0, ordered - shipped);
|
||||||
const isSelected = selectedSkus.has(line.sku);
|
const isSelected = selectedSkus.has(line.sku);
|
||||||
const shipAmount = lineAmounts[line.sku] ?? (remaining > 0 ? String(remaining) : '0');
|
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 {
|
return {
|
||||||
...line,
|
...line,
|
||||||
ordered,
|
ordered,
|
||||||
@@ -264,6 +330,13 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
remaining,
|
remaining,
|
||||||
isSelected,
|
isSelected,
|
||||||
shipAmount,
|
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;
|
const selectedCount = openItems.filter((it) => it.isSelected).length;
|
||||||
|
|
||||||
return (
|
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' }}>
|
<DialogTitle sx={{ m: 0, p: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}>
|
<Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}>
|
||||||
<LocalShippingOutlinedIcon color="primary" />
|
<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 }}>Bestellt</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Geliefert</TableCell>
|
<TableCell align="right" sx={{ fontWeight: 600 }}>Geliefert</TableCell>
|
||||||
<TableCell align="right" sx={{ fontWeight: 600 }}>Offen</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>
|
<TableCell align="right" sx={{ fontWeight: 600, width: 130 }}>Liefermenge</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{items.length === 0 ? (
|
{items.length === 0 ? (
|
||||||
<TableRow>
|
<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.
|
Keine Artikel im Auftrag vorhanden.
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -411,6 +486,38 @@ export default class OrderShipmentDialog extends PureComponent {
|
|||||||
>
|
>
|
||||||
{item.remaining}
|
{item.remaining}
|
||||||
</TableCell>
|
</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">
|
<TableCell align="right">
|
||||||
{isClosed ? (
|
{isClosed ? (
|
||||||
<Chip size="small" label="Geliefert" color="success" variant="outlined" sx={{ height: 22, fontSize: '0.7rem' }} />
|
<Chip size="small" label="Geliefert" color="success" variant="outlined" sx={{ height: 22, fontSize: '0.7rem' }} />
|
||||||
|
|||||||
Reference in New Issue
Block a user