import { Fragment, PureComponent } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import Chip from '@mui/material/Chip'; import CircularProgress from '@mui/material/CircularProgress'; import IconButton from '@mui/material/IconButton'; import TableCell from '@mui/material/TableCell'; import TableRow from '@mui/material/TableRow'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import HelpOutlineIcon from '@mui/icons-material/HelpOutlineOutlined'; import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; import AddIcon from '@mui/icons-material/Add'; import SearchIcon from '@mui/icons-material/Search'; import LinkOffOutlinedIcon from '@mui/icons-material/LinkOffOutlined'; import { getFirstVal, parseNum, LINE_COLUMNS } from './manifestConfig'; import ArticlePhoto from '../article/ArticlePhoto'; import { formatPrice, grossFromNet } from '../../utils/price'; import { formatStock } from '../article/searchFormat'; import { primaryPrice, priceBrutto } from '../../utils/articleDetail'; import artikelAutomat, { createArticleSelector, getArticle } from '../../automats/artikelAutomat'; import { artikelDetailPath } from '../article/articleRoute'; import { BrandLabel } from '../brand'; import { articleSupplierAutomat } from '../../automats/articleDetailAutomat'; import { selectCompanyTaxrate } from '../../automats/companyAutomat'; import { BORDER, GREEN, alpha, } from '../../colors'; const LOCKED_ROW_BG = alpha(GREEN, 0.08); const LOCKED_ROW_HOVER_BG = alpha(GREEN, 0.12); const LOCKED_CELL_BORDER = alpha(GREEN, 0.32); const LOCKED_CELL_BG = alpha(GREEN, 0.08); class ManifestFormLine extends PureComponent { constructor(props) { super(props); const sku = this.getArticleSku(props); this.state = { cachedArticle: sku ? getArticle(sku) : null, loadingArticle: false, fallbackSuppliers: [], editingField: null, }; this.handleRemove = this.handleRemove.bind(this); this.handleMoveUp = this.handleMoveUp.bind(this); this.handleMoveDown = this.handleMoveDown.bind(this); this.handleBlur = this.handleBlur.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.handleStartEdit = this.handleStartEdit.bind(this); this.handleCreateArticle = this.handleCreateArticle.bind(this); this.handleSearchArticle = this.handleSearchArticle.bind(this); this.handleRemoveSupplierArticle = this.handleRemoveSupplierArticle.bind(this); } getArticleSku(props = this.props) { if (props.kind === 'delivery') { return props.supplierArticle?.article?.sku || null; } return props.line?.sku ? String(props.line.sku).trim() : null; } isFullyLedgered(props = this.props) { const { ledgerLine, line } = props; return Boolean( ledgerLine?.isBooked || (ledgerLine && ledgerLine.targetAmount > 0 && ledgerLine.remainingAmount <= 0) || line?.isBooked || line?.is_booked ); } componentDidMount() { this.subscribeArticle(this.getArticleSku()); } componentDidUpdate(prevProps) { const prevSku = this.getArticleSku(prevProps); const nextSku = this.getArticleSku(this.props); if (prevSku !== nextSku) { this.subscribeArticle(nextSku); } } componentWillUnmount() { this.unsubArticle?.(); this.unsubSupplier?.(); } subscribeArticle(sku) { this.unsubArticle?.(); this.unsubArticle = null; this.unsubSupplier?.(); this.unsubSupplier = null; if (!sku) { if (this.state.cachedArticle || this.state.loadingArticle || this.state.fallbackSuppliers?.length) { this.setState({ cachedArticle: null, loadingArticle: false, fallbackSuppliers: [] }); } return; } const current = getArticle(sku); if (current) { this.setState({ cachedArticle: current, loadingArticle: false }); } else { this.setState({ cachedArticle: null, loadingArticle: true }); artikelAutomat.actions.loadArticle(sku) .then((art) => { if (this.getArticleSku() === sku) { this.setState({ cachedArticle: art, loadingArticle: false }); } }) .catch(() => { if (this.getArticleSku() === sku) { this.setState({ cachedArticle: null, loadingArticle: false }); } }); } this.unsubArticle = artikelAutomat.subscribe(this, (state) => { const art = createArticleSelector(sku)(state); return { cachedArticle: art ?? null, ...(art ? { loadingArticle: false } : {}), }; }); // Fallback: subscribe to articleSupplierAutomat if cachedArticle doesn't have suppliers if (!current?.suppliers?.length) { const supKey = articleSupplierAutomat.key(sku); this.unsubSupplier = supKey.subscribe(this, (item) => ({ fallbackSuppliers: Array.isArray(item?.data) ? item.data : [], })); void articleSupplierAutomat.load(sku).catch(() => { }); } } handleRemove() { if (this.props.isOnShipment || this.isFullyLedgered()) return; this.props.onRemove(this.props.line.key); } handleMoveUp() { this.props.onMove?.(this.props.line.key, -1); } handleMoveDown() { this.props.onMove?.(this.props.line.key, 1); } handleStartEdit(columnId) { if (columnId === 'pos' || columnId === 'shippable') return; const { saving } = this.props; const isFullyLedgered = this.isFullyLedgered(); const isDisabled = saving || isFullyLedgered || (columnId === 'sku' && Boolean(this.props.isOnShipment)); if (isDisabled) return; if (this.state.editingField !== columnId) { this.setState({ editingField: columnId }); } } handleKeyDown(e) { if (e.key === 'Enter' || e.key === 'Escape') { e.target.blur(); } } handleBlur(e, columnId) { if (this.state.editingField === columnId) { this.setState({ editingField: null }); } if (columnId === 'sku' && this.props.onSkuBlur) { this.props.onSkuBlur(this.props.line.key, e.target.value); } } handleCreateArticle() { if (this.isFullyLedgered()) return; const { line, onCreateArticle } = this.props; if (onCreateArticle) { onCreateArticle(line); } } handleSearchArticle() { if (this.isFullyLedgered()) return; const { line, onSearchArticle } = this.props; if (onSearchArticle) { onSearchArticle(line); } } handleRemoveSupplierArticle() { if (this.isFullyLedgered()) return; const { line, supplierArticle, onRemoveSupplierArticle } = this.props; if (onRemoveSupplierArticle) { onRemoveSupplierArticle(line, supplierArticle); } } renderField(column, colIdx, totalCols) { const { id, align, type } = column; const { line, saving, onChange, columns } = this.props; const isFirstLine = Boolean(this.props.isFirstLine ?? (this.props.index === 0)); const isLastLine = Boolean(this.props.isLastLine); if (id === 'pos') { const posVal = this.props.positionIndex ?? (this.props.index != null ? this.props.index + 1 : colIdx + 1); return ( {posVal} ); } if (id === 'shippable') { const isShippable = line.shippable !== false; const isFullyLedgered = this.isFullyLedgered(); const isDisabled = saving || isFullyLedgered; const hasPosCol = columns?.[0]?.id === 'pos'; const isFirstInput = hasPosCol ? colIdx === 1 : colIdx === 0; const isLast = colIdx === totalCols - 1; const topLeftRadius = isFirstInput && isFirstLine ? 4 : 0; const topRightRadius = isLast && isFirstLine ? 4 : 0; const bottomRightRadius = isLast && isLastLine ? 4 : 0; const bottomLeftRadius = isFirstInput && isLastLine ? 4 : 0; const borderRadius = `${topLeftRadius}px ${topRightRadius}px ${bottomRightRadius}px ${bottomLeftRadius}px`; const { dirtyFields } = this.props; const isFieldDirty = Boolean(dirtyFields && (dirtyFields.__all__ || dirtyFields[id])); return ( onChange(line.key, 'shippable', !isShippable)} > { e.stopPropagation(); onChange(line.key, 'shippable', e.target.checked); }} slotProps={{ input: { 'aria-label': isShippable ? 'Lieferbar' : 'Nicht lieferbar' }, }} sx={{ p: 0.5 }} /> ); } let val = line[id]; if (val == null || val === '') { if (id === 'name' && this.state.cachedArticle?.name) { val = this.state.cachedArticle.name; } else if (id === 'tax' || id === 'taxrate') { val = line.taxrate ?? line.tax ?? ''; } else if (id === 'net' || id === 'netprice') { val = line.netprice ?? line.net ?? ''; } else if (id === 'gross') { const netNum = parseNum(line.netprice ?? line.net); const taxNum = parseNum(line.taxrate ?? line.tax) ?? 0; const rebateNum = parseNum(line.rebate) ?? 0; if (netNum != null) { val = String(grossFromNet(netNum, taxNum, rebateNum)); } } } let isError = false; if (id === 'name') { isError = !String(line.name ?? '').trim() && !String(line.sku ?? '').trim(); } else if (id === 'amount') { const raw = getFirstVal(line.amount, line.count); isError = raw == null || parseNum(raw) == null; } else if (id === 'pu') { const raw = getFirstVal(line.pu, line.vpe); isError = raw == null || parseNum(raw) == null; } else if (id === 'tax' || id === 'taxrate') { const raw = getFirstVal(line.tax, line.taxrate, line.taxRate, line.mwst); isError = raw == null || parseNum(raw) == null; } else if (id === 'net' || id === 'netprice') { const raw = getFirstVal(line.net, line.netprice, line.netPrice, line.eknetto); isError = raw == null || parseNum(raw) == null; } else if (id === 'gross') { const raw = getFirstVal(line.gross, val); isError = raw == null || parseNum(raw) == null; } else if (id === 'sku') { isError = !String(line.sku ?? '').trim() && !String(line.name ?? '').trim(); } const hasPosCol = columns?.[0]?.id === 'pos'; const isFirstInput = hasPosCol ? colIdx === 1 : colIdx === 0; const isLast = colIdx === totalCols - 1; const topLeftRadius = isFirstInput && isFirstLine ? 4 : 0; const topRightRadius = isLast && isFirstLine ? 4 : 0; const bottomRightRadius = isLast && isLastLine ? 4 : 0; const bottomLeftRadius = isFirstInput && isLastLine ? 4 : 0; const borderRadius = `${topLeftRadius}px ${topRightRadius}px ${bottomRightRadius}px ${bottomLeftRadius}px`; const { dirtyFields } = this.props; const isFieldDirty = Boolean( dirtyFields && (dirtyFields.__all__ || dirtyFields[id]) ); const isEditing = this.state.editingField === id; const isFullyLedgered = this.isFullyLedgered(); const isDisabled = saving || isFullyLedgered || (id === 'sku' && Boolean(this.props.isOnShipment)); const titleText = isFullyLedgered ? 'Position ist bereits vollständig im Ledger verbucht und kann nicht geändert werden' : id === 'sku' && this.props.isOnShipment ? 'Artikel ist bereits auf einem Lieferschein und kann nicht geändert werden' : undefined; if (isEditing) { return ( onChange(line.key, id, e.target.value)} onBlur={(e) => this.handleBlur(e, id)} onKeyDown={this.handleKeyDown} slotProps={{ htmlInput: { inputMode: type === 'number' ? 'decimal' : 'text', style: { textAlign: align }, autoComplete: 'off', step: type === 'number' ? 'any' : undefined, }, }} sx={{ position: 'relative', ml: isFirstInput ? 0 : '-1px', width: isFirstInput ? '100%' : 'calc(100% + 1px)', mt: isFirstLine ? 0 : '-1px', height: isFirstLine ? 40 : 41, zIndex: 3, '& .MuiOutlinedInput-root': { height: isFirstLine ? 40 : 41, borderRadius, width: '100%', ...(isFieldDirty && { bgcolor: 'rgba(237, 108, 2, 0.08)', }), '&:hover': { zIndex: 4, }, '&.Mui-focused': { zIndex: 5, }, }, '& .MuiOutlinedInput-notchedOutline': { borderWidth: '1px !important', borderColor: isFieldDirty ? 'warning.main' : isError ? 'error.main' : 'rgba(0, 0, 0, 0.23)', }, '&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderWidth: '1px !important', borderColor: isFieldDirty ? 'warning.main' : isError ? 'error.main' : 'primary.main', }, '&:hover .MuiOutlinedInput-notchedOutline': { borderWidth: '1px !important', borderColor: isFieldDirty ? 'warning.main' : isError ? 'error.main' : 'rgba(0, 0, 0, 0.87)', }, '& input[type=number]': { MozAppearance: 'textfield', }, '& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button': { WebkitAppearance: 'none', margin: 0, }, }} /> ); } const hasValue = val != null && String(val).trim() !== ''; return ( this.handleStartEdit(id)} onFocus={isDisabled ? undefined : () => this.handleStartEdit(id)} onKeyDown={(e) => { if (isDisabled) return; if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.handleStartEdit(id); } }} title={titleText} sx={{ display: 'flex', alignItems: 'center', justifyContent: align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start', height: isFirstLine ? 40 : 41, minHeight: isFirstLine ? 40 : 41, maxHeight: isFirstLine ? 40 : 41, boxSizing: 'border-box', position: 'relative', ml: isFirstInput ? 0 : '-1px', width: isFirstInput ? '100%' : 'calc(100% + 1px)', mt: isFirstLine ? 0 : '-1px', px: '14px', py: 0, borderRadius, border: '1px solid', borderColor: isFieldDirty ? 'warning.main' : isError ? 'error.main' : isFullyLedgered ? LOCKED_CELL_BORDER : 'rgba(0, 0, 0, 0.23)', cursor: isDisabled ? 'default' : 'text', touchAction: 'manipulation', userSelect: 'none', zIndex: 1, ...(isFullyLedgered && !isFieldDirty && !isError && { bgcolor: LOCKED_CELL_BG, }), ...(isFieldDirty && { bgcolor: 'rgba(237, 108, 2, 0.08)', zIndex: 2, }), ...(isError && !isFieldDirty && { bgcolor: 'rgba(211, 47, 47, 0.04)', borderColor: 'error.main', zIndex: 2, }), '&:hover': isDisabled ? undefined : { bgcolor: isFieldDirty ? 'rgba(237, 108, 2, 0.14)' : isError ? 'rgba(211, 47, 47, 0.08)' : 'action.hover', borderColor: isFieldDirty ? 'warning.main' : isError ? 'error.main' : 'rgba(0, 0, 0, 0.87)', zIndex: 3, }, '&:focus-visible': { outline: '2px solid', outlineColor: 'primary.main', outlineOffset: -2, zIndex: 4, }, }} > {hasValue ? String(val) : '—'} ); } renderSecondLine() { const { kind = 'delivery' } = this.props; const rawSku = this.props.line?.sku != null ? String(this.props.line.sku).trim() : ''; if (kind === 'delivery') { return this.renderDeliverySecondLine(rawSku); } return this.renderArticleSecondLine(rawSku); } renderArticleSecondLine(rawSku) { const { loading, kind, columns } = this.props; const isOrder = kind === 'order'; const hasNameColumn = columns?.some((c) => c.id === 'name'); const { cachedArticle, loadingArticle } = this.state; if (loading || loadingArticle) { return ( Artikeldaten werden geladen… ); } if (cachedArticle) { const photo = cachedArticle.photos?.[0] || cachedArticle.articlepic?.[0]; const fullHash = photo?.hash || photo?.fullHash; const thumbHash = photo?.thumbhash || photo?.thumbHash; const thumbW = photo?.thumbw || photo?.thumbW; const thumbH = photo?.thumbh || photo?.thumbH; const hasPhoto = fullHash != null || thumbHash != null; const eans = cachedArticle.eans ?? cachedArticle.articleean ?? []; const primaryEan = Array.isArray(eans) ? eans[0] : eans; const vkPrice = primaryPrice(cachedArticle); const vkNetto = vkPrice?.net != null ? Number(vkPrice.net) : cachedArticle.net != null ? Number(cachedArticle.net) : cachedArticle.price != null ? Number(cachedArticle.price) : null; const vkTaxRate = vkPrice?.taxRate ?? vkPrice?.taxrate ?? cachedArticle.taxrate ?? cachedArticle.taxRate ?? selectCompanyTaxrate() ?? 19; const vkBrutto = vkPrice ? priceBrutto(vkPrice) ?? (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; const suppliers = Array.isArray(cachedArticle.suppliers) && cachedArticle.suppliers.length > 0 ? cachedArticle.suppliers : (this.state.fallbackSuppliers ?? []); const topSupplier = suppliers.find((s) => s.sort === 0) ?? (suppliers.length > 0 ? [...suppliers].sort((a, b) => (Number(a.sort) || 0) - (Number(b.sort) || 0))[0] : null); const ekNetto = topSupplier?.price?.net != null ? Number(topSupplier.price.net) : topSupplier?.netPrice != null ? Number(topSupplier.netPrice) : topSupplier?.net != null ? Number(topSupplier.net) : null; const ekTaxRate = topSupplier?.price?.taxRate != null ? Number(topSupplier.price.taxRate) : topSupplier?.taxRate != null ? Number(topSupplier.taxRate) : parseNum(this.props.line?.taxrate ?? this.props.line?.tax) ?? selectCompanyTaxrate() ?? 19; const ekBrutto = ekNetto != null ? Math.round((ekNetto * (1 + Number(ekTaxRate) / 100) + Number.EPSILON) * 100) / 100 : null; const marge = vkNetto > 0 && ekNetto != null ? ((vkNetto - ekNetto) / vkNetto) * 100 : vkBrutto > 0 && ekBrutto != null ? ((vkBrutto - ekBrutto) / vkBrutto) * 100 : null; const rebate = parseNum(this.props.line?.rebate) ?? 0; const effectiveNet = vkNetto != null && Number.isFinite(vkNetto) ? vkNetto * (1 - rebate / 100) : null; const effectiveMarge = effectiveNet != null && effectiveNet > 0 && ekNetto != null ? ((effectiveNet - ekNetto) / effectiveNet) * 100 : null; const hasPriceInfo = vkNetto != null || vkBrutto != null || ekNetto != null || ekBrutto != null || kind === 'quote' || kind === 'order'; const rawWarehouses = Array.isArray(cachedArticle.warehouses) ? cachedArticle.warehouses : []; const isTracked = cachedArticle.track_stock ?? cachedArticle.trackStock ?? (rawWarehouses.length > 0); const stockVal = isTracked === false ? null : (cachedArticle.stock != null && !Number.isNaN(Number(cachedArticle.stock)) ? Number(cachedArticle.stock) : (rawWarehouses.length > 0 || isTracked ? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0) : null)); const lineAmount = parseNum(getFirstVal(this.props.line?.amount, this.props.line?.menge)) ?? 0; const undeliveredOrders = Array.isArray(cachedArticle.undeliveredOrders) ? cachedArticle.undeliveredOrders : []; const currentDocId = this.props.docId != null ? Number(this.props.docId) : (this.props.line?.manifestId != null ? Number(this.props.line.manifestId) : null); const otherOrders = currentDocId != null && kind === 'order' ? undeliveredOrders.filter((ord) => Number(ord.orderId) !== currentDocId) : undeliveredOrders; const reservedInOtherOrders = otherOrders.reduce( (sum, ord) => sum + (Number(ord.amount) || 0), 0, ); const reservedTitle = isTracked !== false && reservedInOtherOrders > 0 ? `Bereits in anderen Aufträgen reserviert:\n${otherOrders .map((ord) => `• ${ord.orderNumber || (ord.orderId ? `Auftrag #${ord.orderId}` : 'Auftrag')}: ${formatStock(ord.amount)}${ord.customerName ? ` (${ord.customerName})` : ''}`) .join('\n')}` : undefined; return ( {/* Article Photo Thumbnail */} {hasPhoto ? ( ) : ( )} {/* Internal Article SKU */} {cachedArticle.sku && ( )} {/* Brand */} {cachedArticle.brand?.name && ( )} {/* Article Name: omit if already editable in line 1 or if kind is order */} {!hasNameColumn && !isOrder && cachedArticle.name && ( {cachedArticle.name} )} {/* Primary EAN: omit for order */} {!isOrder && primaryEan && ( EAN: {primaryEan} )} {/* EK Netto, EK Brutto, VK Netto, VK Brutto */} {hasPriceInfo && ( {/* Bestand */} (stockVal - reservedInOtherOrders) ? 'warning.main' : 'text.secondary', fontSize: '0.72rem', }} > Bestand: {isTracked === false ? '∞' : formatStock(stockVal)} {isTracked !== false && reservedInOtherOrders > 0 ? ` (${formatStock(reservedInOtherOrders)} reserviert)` : ''} {/* Marge */} Marge: {marge != null ? `${Math.floor(marge)}%` : '—'} {/* EK Netto */} EK Netto: {ekNetto != null ? formatPrice(ekNetto) : '—'} {/* EK Brutto */} EK Brutto: {ekBrutto != null ? formatPrice(ekBrutto) : '—'} {/* VK Netto */} VK Netto: {vkNetto != null ? formatPrice(vkNetto) : '—'} {/* VK Brutto */} VK Brutto: {vkBrutto != null ? formatPrice(vkBrutto) : '—'} )} ); } if (rawSku) { return ( {this.props.onSearchArticle && ( )} Kein Artikel für ArtNr „{rawSku}“ gefunden. ); } if (this.props.onSearchArticle) { return ( ); } return null; } renderDeliverySecondLine(rawSku) { const { supplierArticle, loading } = this.props; if (loading) { return ( Artikeldaten werden geladen… ); } if (supplierArticle) { const article = supplierArticle.article || {}; const fullArticle = this.state.cachedArticle || article; const photo = fullArticle.photos?.[0] || fullArticle.articlepic?.[0] || article.photos?.[0] || article.articlepic?.[0]; const fullHash = photo?.hash || photo?.fullHash; const thumbHash = photo?.thumbhash || photo?.thumbHash; const thumbW = photo?.thumbw || photo?.thumbW; const thumbH = photo?.thumbh || photo?.thumbH; const hasPhoto = fullHash != null || thumbHash != null; const eans = fullArticle.eans ?? fullArticle.articleean ?? article.eans ?? article.articleean ?? []; const primaryEan = Array.isArray(eans) ? eans[0] : eans; const priceNet = supplierArticle.price?.net; const taxRate = supplierArticle.price?.taxRate; const vkPrice = primaryPrice(fullArticle); const vkNetto = vkPrice?.net; const vkBrutto = vkPrice ? priceBrutto(vkPrice) ?? (vkPrice.net != null && vkPrice.taxrate != null ? vkPrice.net * (1 + vkPrice.taxrate / 100) : null) : null; const priceNetNum = Number(priceNet); const vkNettoNum = Number(vkNetto); const margeDelivery = vkNettoNum > 0 && priceNet != null && Number.isFinite(priceNetNum) ? ((vkNettoNum - priceNetNum) / vkNettoNum) * 100 : null; const rawWarehouses = Array.isArray(fullArticle.warehouses) ? fullArticle.warehouses : []; const isDeliveryTracked = fullArticle.track_stock ?? fullArticle.trackStock ?? (rawWarehouses.length > 0); const deliveryStockVal = isDeliveryTracked === false ? null : (fullArticle.stock != null && !Number.isNaN(Number(fullArticle.stock)) ? Number(fullArticle.stock) : (rawWarehouses.length > 0 || isDeliveryTracked ? rawWarehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0) : null)); const deliveryUndelivered = Array.isArray(fullArticle.undeliveredOrders) ? fullArticle.undeliveredOrders : []; const reservedInOtherOrdersDelivery = deliveryUndelivered.reduce( (sum, ord) => sum + (Number(ord.amount) || 0), 0, ); const reservedTitleDelivery = isDeliveryTracked !== false && reservedInOtherOrdersDelivery > 0 ? `In Kundenaufträgen reserviert:\n${deliveryUndelivered .map((ord) => `• ${ord.orderNumber || (ord.orderId ? `Auftrag #${ord.orderId}` : 'Auftrag')}: ${formatStock(ord.amount)}${ord.customerName ? ` (${ord.customerName})` : ''}`) .join('\n')}` : undefined; return ( {/* Unlink button at the front */} {this.props.onRemoveSupplierArticle && supplierArticle.id != null && ( )} {/* Article Photo Thumbnail */} {hasPhoto ? ( ) : ( )} {/* Internal Article SKU */} {article.sku && ( )} {/* Brand */} {(fullArticle.brand?.name || article.brand?.name) && ( )} {/* Article Name */} {(fullArticle.name || article.name) && ( {fullArticle.name || article.name} )} {/* Supplier SKU if distinct */} {supplierArticle.sku && supplierArticle.sku !== rawSku && supplierArticle.sku !== article.sku && ( (Lief.-ArtNr: {supplierArticle.sku}) )} {/* Primary EAN */} {primaryEan && ( EAN: {primaryEan} )} {/* Stored Purchase Price (Netto), VK Netto, VK Brutto, Marge & Bestand */} {(priceNet != null || vkNetto != null || vkBrutto != null || deliveryStockVal != null) && ( {/* Bestand */} Bestand: {isDeliveryTracked === false ? '∞' : formatStock(deliveryStockVal)} {isDeliveryTracked !== false && reservedInOtherOrdersDelivery > 0 ? ` (${formatStock(reservedInOtherOrdersDelivery)} reserviert)` : ''} {/* Marge */} {margeDelivery != null && ( Marge: {Math.floor(margeDelivery)}% )} {priceNet != null && ( EK Netto: {formatPrice(priceNet)} {taxRate != null ? ` (${taxRate}% MwSt)` : ''} )} {vkNetto != null && ( VK Netto: {formatPrice(vkNetto)} )} {vkBrutto != null && ( VK Brutto: {formatPrice(vkBrutto)} )} )} ); } if (rawSku) { return ( {this.props.onCreateArticle && ( )} {this.props.onSearchArticle && ( )} {this.props.kind === 'delivery' ? `Kein Lieferanten-Artikel für ArtNr „${rawSku}“ gefunden.` : `Kein Artikel für ArtNr „${rawSku}“ gefunden.`} ); } return null; } render() { const { columns = LINE_COLUMNS, saving, ledgerLine } = this.props; const isFullyLedgered = this.isFullyLedgered(); const secondLine = this.renderSecondLine(); return ( {/* Row 1: Editable Line Inputs */} td': { borderBottom: 'none', p: 0 }, ...(isFullyLedgered && { bgcolor: LOCKED_ROW_BG, '&:hover': { bgcolor: LOCKED_ROW_HOVER_BG, }, }), }} > {columns.map((col, colIdx) => ( {this.renderField(col, colIdx, columns.length)} ))} {this.props.kind === 'delivery' && Boolean(this.props.onBookLine) && ( { 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 ? ( ) : ( )} )} {this.props.onMove && ( <> )} {/* Row 2: Matched Supplier Article Details */} {secondLine && ( td': { pt: 0.25, pb: 0.75, borderBottom: 'none' }, ...(isFullyLedgered && { bgcolor: LOCKED_ROW_BG, }), }} > {secondLine} )} ); } } export default ManifestFormLine;