diff --git a/src/components/manifest/BookDeliveryDialog.jsx b/src/components/manifest/BookDeliveryDialog.jsx index 4e784c5..296bea7 100644 --- a/src/components/manifest/BookDeliveryDialog.jsx +++ b/src/components/manifest/BookDeliveryDialog.jsx @@ -27,6 +27,7 @@ import CloseIcon from '@mui/icons-material/Close'; import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; import WarehouseIcon from '@mui/icons-material/Warehouse'; +import AddIcon from '@mui/icons-material/Add'; import { enqueueSnackbar } from 'notistack'; import { notifyError } from '../../utils/notify'; import { formatDate } from './manifestConfig'; @@ -34,7 +35,8 @@ import { fetchDeliveryLedgerSummary, bookDeliveryLines, } from '../../utils/deliveryLedger'; -import { getArticle } from '../../automats/artikelAutomat'; +import { formatStock } from '../article/searchFormat'; +import artikelAutomat, { getArticle } from '../../automats/artikelAutomat'; import warehouseAutomat from '../../automats/warehouseAutomat'; import { supplier as supplierApi } from '../../api'; import deliveryEditorAutomat, { buildSupplierArticlesMap } from '../../automats/deliveryEditorAutomat'; @@ -141,6 +143,7 @@ export default class BookDeliveryDialog extends PureComponent { selectedWarehouseId: initialWarehouses.length === 1 ? initialWarehouses[0].id : '', warehouseError: null, supplierArticlesMap: initialSupplierMap, + enablingTrackStockBySku: {}, }; this.handleAmountChange = this.handleAmountChange.bind(this); @@ -149,6 +152,7 @@ export default class BookDeliveryDialog extends PureComponent { this.handleBookAll = this.handleBookAll.bind(this); this.handleResetAmounts = this.handleResetAmounts.bind(this); this.handleConfirmBook = this.handleConfirmBook.bind(this); + this.handleEnableTrackStock = this.handleEnableTrackStock.bind(this); } loadSupplierArticlesIfNeeded() { @@ -175,6 +179,9 @@ export default class BookDeliveryDialog extends PureComponent { }; }); }); + this.unsubArticle = artikelAutomat.subscribe(() => { + this.forceUpdate(); + }); void warehouseAutomat.actions.load().catch(() => {}); this.loadSupplierArticlesIfNeeded(); @@ -185,6 +192,75 @@ export default class BookDeliveryDialog extends PureComponent { componentWillUnmount() { this.unsubWarehouse?.(); + this.unsubArticle?.(); + } + + async handleEnableTrackStock(line, targetSku) { + if (!targetSku) return; + this.setState((prev) => ({ + enablingTrackStockBySku: { + ...prev.enablingTrackStockBySku, + [targetSku]: true, + }, + })); + + try { + await artikelAutomat.actions.setTrackStock(targetSku, true); + this.setState((prev) => { + if (!prev.summary?.lines) { + return { + enablingTrackStockBySku: { + ...prev.enablingTrackStockBySku, + [targetSku]: false, + }, + }; + } + const updatedLines = prev.summary.lines.map((l) => { + const rawSku = l.sku ? String(l.sku).trim() : ''; + const lineOwnSku = l.articleSku || l.article?.sku || ''; + if ( + l.id === line.id || + rawSku === targetSku || + lineOwnSku === targetSku || + (l.article && String(l.article.sku) === targetSku) + ) { + return { + ...l, + trackStock: true, + article: l.article + ? { ...l.article, track_stock: true } + : { sku: targetSku, track_stock: true }, + }; + } + return l; + }); + + return { + summary: { + ...prev.summary, + hasTrackStock: true, + lines: updatedLines, + }, + enablingTrackStockBySku: { + ...prev.enablingTrackStockBySku, + [targetSku]: false, + }, + }; + }); + enqueueSnackbar(`Lagerbestand für „${targetSku}“ aktiviert.`, { variant: 'success' }); + } catch (err) { + console.error('[BookDeliveryDialog] Failed to enable track_stock:', err); + enqueueSnackbar( + err?.message || `Fehler beim Aktivieren des Lagerbestands für „${targetSku}“.`, + { variant: 'error' } + ); + this.setState((prev) => ({ + enablingTrackStockBySku: { + ...prev.enablingTrackStockBySku, + [targetSku]: false, + }, + })); + } } componentDidUpdate(prevProps) { @@ -472,7 +548,7 @@ export default class BookDeliveryDialog extends PureComponent { } renderLinesTable() { - const { summary, amountsToBook, isBooking, supplierArticlesMap } = this.state; + const { summary, amountsToBook, isBooking, supplierArticlesMap, enablingTrackStockBySku } = this.state; const lines = summary?.lines || []; if (lines.length === 0) { @@ -493,6 +569,7 @@ export default class BookDeliveryDialog extends PureComponent { Eigene SKU EAN Artikel + Bestand Liefermenge Bereits gebucht Jetzt verbuchen @@ -512,6 +589,46 @@ export default class BookDeliveryDialog extends PureComponent { line.article || (ownSku && ownSku !== '–' ? getArticle(ownSku) : null) || (line.sku ? getArticle(line.sku) : null); + const targetSku = + cachedArticle?.sku || + (ownSku && ownSku !== '–' ? ownSku : null) || + (line.sku ? String(line.sku).trim() : null); + + if (targetSku && !cachedArticle) { + void artikelAutomat.actions.loadArticle(targetSku).catch(() => {}); + } + + const isTracked = + Boolean(line.trackStock) || + (cachedArticle?.track_stock !== undefined + ? cachedArticle.track_stock !== false && + cachedArticle.track_stock !== 0 && + cachedArticle.track_stock !== '0' + : false); + + const isEnablingThis = targetSku ? Boolean(enablingTrackStockBySku?.[targetSku]) : false; + + let currentStock = null; + if (cachedArticle) { + if (Array.isArray(cachedArticle.warehouses) && cachedArticle.warehouses.length > 0) { + currentStock = cachedArticle.warehouses.reduce( + (sum, wh) => sum + (Number(wh.stock) || 0), + 0 + ); + } else if (cachedArticle.stock != null) { + currentStock = Number(cachedArticle.stock); + } + } else if (line.stock != null) { + currentStock = Number(line.stock); + } + + const warehouseBreakdown = + Array.isArray(cachedArticle?.warehouses) && cachedArticle.warehouses.length > 0 + ? cachedArticle.warehouses + .map((w) => `${w.name || 'Lager'}: ${formatStock(w.stock ?? 0)}`) + .join(' • ') + : null; + const eans = line.ean ? [line.ean] : line.eans || cachedArticle?.eans || cachedArticle?.articleean || []; @@ -539,6 +656,57 @@ export default class BookDeliveryDialog extends PureComponent { {line.name || '–'} + + {isTracked ? ( + + {currentStock != null ? formatStock(currentStock) : '0'} + + ) : targetSku ? ( + + ) : ( + + — + + )} + {line.targetAmount} diff --git a/src/components/manifest/ManifestFormLine.jsx b/src/components/manifest/ManifestFormLine.jsx index 3153501..8b59624 100644 --- a/src/components/manifest/ManifestFormLine.jsx +++ b/src/components/manifest/ManifestFormLine.jsx @@ -212,7 +212,9 @@ class ManifestFormLine extends PureComponent { ? `Gebucht: ${ledgerLine.bookedAmount} Stk.` : isPartial ? `Gebucht: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.` - : 'Verbuchen: offen'; + : null; + + if (!label) return null; return ( @@ -1158,9 +1160,11 @@ class ManifestFormLine extends PureComponent { } if (this.props.ledgerLine) { + const chip = this.renderLedgerChip(); + if (!chip) return null; return ( - {this.renderLedgerChip()} + {chip} ); }