This commit is contained in:
seb
2026-10-16 23:53:09 +02:00
parent 82db75ac0f
commit b3a81efe46
8 changed files with 2099 additions and 443 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -64,7 +64,20 @@ export {
buildArticleIndex, buildArticleIndex,
selectArticleIndexItems, selectArticleIndexItems,
} from './articleIndexAutomat'; } from './articleIndexAutomat';
export { default as deliveryAutomat } from './deliveryAutomat'; export {
default as deliveryAutomat,
CHUNK_SIZE as DELIVERY_CHUNK_SIZE,
MAX_LRU_CHUNKS as DELIVERY_MAX_LRU_CHUNKS,
selectDeliveries,
selectDeliveryTotal,
selectDeliveryUnfilteredTotal,
selectDeliveryPagination,
selectOnlyUnbooked,
isDeliveryBooked,
getDeliveryUnbookedCount,
getChunkStats as getDeliveryChunkStats,
subscribeChunkStats as subscribeDeliveryChunkStats,
} from './deliveryAutomat';
export { default as quoteAutomat } from './quoteAutomat'; export { default as quoteAutomat } from './quoteAutomat';
export { export {
default as orderAutomat, default as orderAutomat,

View File

@@ -32,6 +32,7 @@ import warehouseAutomat from './warehouseAutomat';
import attributeAutomat from './attributeAutomat'; import attributeAutomat from './attributeAutomat';
import propertyAutomat from './propertyAutomat'; import propertyAutomat from './propertyAutomat';
import supplierAutomat from './supplierAutomat'; import supplierAutomat from './supplierAutomat';
import deliveryAutomat from './deliveryAutomat';
import orderAutomat from './orderAutomat'; import orderAutomat from './orderAutomat';
import quoteAutomat from './quoteAutomat'; import quoteAutomat from './quoteAutomat';
import shipmentAutomat from './shipmentAutomat'; import shipmentAutomat from './shipmentAutomat';
@@ -264,6 +265,12 @@ function connect() {
}); });
}); });
addTrackedListener(es, 'delivery', () => {
void deliveryAutomat.actions.reload().catch((err) => {
console.error('[sse] delivery refresh failed', err);
});
});

View File

@@ -47,7 +47,6 @@ export default class BookDeliveryDialog extends PureComponent {
let initialAmounts = {}; let initialAmounts = {};
if (Array.isArray(props.summary?.lines)) { if (Array.isArray(props.summary?.lines)) {
for (const line of props.summary.lines) { for (const line of props.summary.lines) {
if (!line.trackStock) continue;
if (props.prefillLineId != null) { if (props.prefillLineId != null) {
initialAmounts[line.id] = initialAmounts[line.id] =
line.id === props.prefillLineId line.id === props.prefillLineId
@@ -175,7 +174,6 @@ export default class BookDeliveryDialog extends PureComponent {
const amounts = {}; const amounts = {};
if (Array.isArray(summary?.lines)) { if (Array.isArray(summary?.lines)) {
for (const line of summary.lines) { for (const line of summary.lines) {
if (!line.trackStock) continue;
if (prefillLineId != null) { if (prefillLineId != null) {
amounts[line.id] = amounts[line.id] =
line.id === prefillLineId line.id === prefillLineId
@@ -210,7 +208,6 @@ export default class BookDeliveryDialog extends PureComponent {
if (!summary?.lines) return; if (!summary?.lines) return;
const amounts = {}; const amounts = {};
for (const line of summary.lines) { for (const line of summary.lines) {
if (!line.trackStock) continue;
amounts[line.id] = line.remainingAmount > 0 ? line.remainingAmount : 0; amounts[line.id] = line.remainingAmount > 0 ? line.remainingAmount : 0;
} }
this.setState({ amountsToBook: amounts }); this.setState({ amountsToBook: amounts });
@@ -221,7 +218,6 @@ export default class BookDeliveryDialog extends PureComponent {
if (!summary?.lines) return; if (!summary?.lines) return;
const amounts = {}; const amounts = {};
for (const line of summary.lines) { for (const line of summary.lines) {
if (!line.trackStock) continue;
amounts[line.id] = 0; amounts[line.id] = 0;
} }
this.setState({ amountsToBook: amounts }); this.setState({ amountsToBook: amounts });
@@ -229,16 +225,16 @@ export default class BookDeliveryDialog extends PureComponent {
async handleConfirmBook() { async handleConfirmBook() {
const { delivery, onBookSuccess, onClose } = this.props; const { delivery, onBookSuccess, onClose } = this.props;
const { summary, amountsToBook, transactionText } = this.state; const { summary, amountsToBook, transactionText, selectedWarehouseId } = this.state;
if (!summary?.lines) return; if (!summary?.lines) return;
const linesToBook = summary.lines const linesToBook = summary.lines
.filter((line) => line.trackStock)
.map((line) => ({ .map((line) => ({
manifestarticleId: line.id, manifestarticleId: line.id,
sku: line.sku, sku: line.sku,
name: line.name, name: line.name,
amount: Number(amountsToBook[line.id] || 0), amount: Number(amountsToBook[line.id] || 0),
trackStock: Boolean(line.trackStock),
})) }))
.filter((l) => l.amount > 0); .filter((l) => l.amount > 0);
@@ -247,8 +243,8 @@ export default class BookDeliveryDialog extends PureComponent {
return; return;
} }
const { selectedWarehouseId } = this.state; const hasTracked = linesToBook.some((l) => l.trackStock);
if (!selectedWarehouseId) { if (hasTracked && !selectedWarehouseId) {
this.setState({ warehouseError: 'Bitte wählen Sie ein Ziellager aus.' }); this.setState({ warehouseError: 'Bitte wählen Sie ein Ziellager aus.' });
enqueueSnackbar('Bitte wählen Sie ein Ziellager aus.', { variant: 'warning' }); enqueueSnackbar('Bitte wählen Sie ein Ziellager aus.', { variant: 'warning' });
return; return;
@@ -265,7 +261,7 @@ export default class BookDeliveryDialog extends PureComponent {
const totalBookedQty = linesToBook.reduce((sum, l) => sum + l.amount, 0); const totalBookedQty = linesToBook.reduce((sum, l) => sum + l.amount, 0);
enqueueSnackbar( enqueueSnackbar(
`${linesToBook.length} Position${linesToBook.length === 1 ? '' : 'en'} (${totalBookedQty} Stk.) erfolgreich ins Lager gebucht.`, `${linesToBook.length} Position${linesToBook.length === 1 ? '' : 'en'} (${totalBookedQty} Stk.) erfolgreich gebucht.`,
{ variant: 'success' } { variant: 'success' }
); );
@@ -387,12 +383,12 @@ export default class BookDeliveryDialog extends PureComponent {
renderLinesTable() { renderLinesTable() {
const { summary, amountsToBook, isBooking } = this.state; const { summary, amountsToBook, isBooking } = this.state;
const lines = (summary?.lines || []).filter((l) => l.trackStock); const lines = summary?.lines || [];
if (lines.length === 0) { if (lines.length === 0) {
return ( return (
<Alert severity="info" sx={{ my: 2 }}> <Alert severity="info" sx={{ my: 2 }}>
Diese Lieferung enthält keine lagergeführten Artikel zum Einbuchen. Diese Lieferung enthält keine Positionen zum Verbuchen.
</Alert> </Alert>
); );
} }
@@ -406,7 +402,7 @@ export default class BookDeliveryDialog extends PureComponent {
<TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell> <TableCell sx={{ fontWeight: 600 }}>Artikel</TableCell>
<TableCell align="right" sx={{ fontWeight: 600 }}>Liefermenge</TableCell> <TableCell align="right" sx={{ fontWeight: 600 }}>Liefermenge</TableCell>
<TableCell align="right" sx={{ fontWeight: 600 }}>Bereits gebucht</TableCell> <TableCell align="right" sx={{ fontWeight: 600 }}>Bereits gebucht</TableCell>
<TableCell align="right" sx={{ fontWeight: 600, width: 140 }}>Jetzt einbuchen</TableCell> <TableCell align="right" sx={{ fontWeight: 600, width: 140 }}>Jetzt verbuchen</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@@ -420,9 +416,19 @@ export default class BookDeliveryDialog extends PureComponent {
{line.sku || '–'} {line.sku || '–'}
</TableCell> </TableCell>
<TableCell sx={{ maxWidth: 280 }}> <TableCell sx={{ maxWidth: 280 }}>
<Typography variant="body2" sx={{ fontWeight: 500 }} noWrap> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{line.name || '–'} <Typography variant="body2" sx={{ fontWeight: 500 }} noWrap>
</Typography> {line.name || '–'}
</Typography>
{!line.trackStock && (
<Chip
size="small"
label="Ohne Lagerbestand"
variant="outlined"
sx={{ fontSize: '0.7rem', height: 20, color: 'text.secondary', borderColor: 'divider' }}
/>
)}
</Box>
</TableCell> </TableCell>
<TableCell align="right"> <TableCell align="right">
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600 }}>
@@ -493,8 +499,14 @@ export default class BookDeliveryDialog extends PureComponent {
warehouseError, warehouseError,
} = this.state; } = this.state;
const lines = (summary?.lines || []).filter((l) => l.trackStock); const lines = summary?.lines || [];
const hasTrackStock = lines.length > 0; const hasLines = lines.length > 0;
const hasTrackStock = lines.some((l) => l.trackStock);
const linesWithAmountsToBook = lines.filter(
(l) => Number(amountsToBook[l.id]) > 0
);
const hasTrackedLinesToBook = linesWithAmountsToBook.some((l) => l.trackStock);
const prefillLine = const prefillLine =
this.props.prefillLineId != null this.props.prefillLineId != null
? lines.find((l) => l.id === this.props.prefillLineId) ? lines.find((l) => l.id === this.props.prefillLineId)
@@ -503,12 +515,10 @@ export default class BookDeliveryDialog extends PureComponent {
(sum, l) => sum + (Number(amountsToBook[l.id]) || 0), (sum, l) => sum + (Number(amountsToBook[l.id]) || 0),
0 0
); );
const countLinesToBook = lines.filter( const countLinesToBook = linesWithAmountsToBook.length;
(l) => Number(amountsToBook[l.id]) > 0 const isFullyBooked = hasLines && summary?.status === 'booked';
).length;
const isFullyBooked = hasTrackStock && summary?.status === 'booked';
const selectedWarehouse = warehouses.find((w) => w.id === selectedWarehouseId); const selectedWarehouse = warehouses.find((w) => w.id === selectedWarehouseId);
const isWarehouseMissing = !selectedWarehouseId; const isWarehouseMissing = hasTrackedLinesToBook && !selectedWarehouseId;
return ( return (
<AppDialog <AppDialog
@@ -528,7 +538,7 @@ export default class BookDeliveryDialog extends PureComponent {
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Inventory2OutlinedIcon color="primary" /> <Inventory2OutlinedIcon color="primary" />
<Typography variant="h6" component="span" sx={{ fontWeight: 600 }}> <Typography variant="h6" component="span" sx={{ fontWeight: 600 }}>
Lieferung {delivery?.number || ''} ins Lager buchen Lieferung {delivery?.number || ''} verbuchen
</Typography> </Typography>
</Box> </Box>
<IconButton size="small" onClick={onClose} disabled={isBooking}> <IconButton size="small" onClick={onClose} disabled={isBooking}>
@@ -552,13 +562,13 @@ export default class BookDeliveryDialog extends PureComponent {
<> <>
{prefillLine && ( {prefillLine && (
<Alert severity="info" sx={{ mb: 2 }}> <Alert severity="info" sx={{ mb: 2 }}>
Vorausgewählt zum Einbuchen: Position{' '} Vorausgewählt zum Verbuchen: Position{' '}
<strong>{prefillLine.sku ? `SKU ${prefillLine.sku}` : prefillLine.name}</strong> ({amountsToBook[prefillLine.id] || 0} Stk.). Andere Positionen stehen auf 0. <strong>{prefillLine.sku ? `SKU ${prefillLine.sku}` : prefillLine.name}</strong> ({amountsToBook[prefillLine.id] || 0} Stk.). Andere Positionen stehen auf 0.
</Alert> </Alert>
)} )}
{this.renderLinesTable()} {this.renderLinesTable()}
{!isFullyBooked && hasTrackStock && ( {!isFullyBooked && hasLines && (
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}> <Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<Button <Button
size="small" size="small"
@@ -580,13 +590,13 @@ export default class BookDeliveryDialog extends PureComponent {
</Box> </Box>
)} )}
{hasTrackStock && warehouses.length === 0 && !isFullyBooked && ( {hasTrackedLinesToBook && warehouses.length === 0 && !isFullyBooked && (
<Alert severity="error" sx={{ mb: 2 }}> <Alert severity="error" sx={{ mb: 2 }}>
Es ist noch kein Lager eingerichtet. Bitte legen Sie unter „Artikel → Lager“ zuerst ein Lager an. Es ist noch kein Lager eingerichtet. Für lagergeführte Artikel legen Sie bitte unter „Artikel → Lager“ zuerst ein Lager an.
</Alert> </Alert>
)} )}
{hasTrackStock && ( {hasLines && (
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@@ -595,44 +605,52 @@ export default class BookDeliveryDialog extends PureComponent {
flexDirection: { xs: 'column', sm: 'row' }, flexDirection: { xs: 'column', sm: 'row' },
}} }}
> >
<FormControl {hasTrackStock && (
size="small" <FormControl
required size="small"
error={Boolean(warehouseError)} required={hasTrackedLinesToBook}
disabled={isBooking || isFullyBooked} error={Boolean(warehouseError)}
sx={{ minWidth: { sm: 240 }, flex: { sm: '0 0 auto' } }} 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 ? ( <InputLabel id="warehouse-select-label">
<MenuItem value="" disabled> Ziellager {hasTrackedLinesToBook ? '*' : ''}
Keine Lager vorhanden </InputLabel>
</MenuItem> <Select
) : ( labelId="warehouse-select-label"
warehouses.map((wh) => ( id="warehouse-select"
<MenuItem key={wh.id} value={wh.id}> value={selectedWarehouseId ?? ''}
{wh.name} label={`Ziellager ${hasTrackedLinesToBook ? '*' : ''}`}
onChange={this.handleWarehouseChange}
startAdornment={
<WarehouseIcon
sx={{ mr: 1, color: 'text.secondary', fontSize: 20 }}
/>
}
>
{warehouses.length === 0 ? (
<MenuItem value="" disabled>
Keine Lager vorhanden
</MenuItem> </MenuItem>
)) ) : (
warehouses.map((wh) => (
<MenuItem key={wh.id} value={wh.id}>
{wh.name}
</MenuItem>
))
)}
</Select>
{warehouseError ? (
<FormHelperText>{warehouseError}</FormHelperText>
) : (
<FormHelperText>
{hasTrackedLinesToBook
? 'Pflichtfeld: Ziellager für lagergeführte Artikel'
: 'Ziellager (optional)'}
</FormHelperText>
)} )}
</Select> </FormControl>
{warehouseError ? ( )}
<FormHelperText>{warehouseError}</FormHelperText>
) : (
<FormHelperText>Pflichtfeld: Ziellager für die Buchung</FormHelperText>
)}
</FormControl>
<TextField <TextField
fullWidth fullWidth
@@ -648,26 +666,29 @@ export default class BookDeliveryDialog extends PureComponent {
{isFullyBooked ? ( {isFullyBooked ? (
<Alert severity="success" icon={<CheckCircleOutlineOutlinedIcon />}> <Alert severity="success" icon={<CheckCircleOutlineOutlinedIcon />}>
Alle Artikel dieser Lieferung sind bereits vollständig im Lager verbucht. Alle Positionen dieser Lieferung sind bereits vollständig im Ledger verbucht.
</Alert> </Alert>
) : hasTrackStock && totalToBook > 0 ? ( ) : countLinesToBook > 0 ? (
<Alert severity={selectedWarehouseId ? 'info' : 'warning'}> <Alert severity={isWarehouseMissing ? 'warning' : 'info'}>
{selectedWarehouseId ? ( {isWarehouseMissing ? (
<> <>
Es werden <strong>{totalToBook} Stück</strong> für{' '} Bitte wählen Sie ein <strong>Ziellager</strong> aus, um die lagergeführten Artikel zu buchen.
<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{' '} Es werden <strong>{totalToBook} Stück</strong> für{' '}
<strong>{totalToBook} Stück</strong> durchzuführen. <strong>{countLinesToBook} Position{countLinesToBook === 1 ? '' : 'en'}</strong>{' '}
{hasTrackedLinesToBook && selectedWarehouse ? (
<>in das Lager <strong>„{selectedWarehouse?.name || '–'}“</strong> verbucht.</>
) : (
<>als Dokumentenbuchung im Ledger verbucht (ohne Lagerbestandsführung).</>
)}
</> </>
)} )}
</Alert> </Alert>
) : hasTrackStock ? ( ) : hasLines ? (
<Alert severity="warning"> <Alert severity="warning">
Wählen Sie mindestens eine Menge größer als 0 aus, um den Lagerzugang zu buchen. Wählen Sie mindestens eine Menge größer als 0 aus, um Positionen zu verbuchen.
</Alert> </Alert>
) : null} ) : null}
</> </>
@@ -679,7 +700,7 @@ export default class BookDeliveryDialog extends PureComponent {
{isFullyBooked ? 'Schließen' : 'Abbrechen'} {isFullyBooked ? 'Schließen' : 'Abbrechen'}
</Button> </Button>
{!isFullyBooked && lines.length > 0 && ( {!isFullyBooked && hasLines && (
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
@@ -694,7 +715,7 @@ export default class BookDeliveryDialog extends PureComponent {
} }
sx={{ minWidth: 160, fontWeight: 600 }} sx={{ minWidth: 160, fontWeight: 600 }}
> >
{isBooking ? 'Wird gebucht...' : 'Jetzt ins Lager buchen'} {isBooking ? 'Wird gebucht...' : 'Jetzt verbuchen'}
</Button> </Button>
)} )}
</DialogActions> </DialogActions>

File diff suppressed because it is too large Load Diff

View File

@@ -40,17 +40,20 @@ import orderAutomat, {
} from '../automats/orderAutomat'; } from '../automats/orderAutomat';
import quoteAutomat from '../automats/quoteAutomat'; import quoteAutomat from '../automats/quoteAutomat';
import shipmentAutomat from '../automats/shipmentAutomat'; import shipmentAutomat from '../automats/shipmentAutomat';
import deliveryAutomat from '../automats/deliveryAutomat';
import { formatPrice } from '../utils/price'; import { formatPrice } from '../utils/price';
function getAutomatFor(target) { function getAutomatFor(target) {
if (target === 'quote') return quoteAutomat; if (target === 'quote') return quoteAutomat;
if (target === 'shipment') return shipmentAutomat; if (target === 'shipment') return shipmentAutomat;
if (target === 'delivery') return deliveryAutomat;
return orderAutomat; return orderAutomat;
} }
function getInitialTarget() { function getInitialTarget() {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const p = window.location.pathname; const p = window.location.pathname;
if (p.startsWith('/lieferung')) return 'delivery';
if (p.startsWith('/auftrag/ausliefern')) return 'shipment'; if (p.startsWith('/auftrag/ausliefern')) return 'shipment';
if (p.startsWith('/auftrag/bestellung')) return 'order'; if (p.startsWith('/auftrag/bestellung')) return 'order';
if (p.startsWith('/auftrag')) return 'quote'; if (p.startsWith('/auftrag')) return 'quote';
@@ -296,6 +299,10 @@ class ChunkCacheMonitor extends PureComponent {
if (p.onlyUnpaid) automat.actions.setOnlyUnpaid(true); if (p.onlyUnpaid) automat.actions.setOnlyUnpaid(true);
if (p.onlyUndelivered) automat.actions.setOnlyUndelivered(true); if (p.onlyUndelivered) automat.actions.setOnlyUndelivered(true);
} }
if (this.state.target === 'delivery') {
if (p.onlyUnbooked) automat.actions.setOnlyUnbooked(true);
if (p.supplierId) automat.actions.setSupplierId(p.supplierId);
}
if (this.state.target === 'shipment') { if (this.state.target === 'shipment') {
if (p.stateFilter) automat.actions.setStateFilter(p.stateFilter); if (p.stateFilter) automat.actions.setStateFilter(p.stateFilter);
} }
@@ -723,11 +730,15 @@ class ChunkCacheMonitor extends PureComponent {
{chunkOrders.map(({ item: order, index }) => { {chunkOrders.map(({ item: order, index }) => {
const isOrd = this.state.target === 'order'; const isOrd = this.state.target === 'order';
const isShip = this.state.target === 'shipment'; const isShip = this.state.target === 'shipment';
const isDeliv = this.state.target === 'delivery';
const paid = isOrd ? isOrderPaid(order) : false; const paid = isOrd ? isOrderPaid(order) : false;
const delivered = isOrd ? isOrderDelivered(order) : false; const delivered = isOrd ? isOrderDelivered(order) : false;
const gross = order.grossTotal != null ? Number(order.grossTotal) : (order.lines ? orderGrossTotal(order.lines) : 0); const gross = order.grossTotal != null ? Number(order.grossTotal) : (order.lines ? orderGrossTotal(order.lines) : 0);
const customer = order.customerName || (order.entityId ? `Kunde #${order.entityId}` : '–'); const customer = isDeliv
? (order.supplierName || (order.supplierId ? `Lieferant #${order.supplierId}` : '–'))
: (order.customerName || (order.entityId ? `Kunde #${order.entityId}` : '–'));
const posCount = order.positions != null ? order.positions : (order.lines?.length ?? 0); const posCount = order.positions != null ? order.positions : (order.lines?.length ?? 0);
const unbooked = order.ledger?.unbookedPositions ?? 0;
return ( return (
<Box <Box
@@ -780,6 +791,14 @@ class ChunkCacheMonitor extends PureComponent {
/> />
</> </>
)} )}
{isDeliv && (
<Chip
size="small"
label={unbooked === 0 ? 'Gebucht' : `${unbooked} Ungebucht`}
color={unbooked === 0 ? 'success' : 'warning'}
sx={{ height: 20, fontSize: '0.7rem' }}
/>
)}
{isShip && ( {isShip && (
<> <>
<Chip <Chip
@@ -1072,6 +1091,14 @@ class ChunkCacheMonitor extends PureComponent {
onClick={() => this.switchTarget('order')} onClick={() => this.switchTarget('order')}
sx={{ cursor: 'pointer', height: 24, fontSize: '0.72rem', fontWeight: this.state.target === 'order' ? 700 : 500 }} sx={{ cursor: 'pointer', height: 24, fontSize: '0.72rem', fontWeight: this.state.target === 'order' ? 700 : 500 }}
/> />
<Chip
size="small"
color={this.state.target === 'delivery' ? 'primary' : 'default'}
variant={this.state.target === 'delivery' ? 'filled' : 'outlined'}
label="Lieferungen (deliveryAutomat)"
onClick={() => this.switchTarget('delivery')}
sx={{ cursor: 'pointer', height: 24, fontSize: '0.72rem', fontWeight: this.state.target === 'delivery' ? 700 : 500 }}
/>
<Chip <Chip
size="small" size="small"
color={this.state.target === 'shipment' ? 'primary' : 'default'} color={this.state.target === 'shipment' ? 'primary' : 'default'}

View File

@@ -124,8 +124,8 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def
}); });
const bookedAmount = deliveryEntries.reduce((sum, e) => sum + Number(e.amount || 0), 0); const bookedAmount = deliveryEntries.reduce((sum, e) => sum + Number(e.amount || 0), 0);
const targetAmount = Number(line.amount || 0); const targetAmount = Number(line.amount || 0);
const remainingAmount = trackStock ? Math.max(0, targetAmount - bookedAmount) : 0; const remainingAmount = Math.max(0, targetAmount - bookedAmount);
const isBooked = trackStock ? (targetAmount > 0 ? bookedAmount >= targetAmount : bookedAmount > 0) : true; const isBooked = targetAmount > 0 ? bookedAmount >= targetAmount : bookedAmount > 0;
return { return {
...line, ...line,
@@ -134,21 +134,20 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def
remainingAmount, remainingAmount,
isBooked, isBooked,
trackStock, trackStock,
isBookable: trackStock, isBookable: true,
ledgerEntries: deliveryEntries, ledgerEntries: deliveryEntries,
}; };
}) })
); );
const stockLines = linesWithLedger.filter((l) => l.trackStock); const totalPositions = linesWithLedger.length;
const totalPositions = stockLines.length; const bookedPositions = linesWithLedger.filter((l) => l.isBooked).length;
const bookedPositions = stockLines.filter((l) => l.isBooked).length; const totalQuantity = linesWithLedger.reduce((sum, l) => sum + l.targetAmount, 0);
const totalQuantity = stockLines.reduce((sum, l) => sum + l.targetAmount, 0); const bookedQuantity = linesWithLedger.reduce((sum, l) => sum + l.bookedAmount, 0);
const bookedQuantity = stockLines.reduce((sum, l) => sum + l.bookedAmount, 0); const hasTrackStock = linesWithLedger.some((l) => l.trackStock);
const hasTrackStock = stockLines.length > 0;
let status; let status;
if (!hasTrackStock) { if (totalPositions === 0) {
status = 'empty'; status = 'empty';
} else if (bookedPositions === totalPositions && bookedQuantity >= totalQuantity) { } else if (bookedPositions === totalPositions && bookedQuantity >= totalQuantity) {
status = 'booked'; status = 'booked';
@@ -176,8 +175,8 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def
* *
* @param {Object} params * @param {Object} params
* @param {Object} params.delivery - The delivery object (used for transaction memo and fallback) * @param {Object} params.delivery - The delivery object (used for transaction memo and fallback)
* @param {Array<{ manifestarticleId?: number, id?: number, amount: number, sku?: string }>} params.linesToBook * @param {Array<{ manifestarticleId?: number, id?: number, amount: number, sku?: string, trackStock?: boolean }>} params.linesToBook
* @param {number|string} params.warehouseId - Mandatory warehouse ID * @param {number|string} [params.warehouseId] - Warehouse ID (mandatory only when at least one line tracks stock)
* @param {Object} [params.stockApi] * @param {Object} [params.stockApi]
* @param {string} [params.transactionText] * @param {string} [params.transactionText]
* @returns {Promise<Array<Object>>} * @returns {Promise<Array<Object>>}
@@ -189,15 +188,17 @@ export async function bookDeliveryLines({
stockApi = defaultStockApi, stockApi = defaultStockApi,
transactionText, transactionText,
}) { }) {
const numericWarehouseId = Number(warehouseId);
if (!numericWarehouseId || numericWarehouseId <= 0) {
throw new Error('Bitte wählen Sie ein Lager aus.');
}
if (!Array.isArray(linesToBook) || linesToBook.length === 0) { if (!Array.isArray(linesToBook) || linesToBook.length === 0) {
return []; return [];
} }
const numericWarehouseId = Number(warehouseId) || null;
const hasTrackedLines = linesToBook.some((l) => l.trackStock);
if (hasTrackedLines && (!numericWarehouseId || numericWarehouseId <= 0)) {
throw new Error('Bitte wählen Sie ein Lager aus.');
}
const memo = String( const memo = String(
transactionText != null && transactionText !== '' transactionText != null && transactionText !== ''
? transactionText ? transactionText

View File

@@ -136,7 +136,9 @@ export function getMaxDocumentNumber(kind = 'quote') {
const prefixStyle = refSeq?.prefix ?? ''; const prefixStyle = refSeq?.prefix ?? '';
let prefix = ''; let prefix = '';
if ( if (kind === 'delivery') {
prefix = prefixStyle.startsWith('DEL-') ? 'DEL-' : (prefixStyle.startsWith('L-') ? 'L-' : 'LS-');
} else if (
prefixStyle.startsWith('ART-') || prefixStyle.startsWith('ART-') ||
prefixStyle.startsWith('LS-') || prefixStyle.startsWith('LS-') ||
prefixStyle.startsWith('KD-') || prefixStyle.startsWith('KD-') ||