This commit is contained in:
seb
2026-10-17 17:11:11 +02:00
parent 20d83b00fc
commit 01d17ebb25
11 changed files with 450 additions and 372 deletions

View File

@@ -242,6 +242,8 @@ export function sortArticlesByRelevance(articles, query) {
export function searchArticlesIndex(query, options = {}) { export function searchArticlesIndex(query, options = {}) {
const { const {
excludeSkus = [], excludeSkus = [],
includeSkus = null,
extraArticles = [],
allowParent = false, allowParent = false,
categories = [], categories = [],
offset = 0, offset = 0,
@@ -253,7 +255,15 @@ export function searchArticlesIndex(query, options = {}) {
void articleIndexAutomat.actions.ensureLoaded().catch(() => {}); void articleIndexAutomat.actions.ensureLoaded().catch(() => {});
} }
const articles = articleIndexAutomat.state.items ?? []; let articles = articleIndexAutomat.state.items ?? [];
if (Array.isArray(extraArticles) && extraArticles.length > 0) {
const existingSkus = new Set(articles.map((a) => String(a.sku || '').toLowerCase()));
const missing = extraArticles.filter((a) => a.sku && !existingSkus.has(String(a.sku).toLowerCase()));
if (missing.length > 0) {
articles = [...articles, ...missing];
}
}
const q = String(query ?? '').trim(); const q = String(query ?? '').trim();
const rawMatches = q ? queryFlexSearch(q, articles) : articles; const rawMatches = q ? queryFlexSearch(q, articles) : articles;
@@ -266,6 +276,10 @@ export function searchArticlesIndex(query, options = {}) {
if (excludedSet.size > 0) { if (excludedSet.size > 0) {
pool = pool.filter((a) => a.sku && !excludedSet.has(String(a.sku).toLowerCase())); pool = pool.filter((a) => a.sku && !excludedSet.has(String(a.sku).toLowerCase()));
} }
if (includeSkus != null) {
const includedSet = new Set((includeSkus ?? []).map((s) => String(s).toLowerCase()));
pool = pool.filter((a) => a.sku && includedSet.has(String(a.sku).toLowerCase()));
}
let matchesList = pool; let matchesList = pool;

View File

@@ -441,7 +441,7 @@ export function createDeliveryEditorAutomat(options = {}) {
}); });
try { try {
const res = await supplierApi.getArticles(entityId, skus); const res = await supplierApi.getArticles(entityId);
const articles = Array.isArray(res?.articles) ? res.articles : []; const articles = Array.isArray(res?.articles) ? res.articles : [];
const map = buildSupplierArticlesMap(articles); const map = buildSupplierArticlesMap(articles);

View File

@@ -332,6 +332,8 @@ export default class ArticleSearch extends PureComponent {
if ( if (
this.state.indexStatus !== prevState.indexStatus || this.state.indexStatus !== prevState.indexStatus ||
this.props.excludeSkus !== prevProps.excludeSkus || this.props.excludeSkus !== prevProps.excludeSkus ||
this.props.includeSkus !== prevProps.includeSkus ||
this.props.extraArticles !== prevProps.extraArticles ||
this.props.allowParent !== prevProps.allowParent || this.props.allowParent !== prevProps.allowParent ||
this.props.categories !== prevProps.categories this.props.categories !== prevProps.categories
) { ) {
@@ -355,6 +357,8 @@ export default class ArticleSearch extends PureComponent {
runQuery = (queryText) => { runQuery = (queryText) => {
const { const {
excludeSkus = [], excludeSkus = [],
includeSkus = null,
extraArticles = [],
allowParent = false, allowParent = false,
categories = [], categories = [],
mode, mode,
@@ -364,6 +368,8 @@ export default class ArticleSearch extends PureComponent {
// full match set; the autocomplete popper keeps a small option cap. // full match set; the autocomplete popper keeps a small option cap.
const res = searchArticlesIndex(queryText, { const res = searchArticlesIndex(queryText, {
excludeSkus, excludeSkus,
includeSkus,
extraArticles,
allowParent, allowParent,
categories, categories,
offset: 0, offset: 0,

View File

@@ -11,6 +11,8 @@ import Alert from '@mui/material/Alert';
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import ArticleSearch from '../article/ArticleSearch'; import ArticleSearch from '../article/ArticleSearch';
import deliveryEditorAutomat from '../../automats/deliveryEditorAutomat';
export default class ArticleSearchDialog extends PureComponent { export default class ArticleSearchDialog extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -26,6 +28,11 @@ export default class ArticleSearchDialog extends PureComponent {
} }
} }
handleClose = () => {
this.setState({ assigningSku: null, error: null });
this.props.onClose?.();
};
handleSelect = async (article) => { handleSelect = async (article) => {
const { onAssign, line, kind = 'delivery' } = this.props; const { onAssign, line, kind = 'delivery' } = this.props;
const isDelivery = kind === 'delivery'; const isDelivery = kind === 'delivery';
@@ -41,26 +48,51 @@ export default class ArticleSearchDialog extends PureComponent {
await onAssign(article, line); await onAssign(article, line);
} catch (err) { } catch (err) {
this.setState({ this.setState({
assigningSku: null,
error: error:
err?.message || err?.message ||
(isDelivery (isDelivery
? 'Fehler beim Verknüpfen des Lieferanten-Artikels.' ? (line ? 'Fehler beim Verknüpfen des Lieferanten-Artikels.' : 'Fehler beim Hinzufügen des Artikels.')
: 'Fehler beim Zuordnen des Artikels.'), : 'Fehler beim Zuordnen des Artikels.'),
}); });
} finally {
this.setState({ assigningSku: null });
} }
}; };
render() { render() {
const { open, onClose, line, kind = 'delivery' } = this.props; const { open, onClose, line, kind = 'delivery', supplierArticles: propSupplierArticles } = this.props;
const { assigningSku, error } = this.state; const { assigningSku, error } = this.state;
const isDelivery = kind === 'delivery'; const isDelivery = kind === 'delivery';
const onlySupplierArticles = isDelivery && !line;
const rawSku = line?.sku != null ? String(line.sku).trim() : ''; const rawSku = line?.sku != null ? String(line.sku).trim() : '';
const supplierArticlesList = onlySupplierArticles
? (propSupplierArticles ?? deliveryEditorAutomat.state.supplierArticles ?? [])
: null;
const includeSkus = onlySupplierArticles
? supplierArticlesList.map((sa) => sa.article?.sku || sa.sku).filter(Boolean)
: null;
const extraArticles = onlySupplierArticles
? supplierArticlesList.map((sa) => ({
id: sa.article?.id ?? sa.id,
sku: sa.article?.sku || sa.sku,
name: sa.article?.name || sa.name || sa.sku || '',
brand: sa.article?.brand,
brandName: sa.article?.brand?.name || sa.article?.brandName,
stock: sa.article?.stock ?? sa.stock,
available: sa.article?.available,
price: sa.price?.net ?? sa.article?.price ?? sa.article?.vknetto,
eans: sa.article?.eans || [sa.sku].filter(Boolean),
isParent: false,
}))
: [];
return ( return (
<AppDialog <AppDialog
open={Boolean(open)} open={Boolean(open)}
onClose={assigningSku ? undefined : onClose} onClose={this.handleClose}
maxWidth="md" maxWidth="md"
fullWidth fullWidth
aria-labelledby="article-search-dialog-title" aria-labelledby="article-search-dialog-title"
@@ -89,18 +121,29 @@ export default class ArticleSearchDialog extends PureComponent {
> >
<Box> <Box>
<Typography variant="h6" component="div" fontWeight={600}> <Typography variant="h6" component="div" fontWeight={600}>
{line ? 'Existierenden Artikel zuordnen' : 'Artikel auswählen'} {line
? 'Existierenden Artikel zuordnen'
: isDelivery
? 'Lieferanten-Artikel auswählen'
: 'Artikel auswählen'}
</Typography> </Typography>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
{isDelivery ? 'Lieferanten-SKU' : 'SKU'}: <strong>{rawSku || '–'}</strong> {line ? (
{line?.name ? ` · ${line.name}` : ''} <>
{isDelivery ? 'Lieferanten-SKU' : 'SKU'}: <strong>{rawSku || '–'}</strong>
{line?.name ? ` · ${line.name}` : ''}
</>
) : isDelivery ? (
'Nur mit dem ausgewählten Lieferanten verknüpfte Artikel'
) : (
'Artikel für Beleg auswählen'
)}
</Typography> </Typography>
</Box> </Box>
<IconButton <IconButton
size="small" size="small"
aria-label="Schließen" aria-label="Schließen"
disabled={Boolean(assigningSku)} onClick={this.handleClose}
onClick={onClose}
> >
<CloseIcon fontSize="small" /> <CloseIcon fontSize="small" />
</IconButton> </IconButton>
@@ -123,12 +166,21 @@ export default class ArticleSearchDialog extends PureComponent {
</Alert> </Alert>
)} )}
{onlySupplierArticles && supplierArticlesList.length === 0 && (
<Alert severity="info" sx={{ mb: 2, flexShrink: 0 }}>
Für diesen Lieferanten sind noch keine Artikel verknüpft.
</Alert>
)}
<ArticleSearch <ArticleSearch
mode="list" mode="list"
autoFocus autoFocus
allowParent={false} allowParent={false}
busySku={assigningSku} busySku={assigningSku}
actionLabel={assigningSku ? 'Zuordnen…' : 'Zuordnen'} initialQuery={line?.name ? String(line.name) : ''}
includeSkus={includeSkus}
extraArticles={extraArticles}
actionLabel={assigningSku ? (line ? 'Zuordnen…' : 'Hinzufügen…') : (line ? 'Zuordnen' : 'Hinzufügen')}
onSelect={this.handleSelect} onSelect={this.handleSelect}
/> />
</DialogContent> </DialogContent>
@@ -136,8 +188,7 @@ export default class ArticleSearchDialog extends PureComponent {
<DialogActions sx={{ px: 2, py: 1.5 }}> <DialogActions sx={{ px: 2, py: 1.5 }}>
<Button <Button
size="small" size="small"
disabled={Boolean(assigningSku)} onClick={this.handleClose}
onClick={onClose}
> >
Abbrechen Abbrechen
</Button> </Button>

View File

@@ -1,7 +1,6 @@
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Attachment from './Attachment'; import Attachment from './Attachment';
export class AttachmentList extends PureComponent { export class AttachmentList extends PureComponent {
@@ -13,14 +12,6 @@ export class AttachmentList extends PureComponent {
return ( return (
<Box sx={{ mt: 2.5, pt: 1.5, borderTop: '1px solid', borderColor: 'divider' }}> <Box sx={{ mt: 2.5, pt: 1.5, borderTop: '1px solid', borderColor: 'divider' }}>
<Typography
variant="caption"
color="text.secondary"
fontWeight={600}
sx={{ mb: 1, display: 'block', textTransform: 'uppercase', letterSpacing: '0.05em' }}
>
Dateianhänge
</Typography>
<Stack direction="row" spacing={1.5} sx={{ flexWrap: 'wrap', gap: 1.5 }}> <Stack direction="row" spacing={1.5} sx={{ flexWrap: 'wrap', gap: 1.5 }}>
{attachments.map((attachment, index) => ( {attachments.map((attachment, index) => (
<Attachment <Attachment

View File

@@ -34,12 +34,75 @@ import {
fetchDeliveryLedgerSummary, fetchDeliveryLedgerSummary,
bookDeliveryLines, bookDeliveryLines,
} from '../../utils/deliveryLedger'; } from '../../utils/deliveryLedger';
import { getArticle } from '../../automats/artikelAutomat';
import warehouseAutomat from '../../automats/warehouseAutomat'; import warehouseAutomat from '../../automats/warehouseAutomat';
import { supplier as supplierApi } from '../../api';
import deliveryEditorAutomat, { buildSupplierArticlesMap } from '../../automats/deliveryEditorAutomat';
import { import {
SURFACE_BASE, SURFACE_BASE,
SURFACE_RAISED, SURFACE_RAISED,
} from '../../colors'; } from '../../colors';
function resolveLineSkus(line, delivery, supplierArticlesMap = {}) {
const rawSku = line?.sku ? String(line.sku).trim() : '';
const supplierId = delivery?.supplierId ?? delivery?.partyId;
// 1. Direct line properties if available
let supplierSku =
line?.supplierSku ||
line?.supplier_sku ||
line?.supplierArticle?.sku ||
'';
let ownSku =
line?.articleSku ||
line?.article_sku ||
line?.article?.sku ||
line?.supplierArticle?.article?.sku ||
'';
// 2. Check supplierArticlesMap
const sa = rawSku
? (supplierArticlesMap[rawSku] || supplierArticlesMap[rawSku.toLowerCase()])
: null;
if (sa) {
if (!supplierSku && sa.sku) supplierSku = sa.sku;
if (!ownSku && sa.article?.sku) ownSku = sa.article.sku;
}
// 3. Check loaded article details
const cachedArticle =
line?.article ||
(ownSku ? getArticle(ownSku) : null) ||
(rawSku ? getArticle(rawSku) : null);
if (cachedArticle) {
if (!ownSku && cachedArticle.sku) {
ownSku = cachedArticle.sku;
}
const sup =
(supplierId && cachedArticle.suppliers?.find((s) => s.entityId === supplierId)) ||
cachedArticle.suppliers?.[0];
if (!supplierSku && sup?.sku) {
supplierSku = sup.sku;
}
}
// 4. Fallback heuristics if one is still missing
if (!supplierSku && !ownSku) {
ownSku = rawSku;
} else if (!supplierSku && rawSku && rawSku !== ownSku) {
supplierSku = rawSku;
} else if (!ownSku && rawSku && rawSku !== supplierSku) {
ownSku = rawSku;
}
return {
supplierSku: supplierSku || '–',
ownSku: ownSku || '–',
};
}
export default class BookDeliveryDialog extends PureComponent { export default class BookDeliveryDialog extends PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -61,19 +124,23 @@ export default class BookDeliveryDialog extends PureComponent {
} }
const initialWarehouses = warehouseAutomat.state.warehouses || []; const initialWarehouses = warehouseAutomat.state.warehouses || [];
const initialSupplierMap =
props.supplierArticlesMap ||
(deliveryEditorAutomat?.state?.supplierArticlesMap ?? {});
this.state = { this.state = {
summary: props.summary ?? null, summary: props.summary ?? null,
loading: !props.summary, loading: !props.summary,
error: null, error: null,
amountsToBook: initialAmounts, amountsToBook: initialAmounts,
transactionText: props.delivery?.number transactionText: props.delivery?.externalNumber || (props.delivery?.number
? `Lieferung ${props.delivery.number}` ? `Lieferung ${props.delivery.number}`
: 'Lieferung Wareneingang', : 'Lieferung Wareneingang'),
isBooking: false, isBooking: false,
warehouses: initialWarehouses, warehouses: initialWarehouses,
selectedWarehouseId: initialWarehouses.length === 1 ? initialWarehouses[0].id : '', selectedWarehouseId: initialWarehouses.length === 1 ? initialWarehouses[0].id : '',
warehouseError: null, warehouseError: null,
supplierArticlesMap: initialSupplierMap,
}; };
this.handleAmountChange = this.handleAmountChange.bind(this); this.handleAmountChange = this.handleAmountChange.bind(this);
@@ -84,6 +151,17 @@ export default class BookDeliveryDialog extends PureComponent {
this.handleConfirmBook = this.handleConfirmBook.bind(this); this.handleConfirmBook = this.handleConfirmBook.bind(this);
} }
loadSupplierArticlesIfNeeded() {
const supplierId = this.props.delivery?.supplierId ?? this.props.delivery?.partyId;
if (supplierId && Object.keys(this.state.supplierArticlesMap).length === 0) {
void supplierApi.get(supplierId).then((res) => {
const articles = res?.supplier?.articles || res?.articles || [];
const map = buildSupplierArticlesMap(articles);
this.setState({ supplierArticlesMap: map });
}).catch(() => {});
}
}
componentDidMount() { componentDidMount() {
this.unsubWarehouse = warehouseAutomat.subscribe((state) => { this.unsubWarehouse = warehouseAutomat.subscribe((state) => {
const warehouses = state.warehouses || []; const warehouses = state.warehouses || [];
@@ -98,6 +176,7 @@ export default class BookDeliveryDialog extends PureComponent {
}); });
}); });
void warehouseAutomat.actions.load().catch(() => {}); void warehouseAutomat.actions.load().catch(() => {});
this.loadSupplierArticlesIfNeeded();
if (this.props.open) { if (this.props.open) {
void this.loadSummary(); void this.loadSummary();
@@ -111,6 +190,9 @@ export default class BookDeliveryDialog extends PureComponent {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.open && !prevProps.open) { if (this.props.open && !prevProps.open) {
const currentWarehouses = this.state.warehouses || warehouseAutomat.state.warehouses || []; const currentWarehouses = this.state.warehouses || warehouseAutomat.state.warehouses || [];
const currentSupplierMap =
this.props.supplierArticlesMap ||
(deliveryEditorAutomat?.state?.supplierArticlesMap ?? this.state.supplierArticlesMap ?? {});
this.setState( this.setState(
{ {
summary: this.props.summary ?? null, summary: this.props.summary ?? null,
@@ -120,13 +202,15 @@ export default class BookDeliveryDialog extends PureComponent {
selectedWarehouseId: selectedWarehouseId:
this.state.selectedWarehouseId || this.state.selectedWarehouseId ||
(currentWarehouses.length === 1 ? currentWarehouses[0].id : ''), (currentWarehouses.length === 1 ? currentWarehouses[0].id : ''),
transactionText: this.props.delivery?.number transactionText: this.props.delivery?.externalNumber || (this.props.delivery?.number
? `Lieferung ${this.props.delivery.number}` ? `Lieferung ${this.props.delivery.number}`
: 'Lieferung Wareneingang', : 'Lieferung Wareneingang'),
isBooking: false, isBooking: false,
supplierArticlesMap: currentSupplierMap,
}, },
() => { () => {
void warehouseAutomat.actions.load().catch(() => {}); void warehouseAutomat.actions.load().catch(() => {});
this.loadSupplierArticlesIfNeeded();
if (this.props.summary) { if (this.props.summary) {
this.initAmountsFromSummary(this.props.summary, this.props.prefillLineId); this.initAmountsFromSummary(this.props.summary, this.props.prefillLineId);
} else { } else {
@@ -284,105 +368,111 @@ export default class BookDeliveryDialog extends PureComponent {
} }
} }
renderHeaderInfo() { renderHeaderInfo({ hasTrackedLinesToBook, isWarehouseMissing } = {}) {
const { delivery, partyName } = this.props; const { delivery, partyName } = this.props;
const { summary } = this.state; const {
transactionText,
isBooking,
summary,
warehouses,
selectedWarehouseId,
warehouseError,
} = this.state;
const isFullyBooked = (summary?.lines?.length ?? 0) > 0 && summary?.status === 'booked';
return ( return (
<Paper <Box sx={{ mb: 2 }}>
variant="outlined" <Box
sx={{ sx={{
p: 1.5, display: 'flex',
mb: 2, flexWrap: 'wrap',
display: 'flex', gap: 1.5,
flexWrap: 'wrap', mb: 1.5,
gap: 3, }}
backgroundColor: SURFACE_BASE, >
alignItems: 'center', <TextField
}} size="small"
> label="Datum"
<Box> value={formatDate(delivery?.date) || '–'}
<Typography variant="caption" color="text.secondary" display="block"> disabled
Nummer sx={{ flex: { xs: '1 1 100%', sm: '1 1 110px' }, minWidth: { sm: 100 } }}
</Typography> />
<Typography variant="body2" sx={{ fontWeight: 600 }}> <TextField
{delivery?.number || '–'} size="small"
</Typography> label="Lieferant"
value={partyName || '–'}
disabled
sx={{ flex: { xs: '1 1 100%', sm: '2 1 180px' }, minWidth: { sm: 140 } }}
/>
<TextField
size="small"
label="Nummer"
value={delivery?.number || '–'}
disabled
sx={{ flex: { xs: '1 1 100%', sm: '1 1 110px' }, minWidth: { sm: 100 } }}
/>
</Box> </Box>
{delivery?.externalNumber && ( <Box
<Box> sx={{
<Typography variant="caption" color="text.secondary" display="block"> display: 'flex',
Referenz flexWrap: 'wrap',
</Typography> gap: 1.5,
<Typography variant="body2" sx={{ fontWeight: 600 }}> }}
{delivery.externalNumber} >
</Typography> <TextField
</Box> size="small"
)} label="Referenz"
value={transactionText}
<Box> onChange={this.handleTransactionTextChange}
<Typography variant="caption" color="text.secondary" display="block"> disabled={isBooking || isFullyBooked}
Lieferdatum sx={{ flex: { xs: '1 1 100%', sm: '1 1 200px' } }}
</Typography> />
<Typography variant="body2"> <FormControl
{formatDate(delivery?.date)} size="small"
</Typography> required={hasTrackedLinesToBook}
error={Boolean(warehouseError) || isWarehouseMissing}
disabled={isBooking || isFullyBooked}
sx={{ flex: { xs: '1 1 100%', sm: '1 1 200px' } }}
>
<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>
)}
</FormControl>
</Box> </Box>
</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() { renderLinesTable() {
const { summary, amountsToBook, isBooking } = this.state; const { summary, amountsToBook, isBooking, supplierArticlesMap } = this.state;
const lines = summary?.lines || []; const lines = summary?.lines || [];
if (lines.length === 0) { if (lines.length === 0) {
@@ -398,7 +488,10 @@ export default class BookDeliveryDialog extends PureComponent {
<Table size="small"> <Table size="small">
<TableHead sx={{ backgroundColor: SURFACE_RAISED }}> <TableHead sx={{ backgroundColor: SURFACE_RAISED }}>
<TableRow> <TableRow>
<TableCell sx={{ fontWeight: 600 }}>SKU</TableCell> <TableCell sx={{ fontWeight: 600, width: 44 }} align="center">Pos.</TableCell>
<TableCell sx={{ fontWeight: 600 }}>Lieferanten-SKU</TableCell>
<TableCell sx={{ fontWeight: 600 }}>Eigene SKU</TableCell>
<TableCell sx={{ fontWeight: 600 }}>EAN</TableCell>
<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>
@@ -406,29 +499,45 @@ export default class BookDeliveryDialog extends PureComponent {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{lines.map((line) => { {lines.map((line, index) => {
const toBook = amountsToBook[line.id] ?? 0; const toBook = amountsToBook[line.id] ?? 0;
const isAlreadyFullyBooked = line.isBooked && line.remainingAmount <= 0; const isAlreadyFullyBooked = line.isBooked && line.remainingAmount <= 0;
const posNumber = line.pos ?? line.position ?? (index + 1);
const { supplierSku, ownSku } = resolveLineSkus(
line,
this.props.delivery,
supplierArticlesMap
);
const cachedArticle =
line.article ||
(ownSku && ownSku !== '–' ? getArticle(ownSku) : null) ||
(line.sku ? getArticle(line.sku) : null);
const eans = line.ean
? [line.ean]
: line.eans || cachedArticle?.eans || cachedArticle?.articleean || [];
const primaryEan = Array.isArray(eans) ? (eans[0] ?? '') : (eans ?? '');
return ( return (
<TableRow key={line.id} hover> <TableRow key={line.id} hover>
<TableCell sx={{ fontFamily: 'monospace', fontSize: '0.85rem' }}> <TableCell align="center" sx={{ color: 'text.secondary', fontSize: '0.85rem' }}>
{line.sku || '–'} {posNumber}
</TableCell> </TableCell>
<TableCell sx={{ maxWidth: 280 }}> <TableCell sx={{ fontFamily: 'monospace', fontSize: '0.85rem' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> {supplierSku}
<Typography variant="body2" sx={{ fontWeight: 500 }} noWrap> </TableCell>
{line.name || '–'} <TableCell sx={{ fontFamily: 'monospace', fontSize: '0.85rem' }}>
</Typography> {ownSku}
{!line.trackStock && ( </TableCell>
<Chip <TableCell
size="small" sx={{ fontFamily: 'monospace', fontSize: '0.85rem' }}
label="Ohne Lagerbestand" title={Array.isArray(eans) && eans.length > 1 ? eans.join(', ') : undefined}
variant="outlined" >
sx={{ fontSize: '0.7rem', height: 20, color: 'text.secondary', borderColor: 'divider' }} {primaryEan || '–'}
/> </TableCell>
)} <TableCell sx={{ maxWidth: 260 }}>
</Box> <Typography variant="body2" sx={{ fontWeight: 500 }} noWrap>
{line.name || '–'}
</Typography>
</TableCell> </TableCell>
<TableCell align="right"> <TableCell align="right">
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600 }}>
@@ -524,7 +633,7 @@ export default class BookDeliveryDialog extends PureComponent {
<AppDialog <AppDialog
open={Boolean(open)} open={Boolean(open)}
onClose={isBooking ? undefined : onClose} onClose={isBooking ? undefined : onClose}
maxWidth="md" maxWidth="lg"
fullWidth fullWidth
> >
<DialogTitle <DialogTitle
@@ -538,7 +647,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 || ''} verbuchen Lieferung verbuchen
</Typography> </Typography>
</Box> </Box>
<IconButton size="small" onClick={onClose} disabled={isBooking}> <IconButton size="small" onClick={onClose} disabled={isBooking}>
@@ -546,8 +655,8 @@ export default class BookDeliveryDialog extends PureComponent {
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent dividers sx={{ pt: 2 }}> <DialogContent dividers sx={{ pt: 2, borderBottom: 'none' }}>
{this.renderHeaderInfo()} {this.renderHeaderInfo({ hasTrackedLinesToBook, isWarehouseMissing })}
{loading ? ( {loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6, gap: 2, alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'center', py: 6, gap: 2, alignItems: 'center' }}>
@@ -568,125 +677,21 @@ export default class BookDeliveryDialog extends PureComponent {
)} )}
{this.renderLinesTable()} {this.renderLinesTable()}
{!isFullyBooked && hasLines && (
<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>
)}
{hasTrackedLinesToBook && 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. Für lagergeführte Artikel legen Sie bitte 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>
)} )}
{hasLines && (
<Box
sx={{
display: 'flex',
gap: 2,
mb: 2,
flexDirection: { xs: 'column', sm: 'row' },
}}
>
{hasTrackStock && (
<FormControl
size="small"
required={hasTrackedLinesToBook}
error={Boolean(warehouseError)}
disabled={isBooking || isFullyBooked}
sx={{ minWidth: { sm: 240 }, flex: { sm: '0 0 auto' } }}
>
<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>
)}
</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 ? ( {isFullyBooked ? (
<Alert severity="success" icon={<CheckCircleOutlineOutlinedIcon />}> <Alert severity="success" icon={<CheckCircleOutlineOutlinedIcon />}>
Alle Positionen dieser Lieferung sind bereits vollständig im Ledger verbucht. Alle Positionen dieser Lieferung sind bereits vollständig im Ledger verbucht.
</Alert> </Alert>
) : countLinesToBook > 0 ? ( ) : isWarehouseMissing ? (
<Alert severity={isWarehouseMissing ? 'warning' : 'info'}> <Alert severity="warning">
{isWarehouseMissing ? ( 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 lagergeführten Artikel zu buchen.
</>
) : (
<>
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> </Alert>
) : hasLines ? ( ) : countLinesToBook === 0 && hasLines ? (
<Alert severity="warning"> <Alert severity="warning">
Wählen Sie mindestens eine Menge größer als 0 aus, um Positionen zu verbuchen. Wählen Sie mindestens eine Menge größer als 0 aus, um Positionen zu verbuchen.
</Alert> </Alert>

View File

@@ -251,18 +251,18 @@ class ManifestFormLine extends PureComponent {
'&:hover': isClickable ? { bgcolor: 'rgba(21, 128, 61, 0.16)' } : undefined, '&:hover': isClickable ? { bgcolor: 'rgba(21, 128, 61, 0.16)' } : undefined,
} }
: isPartial : isPartial
? { ? {
bgcolor: '#d97706', bgcolor: '#d97706',
color: '#ffffff', color: '#ffffff',
border: 'none', border: 'none',
'&:hover': isClickable ? { bgcolor: '#b45309' } : undefined, '&:hover': isClickable ? { bgcolor: '#b45309' } : undefined,
} }
: { : {
bgcolor: 'primary.main', bgcolor: 'primary.main',
color: '#ffffff', color: '#ffffff',
border: 'none', border: 'none',
'&:hover': isClickable ? { bgcolor: 'primary.dark' } : undefined, '&:hover': isClickable ? { bgcolor: 'primary.dark' } : undefined,
}), }),
'& .MuiChip-label': { '& .MuiChip-label': {
color: 'inherit', color: 'inherit',
px: 0.75, px: 0.75,
@@ -588,10 +588,10 @@ class ManifestFormLine extends PureComponent {
vkPrice?.net != null vkPrice?.net != null
? Number(vkPrice.net) ? Number(vkPrice.net)
: cachedArticle.net != null : cachedArticle.net != null
? Number(cachedArticle.net) ? Number(cachedArticle.net)
: cachedArticle.price != null : cachedArticle.price != null
? Number(cachedArticle.price) ? Number(cachedArticle.price)
: null; : null;
const vkTaxRate = const vkTaxRate =
vkPrice?.taxRate ?? vkPrice?.taxRate ??
vkPrice?.taxrate ?? vkPrice?.taxrate ??
@@ -601,12 +601,12 @@ class ManifestFormLine extends PureComponent {
19; 19;
const vkBrutto = vkPrice const vkBrutto = vkPrice
? priceBrutto(vkPrice) ?? ? priceBrutto(vkPrice) ??
(vkNetto != null && vkTaxRate != null (vkNetto != null && vkTaxRate != null
? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100 ? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100
: null) : null)
: vkNetto != null : vkNetto != null
? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100 ? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100
: null; : null;
const suppliers = const suppliers =
Array.isArray(cachedArticle.suppliers) && cachedArticle.suppliers.length > 0 Array.isArray(cachedArticle.suppliers) && cachedArticle.suppliers.length > 0
@@ -1206,45 +1206,35 @@ class ManifestFormLine extends PureComponent {
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
{this.props.kind === 'delivery' && Boolean(this.props.onBookLine) && ( {this.props.kind === 'delivery' && Boolean(this.props.onBookLine) && (
<Tooltip <span>
title={ <IconButton
!ledgerLine size="small"
? 'Lieferung zuerst speichern, um diese Position zu verbuchen' aria-label="Position verbuchen"
: ledgerLine.isBooked disabled={saving || !ledgerLine}
? 'Position bereits verbucht (Klicken zum Einsehen)' onClick={(e) => {
: `Position verbuchen (${ledgerLine.remainingAmount > 0 ? `${ledgerLine.remainingAmount} Stk.` : ''})` e.stopPropagation();
} if (ledgerLine) {
> this.props.onBookLine(ledgerLine);
<span> }
<IconButton }}
size="small" color={
aria-label="Position verbuchen" !ledgerLine
disabled={saving || !ledgerLine} ? 'default'
onClick={(e) => { : ledgerLine.isBooked
e.stopPropagation();
if (ledgerLine) {
this.props.onBookLine(ledgerLine);
}
}}
color={
!ledgerLine
? 'default'
: ledgerLine.isBooked
? 'success' ? 'success'
: ledgerLine.bookedAmount > 0 : ledgerLine.bookedAmount > 0
? 'warning' ? 'warning'
: 'primary' : 'primary'
} }
sx={{ p: 0.35 }} sx={{ p: 0.35 }}
> >
{ledgerLine?.isBooked ? ( {ledgerLine?.isBooked ? (
<CheckCircleOutlineOutlinedIcon sx={{ fontSize: 18 }} /> <CheckCircleOutlineOutlinedIcon sx={{ fontSize: 18 }} />
) : ( ) : (
<Inventory2OutlinedIcon sx={{ fontSize: 18 }} /> <Inventory2OutlinedIcon sx={{ fontSize: 18 }} />
)} )}
</IconButton> </IconButton>
</span> </span>
</Tooltip>
)} )}
{this.props.onMove && ( {this.props.onMove && (
<> <>

View File

@@ -5,7 +5,6 @@ import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import Chip from '@mui/material/Chip';
import UploadFileIcon from '@mui/icons-material/UploadFile'; import UploadFileIcon from '@mui/icons-material/UploadFile';
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
@@ -277,8 +276,8 @@ class ManifestHeader extends PureComponent {
return ( return (
<> <>
{/* Tile 1: 3-wide Kopfdaten Tile */} {/* Tile 1: 3-wide Tile */}
<PanelTile wide={3} minHeight={160} color={headerColor} title="Kopfdaten"> <PanelTile wide={3} minHeight={160} color={headerColor}>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@@ -410,42 +409,6 @@ class ManifestHeader extends PureComponent {
}} }}
/> />
) : null} ) : null}
{docId != null && kind === 'delivery' && ledgerSummary && (
<Chip
size="small"
icon={
ledgerSummary.status === 'booked' ? (
<CheckCircleOutlineOutlinedIcon sx={{ fontSize: 16 }} />
) : (
<Inventory2OutlinedIcon sx={{ fontSize: 16 }} />
)
}
label={
ledgerSummary.status === 'booked'
? `Lager: Gebucht (${ledgerSummary.bookedPositions}/${ledgerSummary.totalPositions})`
: ledgerSummary.status === 'partial'
? `Lager: Teilweise (${ledgerSummary.bookedPositions}/${ledgerSummary.totalPositions})`
: ledgerSummary.status === 'empty'
? 'Lager: Keine Pos.'
: `Lager: Offen (${ledgerSummary.totalPositions} Pos.)`
}
color={
ledgerSummary.status === 'booked'
? 'success'
: ledgerSummary.status === 'partial'
? 'warning'
: 'default'
}
variant="outlined"
onClick={onOpenBookDialog}
sx={{
cursor: onOpenBookDialog ? 'pointer' : 'default',
fontWeight: 600,
height: 32,
px: 0.5,
}}
/>
)}
</Box> </Box>
<AttachmentList <AttachmentList
kind={kind} kind={kind}

View File

@@ -23,6 +23,7 @@ class ManifestLines extends PureComponent {
kind = 'delivery', kind = 'delivery',
lines, lines,
saving, saving,
partyId,
linesColor, linesColor,
dirtyLineFields = {}, dirtyLineFields = {},
shipments, shipments,
@@ -68,7 +69,6 @@ class ManifestLines extends PureComponent {
wide={wide} wide={wide}
minHeight={280} minHeight={280}
color={linesColor} color={linesColor}
title="Artikel"
sx={{ sx={{
...(wide >= 4 ? { gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' } } : {}), ...(wide >= 4 ? { gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' } } : {}),
...(!hasArticles ...(!hasArticles
@@ -115,17 +115,7 @@ class ManifestLines extends PureComponent {
} }
if (!lines || lines.length === 0) { if (!lines || lines.length === 0) {
return ( return null;
<TableRow>
<TableCell
colSpan={columns.length + 1}
align="center"
sx={{ py: 3, color: 'text.secondary', fontSize: '0.82rem' }}
>
Keine Positionen vorhanden
</TableCell>
</TableRow>
);
} }
return lines.map((line, index) => { return lines.map((line, index) => {
@@ -190,16 +180,24 @@ class ManifestLines extends PureComponent {
> >
Zeile Zeile
</Button> </Button>
{onSearchArticle && ( {onSearchArticle && (() => {
<Button const searchArticleDisabled = saving || (kind === 'delivery' && !partyId);
size="small" const searchArticleTitle =
startIcon={<SearchIcon />} kind === 'delivery' && !partyId
disabled={saving} ? 'Bitte zuerst einen Lieferanten auswählen'
onClick={() => onSearchArticle(null)} : undefined;
> return (
Artikel suchen <Button
</Button> size="small"
)} startIcon={<SearchIcon />}
disabled={searchArticleDisabled}
onClick={() => onSearchArticle(null)}
title={searchArticleTitle}
>
Artikel suchen
</Button>
);
})()}
{(() => { {(() => {
const hasShippedLines = kind === 'order' && Array.isArray(shipments) && shipments.some((sh) => (sh.lines?.length || 0) > 0); const hasShippedLines = kind === 'order' && Array.isArray(shipments) && shipments.some((sh) => (sh.lines?.length || 0) > 0);
return ( return (

View File

@@ -431,18 +431,67 @@ class LieferungNeuPage extends PureComponent {
async handleAssignArticleForLine(article, line) { async handleAssignArticleForLine(article, line) {
const { partyId } = this.state; const { partyId } = this.state;
if (!partyId) { if (!partyId) {
enqueueSnackbar('Bitte wählen Sie zuerst einen Lieferanten aus.', { variant: 'warning' }); const msg = 'Bitte wählen Sie zuerst einen Lieferanten aus.';
return; enqueueSnackbar(msg, { variant: 'warning' });
throw new Error(msg);
} }
const internalSku = article?.sku; const internalSku = article?.sku;
if (!internalSku) { if (!internalSku) {
enqueueSnackbar('Artikel hat keine SKU.', { variant: 'error' }); const msg = 'Artikel hat keine SKU.';
return; enqueueSnackbar(msg, { variant: 'error' });
throw new Error(msg);
} }
const supplierSku = line?.sku; const supplierSku = line?.sku;
if (!line) {
const supplierArticle =
this.state.supplierArticlesMap?.[internalSku.toLowerCase()] ||
this.state.supplierArticlesMap?.[internalSku] ||
this.state.supplierArticles?.find(
(sa) => sa.article?.sku === internalSku || sa.sku === internalSku
);
const supplierSkuVal = supplierArticle?.sku || '';
const netVal =
supplierArticle?.price?.net != null
? String(supplierArticle.price.net)
: article.price != null
? String(article.price)
: article.net != null
? String(article.net)
: '';
const taxVal =
supplierArticle?.price?.taxRate != null
? String(supplierArticle.price.taxRate)
: article.taxrate != null
? String(article.taxrate)
: article.tax != null
? String(article.tax)
: '';
const puVal = supplierArticle?.pu != null ? Number(supplierArticle.pu) : 1;
this.automat.actions.addLineitem({
amount: 1,
sku: supplierSkuVal || internalSku,
name: article.name || supplierArticle?.article?.name || '',
tax: taxVal,
taxrate: taxVal,
net: netVal,
netprice: netVal,
pu: puVal,
});
enqueueSnackbar(`Artikel "${internalSku}" hinzugefügt.`, { variant: 'success' });
this.setState({
articleSearchDialogOpen: false,
articleSearchLine: null,
});
return;
}
try { try {
await articleApi.addSupplier(internalSku, { await articleApi.addSupplier(internalSku, {
entityId: partyId, entityId: partyId,
@@ -454,9 +503,15 @@ class LieferungNeuPage extends PureComponent {
}); });
if (supplierSku) { if (supplierSku) {
this.automat.actions.fetchSupplierArticleForSku(supplierSku, line.key); await this.automat.actions.fetchSupplierArticleForSku(supplierSku, line.key);
} else { }
this.automat.actions.fetchSupplierArticles(partyId); await this.automat.actions.fetchSupplierArticles(partyId);
if (!line.name && article.name) {
this.automat.actions.updateLineitem({
...line,
name: article.name,
});
} }
enqueueSnackbar( enqueueSnackbar(
@@ -1131,6 +1186,7 @@ class LieferungNeuPage extends PureComponent {
columns={DELIVERY_LINE_COLUMNS} columns={DELIVERY_LINE_COLUMNS}
lines={lines} lines={lines}
saving={saving} saving={saving}
partyId={this.state.partyId}
wide={ wide={
this.state.viewerPosition === 'right' && this.isViewerOpen() this.state.viewerPosition === 'right' && this.isViewerOpen()
? 2 ? 2
@@ -1180,6 +1236,8 @@ class LieferungNeuPage extends PureComponent {
open={this.state.articleSearchDialogOpen} open={this.state.articleSearchDialogOpen}
line={this.state.articleSearchLine} line={this.state.articleSearchLine}
kind="delivery" kind="delivery"
partyId={this.state.partyId}
supplierArticles={this.state.supplierArticles}
onClose={this.handleCloseArticleSearch} onClose={this.handleCloseArticleSearch}
onAssign={this.handleAssignArticleForLine} onAssign={this.handleAssignArticleForLine}
/> />
@@ -1197,6 +1255,7 @@ class LieferungNeuPage extends PureComponent {
summary={this.state.ledgerSummary} summary={this.state.ledgerSummary}
prefillLineId={this.state.bookDeliveryPrefillLineId} prefillLineId={this.state.bookDeliveryPrefillLineId}
partyName={this.state.deliveryEntities?.find((p) => p.id === this.state.partyId)?.name} partyName={this.state.deliveryEntities?.find((p) => p.id === this.state.partyId)?.name}
supplierArticlesMap={this.state.supplierArticlesMap}
onClose={this.handleCloseBookDialog} onClose={this.handleCloseBookDialog}
onBookSuccess={this.handleBookSuccess} onBookSuccess={this.handleBookSuccess}
/> />

View File

@@ -129,6 +129,7 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def
return { return {
...line, ...line,
article,
targetAmount, targetAmount,
bookedAmount, bookedAmount,
remainingAmount, remainingAmount,