From 22063e694487151a88385fe8d5034bf58dc10e5c Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 8 Oct 2026 21:32:52 +0200 Subject: [PATCH] u --- .../article/boxes/ArticleViewLists.jsx | 563 ++++++++++++++---- 1 file changed, 442 insertions(+), 121 deletions(-) diff --git a/src/components/article/boxes/ArticleViewLists.jsx b/src/components/article/boxes/ArticleViewLists.jsx index d85a0e8..d031047 100644 --- a/src/components/article/boxes/ArticleViewLists.jsx +++ b/src/components/article/boxes/ArticleViewLists.jsx @@ -34,6 +34,9 @@ import { artikelDetailPath } from '../articleRoute'; import { article as articleApi } from '../../../api'; import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat'; import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat'; +import companyAutomat from '../../../automats/companyAutomat'; +import { formatStock } from '../searchFormat'; +import { formatPrice, getArticleGross } from '../../../utils/price'; import { notifyError } from '../../../utils/notify'; import articleIndexAutomat from '../../../automats/articleIndexAutomat'; import { @@ -43,6 +46,67 @@ import { alpha, } from '../../../colors'; +function getVariantDetails(child, allArticles, defaultTaxRate) { + const cached = child?.sku ? getArticle(child.sku) : null; + const fromIndex = !cached && child?.sku + ? (allArticles || []).find((a) => a.sku === child.sku) + : null; + const merged = cached || fromIndex || child || {}; + + // 1. Gross price + const gross = getArticleGross(merged, defaultTaxRate); + const priceDisplay = gross != null && Number.isFinite(gross) ? formatPrice(gross) : null; + + // 2. Stock & Unit + let stockVal = null; + if (Array.isArray(merged.warehouses) && merged.warehouses.length > 0) { + stockVal = merged.warehouses.reduce((sum, wh) => sum + (Number(wh.stock) || 0), 0); + } else if (merged.stock != null && !Number.isNaN(Number(merged.stock))) { + stockVal = Number(merged.stock); + } else if (child?.stock != null && !Number.isNaN(Number(child.stock))) { + stockVal = Number(child.stock); + } + + const rawUnit = + merged.unit?.symbol || + merged.unit?.name || + child?.unit?.symbol || + child?.unit?.name || + ''; + const unit = + rawUnit && !['stk', 'stück', 'pc', 'stk.'].includes(rawUnit.trim().toLowerCase()) + ? ` ${rawUnit}` + : ''; + const stockDisplay = stockVal != null ? `${formatStock(stockVal)}${unit}` : null; + + // 3. EAN(s) + const eanCandidates = []; + const rawEans = merged.eans ?? child?.eans; + if (Array.isArray(rawEans)) { + for (const item of rawEans) { + const val = typeof item === 'object' && item !== null ? item.ean : item; + if (val && String(val).trim()) eanCandidates.push(String(val).trim()); + } + } else if (rawEans && typeof rawEans === 'string') { + eanCandidates.push(rawEans.trim()); + } + + const singleEan = merged.ean ?? child?.ean; + if (singleEan && typeof singleEan === 'string' && !eanCandidates.includes(singleEan.trim())) { + eanCandidates.push(singleEan.trim()); + } + + const primaryEan = eanCandidates[0] ?? null; + + return { + priceDisplay, + stockVal, + stockDisplay, + primaryEan, + allEans: eanCandidates, + }; +} + export default class ArticleViewLists extends PureComponent { constructor(props) { super(props); @@ -88,12 +152,16 @@ export default class ArticleViewLists extends PureComponent { // Confirmation dialogs removeTargetChild: null, // { sku, attributeValue } confirmClearType: null, // 'stueckliste' | 'options' | null + company: companyAutomat.state.current, }; this._cancelled = false; } componentDidMount() { this._cancelled = false; + this.unsubCompany = companyAutomat.subscribe(this, (state) => ({ + company: state.current, + })); this.unsubAttr = attributeAutomat.subscribe((state) => { if (this._cancelled) return; this.setState((prev) => { @@ -123,21 +191,48 @@ export default class ArticleViewLists extends PureComponent { void this.loadAssignedAttributes(this.props.article.sku); } + if (this.props.article?.isParent) { + this.ensureChildren(this.props.article.children); + } + if (this.props.article?.parentSku) { - void artikelAutomat.actions.loadArticle(this.props.article.parentSku); + void artikelAutomat.actions.loadArticle(this.props.article.parentSku).then((parent) => { + if (!this._cancelled && parent?.children) { + this.ensureChildren(parent.children); + } + }).catch(() => {}); } void this.loadAllArticles(); } componentDidUpdate(prevProps) { + if (this.props.article?.isParent) { + if ( + prevProps.article?.sku !== this.props.article?.sku || + prevProps.article?.children !== this.props.article?.children + ) { + this.ensureChildren(this.props.article.children); + } + } + if (this.props.article?.parentSku) { + if (prevProps.article?.parentSku !== this.props.article?.parentSku) { + void artikelAutomat.actions.loadArticle(this.props.article.parentSku).then((parent) => { + if (!this._cancelled && parent?.children) { + this.ensureChildren(parent.children); + } + }).catch(() => {}); + } else { + const parent = getArticle(this.props.article.parentSku); + if (parent?.children) { + this.ensureChildren(parent.children); + } + } + } if (prevProps.article?.sku !== this.props.article?.sku) { if (this.props.article?.sku) { void this.loadAssignedAttributes(this.props.article.sku); } - if (this.props.article?.parentSku) { - void artikelAutomat.actions.loadArticle(this.props.article.parentSku); - } const nextArticle = this.props.article; const hasStueckliste = Boolean( nextArticle?.articlelistId != null || nextArticle?.articlelist?.length > 0 @@ -166,10 +261,20 @@ export default class ArticleViewLists extends PureComponent { componentWillUnmount() { this._cancelled = true; + this.unsubCompany?.(); this.unsubAttr?.(); this.unsubArtikel?.(); } + ensureChildren(children) { + if (!Array.isArray(children)) return; + for (const child of children) { + if (child?.sku && !getArticle(child.sku)) { + void artikelAutomat.actions.loadArticle(child.sku).catch(() => {}); + } + } + } + getApi() { return articleApi; } @@ -1073,6 +1178,8 @@ export default class ArticleViewLists extends PureComponent { renderChildView(parentSku, parentArticle, attributes, assignedAttributes) { const { article: articleData } = this.props; + const { allArticles } = this.state; + const defaultTaxRate = this.state.company?.taxrate ?? companyAutomat.state.current?.taxrate ?? 19; const parentAttrName = parentArticle?.parentAttributeName || (parentArticle?.parentAttributeNameId @@ -1235,6 +1342,13 @@ export default class ArticleViewLists extends PureComponent { const isSelf = sibling.sku === articleData.sku || (articleData.id != null && sibling.id === articleData.id); + const { + priceDisplay, + stockVal, + stockDisplay, + primaryEan, + allEans, + } = getVariantDetails(sibling, allArticles, defaultTaxRate); return ( - - {sibling.attributeValue || variationValue || sibling.name || sibling.sku} - - + + {sibling.attributeValue || variationValue || sibling.name || sibling.sku} + + + {priceDisplay || '—'} + + + {sibling.sku} + + + · + + + 1 ? `EANs: ${allEans.join(', ')}` : ''} + disableHoverListener={allEans.length <= 1} + > + + {primaryEan ? `EAN ${primaryEan}${allEans.length > 1 ? ` (+${allEans.length - 1})` : ''}` : 'EAN —'} + + + + + · + + + 0 ? 'success.dark' : 'error.main', + whiteSpace: 'nowrap', + }} + > + Bestand {stockDisplay ?? '—'} + ) : ( @@ -1319,37 +1495,101 @@ export default class ArticleViewLists extends PureComponent { }, }} > - - {sibling.attributeValue || sibling.name || sibling.sku} - - - {sibling.sku} + {sibling.attributeValue || sibling.name || sibling.sku} + + + {priceDisplay || '—'} + + + + + + {sibling.sku} + + + + + + · + + + 1 ? `EANs: ${allEans.join(', ')}` : ''} + disableHoverListener={allEans.length <= 1} + > + + {primaryEan ? `EAN ${primaryEan}${allEans.length > 1 ? ` (+${allEans.length - 1})` : ''}` : 'EAN —'} + + + + + · + + + 0 ? 'success.dark' : 'error.main', + whiteSpace: 'nowrap', + }} + > + Bestand {stockDisplay ?? '—'} - )} @@ -1645,100 +1885,181 @@ export default class ArticleViewLists extends PureComponent { ) : ( - {children.map((child) => ( - - { + const defaultTaxRate = this.state.company?.taxrate ?? companyAutomat.state.current?.taxrate ?? 19; + const { + priceDisplay, + stockVal, + stockDisplay, + primaryEan, + allEans, + } = getVariantDetails(child, allArticles, defaultTaxRate); + + return ( + - - {child.attributeValue || child.name || child.sku} - - - - {child.sku} - - - - + + {child.attributeValue || child.name || child.sku} + + + {priceDisplay || '—'} + + - this.handleOpenRemoveDialog(child)} - title="Variante entfernen" - sx={{ - p: 0.35, - color: 'text.secondary', - '&:hover': { color: 'error.main' }, - }} - > - - - - ))} + + + + {child.sku} + + + + + + · + + + 1 ? `EANs: ${allEans.join(', ')}` : ''} + disableHoverListener={allEans.length <= 1} + > + + {primaryEan ? `EAN ${primaryEan}${allEans.length > 1 ? ` (+${allEans.length - 1})` : ''}` : 'EAN —'} + + + + + · + + + 0 ? 'success.dark' : 'error.main', + whiteSpace: 'nowrap', + }} + > + Bestand {stockDisplay ?? '—'} + + + + + this.handleOpenRemoveDialog(child)} + title="Variante entfernen" + sx={{ + p: 0.35, + color: 'text.secondary', + '&:hover': { color: 'error.main' }, + }} + > + + + + ); + })} )}