From 01d17ebb25a7babf5118f69bfc8391462640ddf1 Mon Sep 17 00:00:00 2001 From: seb Date: Sat, 17 Oct 2026 17:11:11 +0200 Subject: [PATCH] u --- src/automats/articleSearchAutomat.js | 16 +- src/automats/deliveryEditorAutomat.js | 2 +- src/components/article/ArticleSearch.jsx | 6 + .../manifest/ArticleSearchDialog.jsx | 75 ++- src/components/manifest/AttachmentList.jsx | 9 - .../manifest/BookDeliveryDialog.jsx | 451 +++++++++--------- src/components/manifest/ManifestFormLine.jsx | 106 ++-- src/components/manifest/ManifestHeader.jsx | 41 +- src/components/manifest/ManifestLines.jsx | 42 +- src/routes/lieferung/neu.jsx | 73 ++- src/utils/deliveryLedger.js | 1 + 11 files changed, 450 insertions(+), 372 deletions(-) diff --git a/src/automats/articleSearchAutomat.js b/src/automats/articleSearchAutomat.js index 29c53b1..7506fe7 100644 --- a/src/automats/articleSearchAutomat.js +++ b/src/automats/articleSearchAutomat.js @@ -242,6 +242,8 @@ export function sortArticlesByRelevance(articles, query) { export function searchArticlesIndex(query, options = {}) { const { excludeSkus = [], + includeSkus = null, + extraArticles = [], allowParent = false, categories = [], offset = 0, @@ -253,7 +255,15 @@ export function searchArticlesIndex(query, options = {}) { 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 rawMatches = q ? queryFlexSearch(q, articles) : articles; @@ -266,6 +276,10 @@ export function searchArticlesIndex(query, options = {}) { if (excludedSet.size > 0) { 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; diff --git a/src/automats/deliveryEditorAutomat.js b/src/automats/deliveryEditorAutomat.js index 71914c9..ec6aae2 100644 --- a/src/automats/deliveryEditorAutomat.js +++ b/src/automats/deliveryEditorAutomat.js @@ -441,7 +441,7 @@ export function createDeliveryEditorAutomat(options = {}) { }); try { - const res = await supplierApi.getArticles(entityId, skus); + const res = await supplierApi.getArticles(entityId); const articles = Array.isArray(res?.articles) ? res.articles : []; const map = buildSupplierArticlesMap(articles); diff --git a/src/components/article/ArticleSearch.jsx b/src/components/article/ArticleSearch.jsx index 0f37b8b..9b4f2cd 100644 --- a/src/components/article/ArticleSearch.jsx +++ b/src/components/article/ArticleSearch.jsx @@ -332,6 +332,8 @@ export default class ArticleSearch extends PureComponent { if ( this.state.indexStatus !== prevState.indexStatus || this.props.excludeSkus !== prevProps.excludeSkus || + this.props.includeSkus !== prevProps.includeSkus || + this.props.extraArticles !== prevProps.extraArticles || this.props.allowParent !== prevProps.allowParent || this.props.categories !== prevProps.categories ) { @@ -355,6 +357,8 @@ export default class ArticleSearch extends PureComponent { runQuery = (queryText) => { const { excludeSkus = [], + includeSkus = null, + extraArticles = [], allowParent = false, categories = [], mode, @@ -364,6 +368,8 @@ export default class ArticleSearch extends PureComponent { // full match set; the autocomplete popper keeps a small option cap. const res = searchArticlesIndex(queryText, { excludeSkus, + includeSkus, + extraArticles, allowParent, categories, offset: 0, diff --git a/src/components/manifest/ArticleSearchDialog.jsx b/src/components/manifest/ArticleSearchDialog.jsx index 306eee6..8774ccb 100644 --- a/src/components/manifest/ArticleSearchDialog.jsx +++ b/src/components/manifest/ArticleSearchDialog.jsx @@ -11,6 +11,8 @@ import Alert from '@mui/material/Alert'; import CloseIcon from '@mui/icons-material/Close'; import ArticleSearch from '../article/ArticleSearch'; +import deliveryEditorAutomat from '../../automats/deliveryEditorAutomat'; + export default class ArticleSearchDialog extends PureComponent { constructor(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) => { const { onAssign, line, kind = 'delivery' } = this.props; const isDelivery = kind === 'delivery'; @@ -41,26 +48,51 @@ export default class ArticleSearchDialog extends PureComponent { await onAssign(article, line); } catch (err) { this.setState({ - assigningSku: null, error: err?.message || (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.'), }); + } finally { + this.setState({ assigningSku: null }); } }; 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 isDelivery = kind === 'delivery'; + const onlySupplierArticles = isDelivery && !line; 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 ( - {line ? 'Existierenden Artikel zuordnen' : 'Artikel auswählen'} + {line + ? 'Existierenden Artikel zuordnen' + : isDelivery + ? 'Lieferanten-Artikel auswählen' + : 'Artikel auswählen'} - {isDelivery ? 'Lieferanten-SKU' : 'SKU'}: {rawSku || '–'} - {line?.name ? ` · ${line.name}` : ''} + {line ? ( + <> + {isDelivery ? 'Lieferanten-SKU' : 'SKU'}: {rawSku || '–'} + {line?.name ? ` · ${line.name}` : ''} + + ) : isDelivery ? ( + 'Nur mit dem ausgewählten Lieferanten verknüpfte Artikel' + ) : ( + 'Artikel für Beleg auswählen' + )} @@ -123,12 +166,21 @@ export default class ArticleSearchDialog extends PureComponent { )} + {onlySupplierArticles && supplierArticlesList.length === 0 && ( + + Für diesen Lieferanten sind noch keine Artikel verknüpft. + + )} + @@ -136,8 +188,7 @@ export default class ArticleSearchDialog extends PureComponent { diff --git a/src/components/manifest/AttachmentList.jsx b/src/components/manifest/AttachmentList.jsx index 0a78474..dfc8506 100644 --- a/src/components/manifest/AttachmentList.jsx +++ b/src/components/manifest/AttachmentList.jsx @@ -1,7 +1,6 @@ import { PureComponent } from 'react'; import Box from '@mui/material/Box'; import Stack from '@mui/material/Stack'; -import Typography from '@mui/material/Typography'; import Attachment from './Attachment'; export class AttachmentList extends PureComponent { @@ -13,14 +12,6 @@ export class AttachmentList extends PureComponent { return ( - - Dateianhänge - {attachments.map((attachment, index) => ( 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 { constructor(props) { super(props); @@ -61,19 +124,23 @@ export default class BookDeliveryDialog extends PureComponent { } const initialWarehouses = warehouseAutomat.state.warehouses || []; + const initialSupplierMap = + props.supplierArticlesMap || + (deliveryEditorAutomat?.state?.supplierArticlesMap ?? {}); this.state = { summary: props.summary ?? null, loading: !props.summary, error: null, amountsToBook: initialAmounts, - transactionText: props.delivery?.number + transactionText: props.delivery?.externalNumber || (props.delivery?.number ? `Lieferung ${props.delivery.number}` - : 'Lieferung Wareneingang', + : 'Lieferung Wareneingang'), isBooking: false, warehouses: initialWarehouses, selectedWarehouseId: initialWarehouses.length === 1 ? initialWarehouses[0].id : '', warehouseError: null, + supplierArticlesMap: initialSupplierMap, }; this.handleAmountChange = this.handleAmountChange.bind(this); @@ -84,6 +151,17 @@ export default class BookDeliveryDialog extends PureComponent { 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() { this.unsubWarehouse = warehouseAutomat.subscribe((state) => { const warehouses = state.warehouses || []; @@ -98,6 +176,7 @@ export default class BookDeliveryDialog extends PureComponent { }); }); void warehouseAutomat.actions.load().catch(() => {}); + this.loadSupplierArticlesIfNeeded(); if (this.props.open) { void this.loadSummary(); @@ -111,6 +190,9 @@ export default class BookDeliveryDialog extends PureComponent { componentDidUpdate(prevProps) { if (this.props.open && !prevProps.open) { const currentWarehouses = this.state.warehouses || warehouseAutomat.state.warehouses || []; + const currentSupplierMap = + this.props.supplierArticlesMap || + (deliveryEditorAutomat?.state?.supplierArticlesMap ?? this.state.supplierArticlesMap ?? {}); this.setState( { summary: this.props.summary ?? null, @@ -120,13 +202,15 @@ export default class BookDeliveryDialog extends PureComponent { selectedWarehouseId: this.state.selectedWarehouseId || (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 Wareneingang', + : 'Lieferung Wareneingang'), isBooking: false, + supplierArticlesMap: currentSupplierMap, }, () => { void warehouseAutomat.actions.load().catch(() => {}); + this.loadSupplierArticlesIfNeeded(); if (this.props.summary) { this.initAmountsFromSummary(this.props.summary, this.props.prefillLineId); } else { @@ -284,105 +368,111 @@ export default class BookDeliveryDialog extends PureComponent { } } - renderHeaderInfo() { + renderHeaderInfo({ hasTrackedLinesToBook, isWarehouseMissing } = {}) { 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 ( - - - - Nummer - - - {delivery?.number || '–'} - + + + + + - {delivery?.externalNumber && ( - - - Referenz - - - {delivery.externalNumber} - - - )} - - - - Lieferdatum - - - {formatDate(delivery?.date)} - + + + + + Ziellager{hasTrackedLinesToBook ? ' *' : ''} + + + {warehouseError && ( + {warehouseError} + )} + - - {partyName && ( - - - Lieferant - - - {partyName} - - - )} - - - - Aktueller Status: - - {summary?.status === 'booked' && ( - } - label="Vollständig gebucht" - color="success" - variant="outlined" - sx={{ fontWeight: 600 }} - /> - )} - {summary?.status === 'partial' && ( - } - label={`Teilweise gebucht (${summary.bookedPositions}/${summary.totalPositions})`} - color="warning" - variant="outlined" - sx={{ fontWeight: 600 }} - /> - )} - {summary?.status === 'unbooked' && ( - - )} - {summary?.status === 'empty' && ( - - )} - - + ); } renderLinesTable() { - const { summary, amountsToBook, isBooking } = this.state; + const { summary, amountsToBook, isBooking, supplierArticlesMap } = this.state; const lines = summary?.lines || []; if (lines.length === 0) { @@ -398,7 +488,10 @@ export default class BookDeliveryDialog extends PureComponent { - SKU + Pos. + Lieferanten-SKU + Eigene SKU + EAN Artikel Liefermenge Bereits gebucht @@ -406,29 +499,45 @@ export default class BookDeliveryDialog extends PureComponent { - {lines.map((line) => { + {lines.map((line, index) => { const toBook = amountsToBook[line.id] ?? 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 ( - - {line.sku || '–'} + + {posNumber} - - - - {line.name || '–'} - - {!line.trackStock && ( - - )} - + + {supplierSku} + + + {ownSku} + + 1 ? eans.join(', ') : undefined} + > + {primaryEan || '–'} + + + + {line.name || '–'} + @@ -524,7 +633,7 @@ export default class BookDeliveryDialog extends PureComponent { - Lieferung {delivery?.number || ''} verbuchen + Lieferung verbuchen @@ -546,8 +655,8 @@ export default class BookDeliveryDialog extends PureComponent { - - {this.renderHeaderInfo()} + + {this.renderHeaderInfo({ hasTrackedLinesToBook, isWarehouseMissing })} {loading ? ( @@ -568,125 +677,21 @@ export default class BookDeliveryDialog extends PureComponent { )} {this.renderLinesTable()} - {!isFullyBooked && hasLines && ( - - - - - )} - {hasTrackedLinesToBook && warehouses.length === 0 && !isFullyBooked && ( Es ist noch kein Lager eingerichtet. Für lagergeführte Artikel legen Sie bitte unter „Artikel → Lager“ zuerst ein Lager an. )} - {hasLines && ( - - {hasTrackStock && ( - - - Ziellager {hasTrackedLinesToBook ? '*' : ''} - - - {warehouseError ? ( - {warehouseError} - ) : ( - - {hasTrackedLinesToBook - ? 'Pflichtfeld: Ziellager für lagergeführte Artikel' - : 'Ziellager (optional)'} - - )} - - )} - - - - )} - {isFullyBooked ? ( }> Alle Positionen dieser Lieferung sind bereits vollständig im Ledger verbucht. - ) : countLinesToBook > 0 ? ( - - {isWarehouseMissing ? ( - <> - Bitte wählen Sie ein Ziellager aus, um die lagergeführten Artikel zu buchen. - - ) : ( - <> - Es werden {totalToBook} Stück für{' '} - {countLinesToBook} Position{countLinesToBook === 1 ? '' : 'en'}{' '} - {hasTrackedLinesToBook && selectedWarehouse ? ( - <>in das Lager „{selectedWarehouse?.name || '–'}“ verbucht. - ) : ( - <>als Dokumentenbuchung im Ledger verbucht (ohne Lagerbestandsführung). - )} - - )} + ) : isWarehouseMissing ? ( + + Bitte wählen Sie ein Ziellager aus, um die lagergeführten Artikel zu buchen. - ) : hasLines ? ( + ) : countLinesToBook === 0 && hasLines ? ( Wählen Sie mindestens eine Menge größer als 0 aus, um Positionen zu verbuchen. diff --git a/src/components/manifest/ManifestFormLine.jsx b/src/components/manifest/ManifestFormLine.jsx index 02fc1f8..3153501 100644 --- a/src/components/manifest/ManifestFormLine.jsx +++ b/src/components/manifest/ManifestFormLine.jsx @@ -251,18 +251,18 @@ class ManifestFormLine extends PureComponent { '&:hover': isClickable ? { bgcolor: 'rgba(21, 128, 61, 0.16)' } : 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, - }), + ? { + 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, @@ -588,10 +588,10 @@ class ManifestFormLine extends PureComponent { vkPrice?.net != null ? Number(vkPrice.net) : cachedArticle.net != null - ? Number(cachedArticle.net) - : cachedArticle.price != null - ? Number(cachedArticle.price) - : null; + ? Number(cachedArticle.net) + : cachedArticle.price != null + ? Number(cachedArticle.price) + : null; const vkTaxRate = vkPrice?.taxRate ?? vkPrice?.taxrate ?? @@ -601,12 +601,12 @@ class ManifestFormLine extends PureComponent { 19; const vkBrutto = vkPrice ? priceBrutto(vkPrice) ?? - (vkNetto != null && vkTaxRate != null - ? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100 - : null) + (vkNetto != null && vkTaxRate != null + ? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100 + : null) : vkNetto != null - ? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100 - : null; + ? Math.round((vkNetto * (1 + Number(vkTaxRate) / 100) + Number.EPSILON) * 100) / 100 + : null; const suppliers = Array.isArray(cachedArticle.suppliers) && cachedArticle.suppliers.length > 0 @@ -1206,45 +1206,35 @@ class ManifestFormLine extends PureComponent { > {this.props.kind === 'delivery' && Boolean(this.props.onBookLine) && ( - 0 ? `${ledgerLine.remainingAmount} Stk.` : ''})` - } - > - - { - e.stopPropagation(); - if (ledgerLine) { - this.props.onBookLine(ledgerLine); - } - }} - color={ - !ledgerLine - ? 'default' - : ledgerLine.isBooked + + { + 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 ? ( - - ) : ( - - )} - - - + ? 'warning' + : 'primary' + } + sx={{ p: 0.35 }} + > + {ledgerLine?.isBooked ? ( + + ) : ( + + )} + + )} {this.props.onMove && ( <> diff --git a/src/components/manifest/ManifestHeader.jsx b/src/components/manifest/ManifestHeader.jsx index 0870282..e5c7eaf 100644 --- a/src/components/manifest/ManifestHeader.jsx +++ b/src/components/manifest/ManifestHeader.jsx @@ -5,7 +5,6 @@ import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; -import Chip from '@mui/material/Chip'; import UploadFileIcon from '@mui/icons-material/UploadFile'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; @@ -277,8 +276,8 @@ class ManifestHeader extends PureComponent { return ( <> - {/* Tile 1: 3-wide Kopfdaten Tile */} - + {/* Tile 1: 3-wide Tile */} + ) : null} - {docId != null && kind === 'delivery' && ledgerSummary && ( - - ) : ( - - ) - } - 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, - }} - /> - )} = 4 ? { gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' } } : {}), ...(!hasArticles @@ -115,17 +115,7 @@ class ManifestLines extends PureComponent { } if (!lines || lines.length === 0) { - return ( - - - Keine Positionen vorhanden - - - ); + return null; } return lines.map((line, index) => { @@ -190,16 +180,24 @@ class ManifestLines extends PureComponent { > Zeile - {onSearchArticle && ( - - )} + {onSearchArticle && (() => { + const searchArticleDisabled = saving || (kind === 'delivery' && !partyId); + const searchArticleTitle = + kind === 'delivery' && !partyId + ? 'Bitte zuerst einen Lieferanten auswählen' + : undefined; + return ( + + ); + })()} {(() => { const hasShippedLines = kind === 'order' && Array.isArray(shipments) && shipments.some((sh) => (sh.lines?.length || 0) > 0); return ( diff --git a/src/routes/lieferung/neu.jsx b/src/routes/lieferung/neu.jsx index f5f674e..859161e 100644 --- a/src/routes/lieferung/neu.jsx +++ b/src/routes/lieferung/neu.jsx @@ -431,18 +431,67 @@ class LieferungNeuPage extends PureComponent { async handleAssignArticleForLine(article, line) { const { partyId } = this.state; if (!partyId) { - enqueueSnackbar('Bitte wählen Sie zuerst einen Lieferanten aus.', { variant: 'warning' }); - return; + const msg = 'Bitte wählen Sie zuerst einen Lieferanten aus.'; + enqueueSnackbar(msg, { variant: 'warning' }); + throw new Error(msg); } const internalSku = article?.sku; if (!internalSku) { - enqueueSnackbar('Artikel hat keine SKU.', { variant: 'error' }); - return; + const msg = 'Artikel hat keine SKU.'; + enqueueSnackbar(msg, { variant: 'error' }); + throw new Error(msg); } 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 { await articleApi.addSupplier(internalSku, { entityId: partyId, @@ -454,9 +503,15 @@ class LieferungNeuPage extends PureComponent { }); if (supplierSku) { - this.automat.actions.fetchSupplierArticleForSku(supplierSku, line.key); - } else { - this.automat.actions.fetchSupplierArticles(partyId); + await this.automat.actions.fetchSupplierArticleForSku(supplierSku, line.key); + } + await this.automat.actions.fetchSupplierArticles(partyId); + + if (!line.name && article.name) { + this.automat.actions.updateLineitem({ + ...line, + name: article.name, + }); } enqueueSnackbar( @@ -1131,6 +1186,7 @@ class LieferungNeuPage extends PureComponent { columns={DELIVERY_LINE_COLUMNS} lines={lines} saving={saving} + partyId={this.state.partyId} wide={ this.state.viewerPosition === 'right' && this.isViewerOpen() ? 2 @@ -1180,6 +1236,8 @@ class LieferungNeuPage extends PureComponent { open={this.state.articleSearchDialogOpen} line={this.state.articleSearchLine} kind="delivery" + partyId={this.state.partyId} + supplierArticles={this.state.supplierArticles} onClose={this.handleCloseArticleSearch} onAssign={this.handleAssignArticleForLine} /> @@ -1197,6 +1255,7 @@ class LieferungNeuPage extends PureComponent { summary={this.state.ledgerSummary} prefillLineId={this.state.bookDeliveryPrefillLineId} partyName={this.state.deliveryEntities?.find((p) => p.id === this.state.partyId)?.name} + supplierArticlesMap={this.state.supplierArticlesMap} onClose={this.handleCloseBookDialog} onBookSuccess={this.handleBookSuccess} /> diff --git a/src/utils/deliveryLedger.js b/src/utils/deliveryLedger.js index a38228c..5fbe909 100644 --- a/src/utils/deliveryLedger.js +++ b/src/utils/deliveryLedger.js @@ -129,6 +129,7 @@ export async function fetchDeliveryLedgerSummary(deliveryId, { deliveryApi = def return { ...line, + article, targetAmount, bookedAmount, remainingAmount,