This commit is contained in:
seb
2026-10-20 02:31:49 +02:00
parent 90ee11fefb
commit 067207c58e
2 changed files with 180 additions and 27 deletions

View File

@@ -101,7 +101,7 @@ class ArticleView extends PureComponent {
article={article}
/>
<ArticleViewStock article={article} />
<ArticleViewStock article={article} navigate={this.props.navigate} />
<ArticleViewBrand article={article} />

View File

@@ -4,11 +4,13 @@ import Typography from '@mui/material/Typography';
import WarehouseIcon from '@mui/icons-material/Warehouse';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import ShoppingCartOutlinedIcon from '@mui/icons-material/ShoppingCartOutlined';
import Tooltip from '@mui/material/Tooltip';
import { PanelTile } from 'dashgui/panels';
import { formatStock } from '../searchFormat';
import ArticleStockAdjustDialog from '../ArticleStockAdjustDialog';
import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
import { router } from '../../../appRouter';
import {
GREEN_PANEL,
PETROL_PANEL,
@@ -96,6 +98,58 @@ function extractIncoming(articleData, isParent) {
return items;
}
function extractUndeliveredOrders(articleData, isParent) {
const items = [];
const collect = (article, variantInfo = null) => {
if (!article) return;
if (Array.isArray(article.undeliveredOrders)) {
for (const ord of article.undeliveredOrders) {
const amount = Number(ord?.amount) || 0;
if (amount > 0) {
items.push({
orderId: ord.orderId,
orderNumber: ord.orderNumber || (ord.orderId ? `A-${ord.orderId}` : null),
date: ord.date || null,
customerName: ord.customerName || null,
amount,
variantSku: variantInfo?.sku || null,
variantName: variantInfo?.name || null,
});
}
}
}
};
collect(articleData);
if (isParent) {
const children = Array.isArray(articleData?.children) ? articleData.children : [];
for (const child of children) {
const fullChild = getArticle(child.sku) || child;
collect(fullChild, {
sku: fullChild.sku || child.sku,
name: fullChild.name || child.name,
});
}
}
items.sort((a, b) => {
if (a.date && b.date) {
const diff = new Date(a.date).getTime() - new Date(b.date).getTime();
if (diff !== 0) return diff;
} else if (a.date) {
return -1;
} else if (b.date) {
return 1;
}
return (Number(a.orderId) || 0) - (Number(b.orderId) || 0);
});
return items;
}
export default class ArticleViewStock extends PureComponent {
constructor(props) {
super(props);
@@ -104,6 +158,17 @@ export default class ArticleViewStock extends PureComponent {
};
this.handleOpenAdjust = this.handleOpenAdjust.bind(this);
this.handleCloseAdjust = this.handleCloseAdjust.bind(this);
this.handleOpenOrder = this.handleOpenOrder.bind(this);
}
handleOpenOrder(orderId, event) {
if (!orderId) return;
if (event?.metaKey || event?.ctrlKey) {
window.open(`/auftrag/bestellung/${orderId}`, '_blank');
return;
}
const nav = this.props.navigate || router.navigate;
void nav(`/auftrag/bestellung/${orderId}`);
}
componentDidMount() {
@@ -205,6 +270,10 @@ export default class ArticleViewStock extends PureComponent {
const totalIncoming = incomingItems.reduce((sum, inc) => sum + inc.amount, 0);
const hasIncoming = totalIncoming > 0;
const undeliveredOrders = extractUndeliveredOrders(articleData, isParent);
const totalUndelivered = undeliveredOrders.reduce((sum, ord) => sum + ord.amount, 0);
const hasUndelivered = totalUndelivered > 0;
return (
<>
<PanelTile
@@ -301,8 +370,7 @@ export default class ArticleViewStock extends PureComponent {
onClick={this.handleOpenAdjust}
sx={{
display: 'inline-flex',
alignItems: 'baseline',
gap: 0.75,
flexDirection: 'column',
my: 0.5,
cursor: 'pointer',
borderRadius: 1,
@@ -314,27 +382,29 @@ export default class ArticleViewStock extends PureComponent {
},
}}
>
<Typography
component="div"
sx={{
fontSize: '2.4rem',
fontWeight: 700,
lineHeight: 1.1,
letterSpacing: '-0.02em',
color: totalStock > 0 ? 'text.primary' : 'text.secondary',
}}
>
{formatStock(totalStock)}
</Typography>
{unit ? (
<Box sx={{ display: 'inline-flex', alignItems: 'baseline', gap: 0.75 }}>
<Typography
variant="body2"
color="text.secondary"
sx={{ fontWeight: 500 }}
component="div"
sx={{
fontSize: '2.4rem',
fontWeight: 700,
lineHeight: 1.1,
letterSpacing: '-0.02em',
color: totalStock > 0 ? 'text.primary' : 'text.secondary',
}}
>
{unit}
{formatStock(totalStock)}
</Typography>
) : null}
{unit ? (
<Typography
variant="body2"
color="text.secondary"
sx={{ fontWeight: 500 }}
>
{unit}
</Typography>
) : null}
</Box>
</Box>
</Tooltip>
) : (
@@ -374,8 +444,8 @@ export default class ArticleViewStock extends PureComponent {
)}
</Box>
{/* Bottom section: Per-warehouse breakdown & incoming shipments */}
{hasWarehouses || hasIncoming ? (
{/* Bottom section: Per-warehouse breakdown, incoming shipments & open orders */}
{hasWarehouses || hasIncoming || hasUndelivered ? (
<Box
sx={{
mt: 1,
@@ -461,12 +531,12 @@ export default class ArticleViewStock extends PureComponent {
<>
{incomingItems.map((inc, idx) => {
const dateLabel = inc.date ? formatDate(inc.date) : null;
const label = [
const labelParts = [
inc.variantName,
inc.supplierName || 'Lieferant',
dateLabel,
]
.filter(Boolean)
.join(' – ');
].filter(Boolean);
const label = labelParts.join(' – ');
return (
<Box
@@ -528,6 +598,89 @@ export default class ArticleViewStock extends PureComponent {
})}
</>
) : null}
{/* Undelivered / Open Customer Orders */}
{hasUndelivered ? (
<>
{undeliveredOrders.map((ord, idx) => {
const dateLabel = ord.date ? formatDate(ord.date) : null;
const orderLabel = ord.orderNumber || (ord.orderId ? `Auftrag #${ord.orderId}` : 'Auftrag');
const labelParts = [
ord.variantName,
orderLabel,
ord.customerName,
dateLabel,
].filter(Boolean);
const label = labelParts.join(' – ');
const tooltipText = ord.orderId
? `Auftrag ${orderLabel}${ord.customerName ? ` (${ord.customerName})` : ''} öffnen`
: label;
return (
<Box
key={ord.orderId ? `order-${ord.orderId}` : `ord-${idx}`}
onClick={(e) => this.handleOpenOrder(ord.orderId, e)}
title={tooltipText}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
py: 0.35,
px: 0.75,
borderRadius: 1,
backgroundColor: VEIL_PANEL,
cursor: ord.orderId ? 'pointer' : 'default',
transition: 'background-color 0.15s ease',
'&:hover': ord.orderId ? {
backgroundColor: TINT_ROW_HOVER,
} : undefined,
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
minWidth: 0,
mr: 1,
}}
>
<ShoppingCartOutlinedIcon
sx={{
fontSize: 15,
color: 'text.secondary',
mr: 0.6,
flexShrink: 0,
}}
/>
<Typography
variant="body2"
noWrap
sx={{
fontWeight: 500,
fontSize: '0.82rem',
color: 'text.primary',
}}
>
{label}
</Typography>
</Box>
<Typography
variant="body2"
sx={{
fontWeight: 700,
fontSize: '0.82rem',
flexShrink: 0,
color: 'text.primary',
}}
>
-{formatStock(ord.amount)}
</Typography>
</Box>
);
})}
</>
) : null}
</Box>
</Box>
) : null}