Compare commits
2 Commits
82db75ac0f
...
0b0ffb2203
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b0ffb2203 | ||
|
|
b3a81efe46 |
File diff suppressed because it is too large
Load Diff
@@ -64,7 +64,20 @@ export {
|
||||
buildArticleIndex,
|
||||
selectArticleIndexItems,
|
||||
} 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 orderAutomat,
|
||||
|
||||
@@ -32,6 +32,7 @@ import warehouseAutomat from './warehouseAutomat';
|
||||
import attributeAutomat from './attributeAutomat';
|
||||
import propertyAutomat from './propertyAutomat';
|
||||
import supplierAutomat from './supplierAutomat';
|
||||
import deliveryAutomat from './deliveryAutomat';
|
||||
import orderAutomat from './orderAutomat';
|
||||
import quoteAutomat from './quoteAutomat';
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
let initialAmounts = {};
|
||||
if (Array.isArray(props.summary?.lines)) {
|
||||
for (const line of props.summary.lines) {
|
||||
if (!line.trackStock) continue;
|
||||
if (props.prefillLineId != null) {
|
||||
initialAmounts[line.id] =
|
||||
line.id === props.prefillLineId
|
||||
@@ -175,7 +174,6 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
const amounts = {};
|
||||
if (Array.isArray(summary?.lines)) {
|
||||
for (const line of summary.lines) {
|
||||
if (!line.trackStock) continue;
|
||||
if (prefillLineId != null) {
|
||||
amounts[line.id] =
|
||||
line.id === prefillLineId
|
||||
@@ -210,7 +208,6 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
if (!summary?.lines) return;
|
||||
const amounts = {};
|
||||
for (const line of summary.lines) {
|
||||
if (!line.trackStock) continue;
|
||||
amounts[line.id] = line.remainingAmount > 0 ? line.remainingAmount : 0;
|
||||
}
|
||||
this.setState({ amountsToBook: amounts });
|
||||
@@ -221,7 +218,6 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
if (!summary?.lines) return;
|
||||
const amounts = {};
|
||||
for (const line of summary.lines) {
|
||||
if (!line.trackStock) continue;
|
||||
amounts[line.id] = 0;
|
||||
}
|
||||
this.setState({ amountsToBook: amounts });
|
||||
@@ -229,16 +225,16 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
|
||||
async handleConfirmBook() {
|
||||
const { delivery, onBookSuccess, onClose } = this.props;
|
||||
const { summary, amountsToBook, transactionText } = this.state;
|
||||
const { summary, amountsToBook, transactionText, selectedWarehouseId } = this.state;
|
||||
if (!summary?.lines) return;
|
||||
|
||||
const linesToBook = summary.lines
|
||||
.filter((line) => line.trackStock)
|
||||
.map((line) => ({
|
||||
manifestarticleId: line.id,
|
||||
sku: line.sku,
|
||||
name: line.name,
|
||||
amount: Number(amountsToBook[line.id] || 0),
|
||||
trackStock: Boolean(line.trackStock),
|
||||
}))
|
||||
.filter((l) => l.amount > 0);
|
||||
|
||||
@@ -247,8 +243,8 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectedWarehouseId } = this.state;
|
||||
if (!selectedWarehouseId) {
|
||||
const hasTracked = linesToBook.some((l) => l.trackStock);
|
||||
if (hasTracked && !selectedWarehouseId) {
|
||||
this.setState({ warehouseError: 'Bitte wählen Sie ein Ziellager aus.' });
|
||||
enqueueSnackbar('Bitte wählen Sie ein Ziellager aus.', { variant: 'warning' });
|
||||
return;
|
||||
@@ -265,7 +261,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
|
||||
const totalBookedQty = linesToBook.reduce((sum, l) => sum + l.amount, 0);
|
||||
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' }
|
||||
);
|
||||
|
||||
@@ -387,12 +383,12 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
|
||||
renderLinesTable() {
|
||||
const { summary, amountsToBook, isBooking } = this.state;
|
||||
const lines = (summary?.lines || []).filter((l) => l.trackStock);
|
||||
const lines = summary?.lines || [];
|
||||
|
||||
if (lines.length === 0) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -406,7 +402,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
<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>
|
||||
<TableCell align="right" sx={{ fontWeight: 600, width: 140 }}>Jetzt verbuchen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -420,9 +416,19 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
{line.sku || '–'}
|
||||
</TableCell>
|
||||
<TableCell sx={{ maxWidth: 280 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }} noWrap>
|
||||
{line.name || '–'}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }} noWrap>
|
||||
{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 align="right">
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
@@ -493,8 +499,14 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
warehouseError,
|
||||
} = this.state;
|
||||
|
||||
const lines = (summary?.lines || []).filter((l) => l.trackStock);
|
||||
const hasTrackStock = lines.length > 0;
|
||||
const lines = summary?.lines || [];
|
||||
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 =
|
||||
this.props.prefillLineId != null
|
||||
? 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),
|
||||
0
|
||||
);
|
||||
const countLinesToBook = lines.filter(
|
||||
(l) => Number(amountsToBook[l.id]) > 0
|
||||
).length;
|
||||
const isFullyBooked = hasTrackStock && summary?.status === 'booked';
|
||||
const countLinesToBook = linesWithAmountsToBook.length;
|
||||
const isFullyBooked = hasLines && summary?.status === 'booked';
|
||||
const selectedWarehouse = warehouses.find((w) => w.id === selectedWarehouseId);
|
||||
const isWarehouseMissing = !selectedWarehouseId;
|
||||
const isWarehouseMissing = hasTrackedLinesToBook && !selectedWarehouseId;
|
||||
|
||||
return (
|
||||
<AppDialog
|
||||
@@ -528,7 +538,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
<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
|
||||
Lieferung {delivery?.number || ''} verbuchen
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton size="small" onClick={onClose} disabled={isBooking}>
|
||||
@@ -552,13 +562,13 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
<>
|
||||
{prefillLine && (
|
||||
<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.
|
||||
</Alert>
|
||||
)}
|
||||
{this.renderLinesTable()}
|
||||
|
||||
{!isFullyBooked && hasTrackStock && (
|
||||
{!isFullyBooked && hasLines && (
|
||||
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
|
||||
<Button
|
||||
size="small"
|
||||
@@ -580,13 +590,13 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{hasTrackStock && warehouses.length === 0 && !isFullyBooked && (
|
||||
{hasTrackedLinesToBook && 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.
|
||||
Es ist noch kein Lager eingerichtet. Für lagergeführte Artikel legen Sie bitte unter „Artikel → Lager“ zuerst ein Lager an.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasTrackStock && (
|
||||
{hasLines && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -595,44 +605,52 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
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 }}
|
||||
/>
|
||||
}
|
||||
{hasTrackStock && (
|
||||
<FormControl
|
||||
size="small"
|
||||
required={hasTrackedLinesToBook}
|
||||
error={Boolean(warehouseError)}
|
||||
disabled={isBooking || isFullyBooked}
|
||||
sx={{ minWidth: { sm: 240 }, flex: { sm: '0 0 auto' } }}
|
||||
>
|
||||
{warehouses.length === 0 ? (
|
||||
<MenuItem value="" disabled>
|
||||
Keine Lager vorhanden
|
||||
</MenuItem>
|
||||
) : (
|
||||
warehouses.map((wh) => (
|
||||
<MenuItem key={wh.id} value={wh.id}>
|
||||
{wh.name}
|
||||
<InputLabel id="warehouse-select-label">
|
||||
Ziellager {hasTrackedLinesToBook ? '*' : ''}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="warehouse-select-label"
|
||||
id="warehouse-select"
|
||||
value={selectedWarehouseId ?? ''}
|
||||
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>
|
||||
))
|
||||
) : (
|
||||
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>
|
||||
{warehouseError ? (
|
||||
<FormHelperText>{warehouseError}</FormHelperText>
|
||||
) : (
|
||||
<FormHelperText>Pflichtfeld: Ziellager für die Buchung</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
@@ -648,26 +666,29 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
|
||||
{isFullyBooked ? (
|
||||
<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>
|
||||
) : hasTrackStock && totalToBook > 0 ? (
|
||||
<Alert severity={selectedWarehouseId ? 'info' : 'warning'}>
|
||||
{selectedWarehouseId ? (
|
||||
) : countLinesToBook > 0 ? (
|
||||
<Alert severity={isWarehouseMissing ? 'warning' : 'info'}>
|
||||
{isWarehouseMissing ? (
|
||||
<>
|
||||
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 lagergeführten Artikel zu buchen.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Bitte wählen Sie ein <strong>Ziellager</strong> aus, um die Buchung von{' '}
|
||||
<strong>{totalToBook} Stück</strong> durchzuführen.
|
||||
Es werden <strong>{totalToBook} Stück</strong> für{' '}
|
||||
<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>
|
||||
) : hasTrackStock ? (
|
||||
) : hasLines ? (
|
||||
<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>
|
||||
) : null}
|
||||
</>
|
||||
@@ -679,7 +700,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
{isFullyBooked ? 'Schließen' : 'Abbrechen'}
|
||||
</Button>
|
||||
|
||||
{!isFullyBooked && lines.length > 0 && (
|
||||
{!isFullyBooked && hasLines && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
@@ -694,7 +715,7 @@ export default class BookDeliveryDialog extends PureComponent {
|
||||
}
|
||||
sx={{ minWidth: 160, fontWeight: 600 }}
|
||||
>
|
||||
{isBooking ? 'Wird gebucht...' : 'Jetzt ins Lager buchen'}
|
||||
{isBooking ? 'Wird gebucht...' : 'Jetzt verbuchen'}
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -184,21 +184,38 @@ class ManifestFormLine extends PureComponent {
|
||||
|
||||
renderLedgerChip() {
|
||||
const { ledgerLine, onBookLine } = this.props;
|
||||
if (!ledgerLine || !ledgerLine.trackStock) return null;
|
||||
if (!ledgerLine) return null;
|
||||
const isBooked = ledgerLine.isBooked;
|
||||
const isPartial = !isBooked && ledgerLine.bookedAmount > 0;
|
||||
const isClickable = Boolean(onBookLine);
|
||||
const isTracked = Boolean(ledgerLine.trackStock);
|
||||
|
||||
const tooltipTitle = isTracked
|
||||
? isBooked
|
||||
? `Vollständig im Lager gebucht: ${ledgerLine.bookedAmount} Stück (Klicken für Details)`
|
||||
: isPartial
|
||||
? `Teilweise im Lager gebucht: ${ledgerLine.bookedAmount} von ${ledgerLine.targetAmount} Stück (Klicken zum Einbuchen dieser Position)`
|
||||
: `Noch nicht im Lager gebucht (${ledgerLine.targetAmount} Stück – Klicken zum Einbuchen dieser Position)`
|
||||
: isBooked
|
||||
? `Vollständig im Ledger verbucht: ${ledgerLine.bookedAmount} Stück (Klicken für Details)`
|
||||
: isPartial
|
||||
? `Teilweise im Ledger verbucht: ${ledgerLine.bookedAmount} von ${ledgerLine.targetAmount} Stück (Klicken zum Verbuchen dieser Position)`
|
||||
: `Noch nicht im Ledger verbucht (${ledgerLine.targetAmount} Stück – Klicken zum Verbuchen dieser Position)`;
|
||||
|
||||
const label = isTracked
|
||||
? isBooked
|
||||
? `Lager: ${ledgerLine.bookedAmount} Stk. gebucht`
|
||||
: isPartial
|
||||
? `Lager: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.`
|
||||
: 'Lager: offen'
|
||||
: isBooked
|
||||
? `Gebucht: ${ledgerLine.bookedAmount} Stk.`
|
||||
: isPartial
|
||||
? `Gebucht: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.`
|
||||
: 'Verbuchen: offen';
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
isBooked
|
||||
? `Vollständig im Lager gebucht: ${ledgerLine.bookedAmount} Stück (Klicken für Details)`
|
||||
: isPartial
|
||||
? `Teilweise im Lager gebucht: ${ledgerLine.bookedAmount} von ${ledgerLine.targetAmount} Stück (Klicken zum Einbuchen dieser Position)`
|
||||
: `Noch nicht im Lager gebucht (${ledgerLine.targetAmount} Stück – Klicken zum Einbuchen dieser Position)`
|
||||
}
|
||||
>
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<Chip
|
||||
size="small"
|
||||
clickable={isClickable}
|
||||
@@ -212,35 +229,47 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
icon={
|
||||
isBooked ? (
|
||||
<CheckCircleOutlineOutlinedIcon sx={{ '&&': { fontSize: 13 } }} />
|
||||
<CheckCircleOutlineOutlinedIcon sx={{ '&&': { fontSize: 13, color: 'inherit' } }} />
|
||||
) : (
|
||||
<Inventory2OutlinedIcon sx={{ '&&': { fontSize: 13 } }} />
|
||||
<Inventory2OutlinedIcon sx={{ '&&': { fontSize: 13, color: 'inherit' } }} />
|
||||
)
|
||||
}
|
||||
label={
|
||||
isBooked
|
||||
? `Lager: ${ledgerLine.bookedAmount} Stk. gebucht`
|
||||
: isPartial
|
||||
? `Lager: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.`
|
||||
: 'Lager: offen'
|
||||
}
|
||||
color={isBooked ? 'success' : isPartial ? 'warning' : 'default'}
|
||||
variant="outlined"
|
||||
label={label}
|
||||
variant={isBooked ? 'outlined' : 'filled'}
|
||||
sx={{
|
||||
height: 20,
|
||||
fontSize: '0.68rem',
|
||||
fontWeight: 600,
|
||||
height: 22,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 700,
|
||||
cursor: isClickable ? 'pointer' : 'default',
|
||||
bgcolor: isBooked ? 'success.50' : undefined,
|
||||
'&:hover': isClickable
|
||||
...(isBooked
|
||||
? {
|
||||
bgcolor: isBooked
|
||||
? 'success.100'
|
||||
: isPartial
|
||||
? 'warning.100'
|
||||
: 'action.hover',
|
||||
bgcolor: 'rgba(21, 128, 61, 0.08)',
|
||||
color: '#15803d',
|
||||
borderColor: '#15803d',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
'&:hover': isClickable ? { bgcolor: 'rgba(21, 128, 61, 0.16)' } : undefined,
|
||||
}
|
||||
: undefined,
|
||||
: isPartial
|
||||
? {
|
||||
bgcolor: '#d97706',
|
||||
color: '#ffffff',
|
||||
border: 'none',
|
||||
'&:hover': isClickable ? { bgcolor: '#b45309' } : undefined,
|
||||
}
|
||||
: {
|
||||
bgcolor: 'primary.main',
|
||||
color: '#ffffff',
|
||||
border: 'none',
|
||||
'&:hover': isClickable ? { bgcolor: 'primary.dark' } : undefined,
|
||||
}),
|
||||
'& .MuiChip-label': {
|
||||
color: 'inherit',
|
||||
px: 0.75,
|
||||
},
|
||||
'& .MuiChip-icon': {
|
||||
color: 'inherit',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -1122,7 +1151,7 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { columns = LINE_COLUMNS, saving } = this.props;
|
||||
const { columns = LINE_COLUMNS, saving, ledgerLine } = this.props;
|
||||
const secondLine = this.renderSecondLine();
|
||||
|
||||
return (
|
||||
@@ -1153,11 +1182,52 @@ class ManifestFormLine extends PureComponent {
|
||||
pl: 0.5,
|
||||
pr: 0.5,
|
||||
verticalAlign: 'middle',
|
||||
width: this.props.onMove ? 92 : 40,
|
||||
width: (this.props.onMove ? 80 : 36) + (this.props.kind === 'delivery' && Boolean(this.props.onBookLine) ? 36 : 0),
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
||||
{this.props.kind === 'delivery' && Boolean(this.props.onBookLine) && (
|
||||
<Tooltip
|
||||
title={
|
||||
!ledgerLine
|
||||
? 'Lieferung zuerst speichern, um diese Position zu verbuchen'
|
||||
: ledgerLine.isBooked
|
||||
? 'Position bereits verbucht (Klicken zum Einsehen)'
|
||||
: `Position verbuchen (${ledgerLine.remainingAmount > 0 ? `${ledgerLine.remainingAmount} Stk.` : ''})`
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label="Position verbuchen"
|
||||
disabled={saving || !ledgerLine}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (ledgerLine) {
|
||||
this.props.onBookLine(ledgerLine);
|
||||
}
|
||||
}}
|
||||
color={
|
||||
!ledgerLine
|
||||
? 'default'
|
||||
: ledgerLine.isBooked
|
||||
? 'success'
|
||||
: ledgerLine.bookedAmount > 0
|
||||
? 'warning'
|
||||
: 'primary'
|
||||
}
|
||||
sx={{ p: 0.35 }}
|
||||
>
|
||||
{ledgerLine?.isBooked ? (
|
||||
<CheckCircleOutlineOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
) : (
|
||||
<Inventory2OutlinedIcon sx={{ fontSize: 18 }} />
|
||||
)}
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{this.props.onMove && (
|
||||
<>
|
||||
<span>
|
||||
|
||||
@@ -511,7 +511,7 @@ class ManifestHeader extends PureComponent {
|
||||
Wiederherstellen
|
||||
</Button>
|
||||
)}
|
||||
{docId != null && kind === 'delivery' && onOpenBookDialog && ledgerSummary?.hasTrackStock && (
|
||||
{docId != null && kind === 'delivery' && onOpenBookDialog && Boolean(ledgerSummary && (ledgerSummary.totalPositions > 0 || ledgerSummary.lines?.length > 0 || ledgerSummary.hasTrackStock)) && (
|
||||
<Button
|
||||
variant={ledgerSummary?.status === 'booked' ? 'outlined' : 'contained'}
|
||||
color={
|
||||
@@ -535,13 +535,17 @@ class ManifestHeader extends PureComponent {
|
||||
fontWeight: 700,
|
||||
fontSize: '0.8125rem',
|
||||
whiteSpace: 'nowrap',
|
||||
...(ledgerSummary?.status !== 'booked' && {
|
||||
color: '#ffffff',
|
||||
'& .MuiButton-startIcon': { color: '#ffffff' },
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{ledgerSummary?.status === 'booked'
|
||||
? 'Lager gebucht'
|
||||
? 'Vollständig gebucht'
|
||||
: ledgerSummary?.status === 'partial'
|
||||
? 'Rest einbuchen'
|
||||
: 'Ins Lager buchen'}
|
||||
? 'Rest verbuchen'
|
||||
: 'Lieferung verbuchen'}
|
||||
</Button>
|
||||
)}
|
||||
{kind === 'quote' && docId != null && onCreateOrder && (
|
||||
|
||||
@@ -40,17 +40,20 @@ import orderAutomat, {
|
||||
} from '../automats/orderAutomat';
|
||||
import quoteAutomat from '../automats/quoteAutomat';
|
||||
import shipmentAutomat from '../automats/shipmentAutomat';
|
||||
import deliveryAutomat from '../automats/deliveryAutomat';
|
||||
import { formatPrice } from '../utils/price';
|
||||
|
||||
function getAutomatFor(target) {
|
||||
if (target === 'quote') return quoteAutomat;
|
||||
if (target === 'shipment') return shipmentAutomat;
|
||||
if (target === 'delivery') return deliveryAutomat;
|
||||
return orderAutomat;
|
||||
}
|
||||
|
||||
function getInitialTarget() {
|
||||
if (typeof window !== 'undefined') {
|
||||
const p = window.location.pathname;
|
||||
if (p.startsWith('/lieferung')) return 'delivery';
|
||||
if (p.startsWith('/auftrag/ausliefern')) return 'shipment';
|
||||
if (p.startsWith('/auftrag/bestellung')) return 'order';
|
||||
if (p.startsWith('/auftrag')) return 'quote';
|
||||
@@ -296,6 +299,10 @@ class ChunkCacheMonitor extends PureComponent {
|
||||
if (p.onlyUnpaid) automat.actions.setOnlyUnpaid(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 (p.stateFilter) automat.actions.setStateFilter(p.stateFilter);
|
||||
}
|
||||
@@ -723,11 +730,15 @@ class ChunkCacheMonitor extends PureComponent {
|
||||
{chunkOrders.map(({ item: order, index }) => {
|
||||
const isOrd = this.state.target === 'order';
|
||||
const isShip = this.state.target === 'shipment';
|
||||
const isDeliv = this.state.target === 'delivery';
|
||||
const paid = isOrd ? isOrderPaid(order) : false;
|
||||
const delivered = isOrd ? isOrderDelivered(order) : false;
|
||||
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 unbooked = order.ledger?.unbookedPositions ?? 0;
|
||||
|
||||
return (
|
||||
<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 && (
|
||||
<>
|
||||
<Chip
|
||||
@@ -1072,6 +1091,14 @@ class ChunkCacheMonitor extends PureComponent {
|
||||
onClick={() => this.switchTarget('order')}
|
||||
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
|
||||
size="small"
|
||||
color={this.state.target === 'shipment' ? 'primary' : 'default'}
|
||||
|
||||
@@ -124,8 +124,8 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def
|
||||
});
|
||||
const bookedAmount = deliveryEntries.reduce((sum, e) => sum + Number(e.amount || 0), 0);
|
||||
const targetAmount = Number(line.amount || 0);
|
||||
const remainingAmount = trackStock ? Math.max(0, targetAmount - bookedAmount) : 0;
|
||||
const isBooked = trackStock ? (targetAmount > 0 ? bookedAmount >= targetAmount : bookedAmount > 0) : true;
|
||||
const remainingAmount = Math.max(0, targetAmount - bookedAmount);
|
||||
const isBooked = targetAmount > 0 ? bookedAmount >= targetAmount : bookedAmount > 0;
|
||||
|
||||
return {
|
||||
...line,
|
||||
@@ -134,21 +134,20 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def
|
||||
remainingAmount,
|
||||
isBooked,
|
||||
trackStock,
|
||||
isBookable: trackStock,
|
||||
isBookable: true,
|
||||
ledgerEntries: deliveryEntries,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const stockLines = linesWithLedger.filter((l) => l.trackStock);
|
||||
const totalPositions = stockLines.length;
|
||||
const bookedPositions = stockLines.filter((l) => l.isBooked).length;
|
||||
const totalQuantity = stockLines.reduce((sum, l) => sum + l.targetAmount, 0);
|
||||
const bookedQuantity = stockLines.reduce((sum, l) => sum + l.bookedAmount, 0);
|
||||
const hasTrackStock = stockLines.length > 0;
|
||||
const totalPositions = linesWithLedger.length;
|
||||
const bookedPositions = linesWithLedger.filter((l) => l.isBooked).length;
|
||||
const totalQuantity = linesWithLedger.reduce((sum, l) => sum + l.targetAmount, 0);
|
||||
const bookedQuantity = linesWithLedger.reduce((sum, l) => sum + l.bookedAmount, 0);
|
||||
const hasTrackStock = linesWithLedger.some((l) => l.trackStock);
|
||||
|
||||
let status;
|
||||
if (!hasTrackStock) {
|
||||
if (totalPositions === 0) {
|
||||
status = 'empty';
|
||||
} else if (bookedPositions === totalPositions && bookedQuantity >= totalQuantity) {
|
||||
status = 'booked';
|
||||
@@ -176,8 +175,8 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def
|
||||
*
|
||||
* @param {Object} params
|
||||
* @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 {number|string} params.warehouseId - Mandatory warehouse ID
|
||||
* @param {Array<{ manifestarticleId?: number, id?: number, amount: number, sku?: string, trackStock?: boolean }>} params.linesToBook
|
||||
* @param {number|string} [params.warehouseId] - Warehouse ID (mandatory only when at least one line tracks stock)
|
||||
* @param {Object} [params.stockApi]
|
||||
* @param {string} [params.transactionText]
|
||||
* @returns {Promise<Array<Object>>}
|
||||
@@ -189,15 +188,17 @@ export async function bookDeliveryLines({
|
||||
stockApi = defaultStockApi,
|
||||
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) {
|
||||
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(
|
||||
transactionText != null && transactionText !== ''
|
||||
? transactionText
|
||||
|
||||
@@ -136,7 +136,9 @@ export function getMaxDocumentNumber(kind = 'quote') {
|
||||
const prefixStyle = refSeq?.prefix ?? '';
|
||||
|
||||
let prefix = '';
|
||||
if (
|
||||
if (kind === 'delivery') {
|
||||
prefix = prefixStyle.startsWith('DEL-') ? 'DEL-' : (prefixStyle.startsWith('L-') ? 'L-' : 'LS-');
|
||||
} else if (
|
||||
prefixStyle.startsWith('ART-') ||
|
||||
prefixStyle.startsWith('LS-') ||
|
||||
prefixStyle.startsWith('KD-') ||
|
||||
|
||||
Reference in New Issue
Block a user