This commit is contained in:
seb
2026-10-01 08:16:17 +02:00
parent 359efc6481
commit 6ea4f8fb36
3 changed files with 53 additions and 7 deletions

View File

@@ -74,7 +74,7 @@ Varianten bilden im System eine **Baum-Struktur** ab (z. B. für mehrdimensional
* **Hat Bestand?**:
- **Virtuell abgeleitet**: Ergibt sich dynamisch aus der kleinsten verfügbaren Komponentenmenge:
$$\text{Bestand}_{\text{Set}} = \min_{i} \left( \left\lfloor \frac{\text{Bestand}_i}{\text{Menge}_i} \right\rfloor \right)$$
Bei Verkauf werden die Komponenten aus dem Lager abgebucht.
Bei Verkauf werden die Komponenten aus dem Lager abgebucht. **Manuelle Bestandsanpassungen (`POST /api/stock/adjust`) auf Stücklisten sind gesperrt** (HTTP 400), um eine künstliche Komponenten-Inflation zu verhindern. Inventur- und Bestandskorrekturen erfolgen direkt an den physischen Komponenten.
* **Eigene Eigenschaften (Hersteller, Kategorie)?**:
- **Ja**: Eigenständige Zuweisung von Marke (`brand_id`), Kategorien (`categoryarticle`), Name und Beschreibung.
* **Hat Lieferanten?**:

View File

@@ -32,8 +32,13 @@ export default class ArticleViewStock extends PureComponent {
const { article: articleData } = this.props;
const { adjustOpen } = this.state;
const isFather = Boolean(articleData?.isFather);
const isStueckliste = Boolean(
articleData?.articlelistId != null ||
(articleData?.articleListCount ?? 0) > 0 ||
articleData?.articlelist?.length > 0
);
const isTrackStock =
!isFather && articleData?.track_stock !== false && articleData?.track_stock !== 0;
!isFather && !isStueckliste && articleData?.track_stock !== false && articleData?.track_stock !== 0;
const rawWarehouses = Array.isArray(articleData?.warehouses)
? articleData.warehouses
@@ -61,9 +66,9 @@ export default class ArticleViewStock extends PureComponent {
<PanelTile
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color={isFather ? '#e2e8f0' : '#bbf7d0'}
color={isFather ? '#e2e8f0' : isStueckliste ? '#cffafe' : '#bbf7d0'}
action={
isFather
isFather || isStueckliste
? null
: {
label: 'Bestand anpassen',
@@ -116,6 +121,41 @@ export default class ArticleViewStock extends PureComponent {
Bestand wird auf Variantenebene geführt
</Typography>
</Box>
) : isStueckliste ? (
<Tooltip title="Virtueller Bestand – Anpassung erfolgt an den einzelnen Komponenten" arrow placement="top-start">
<Box sx={{ my: 0.5 }}>
<Box sx={{ display: 'inline-flex', alignItems: 'baseline', gap: 0.75 }}>
<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 ? (
<Typography
variant="body2"
color="text.secondary"
sx={{ fontWeight: 500 }}
>
{unit}
</Typography>
) : null}
</Box>
<Typography
variant="caption"
color="text.secondary"
sx={{ display: 'block', mt: 0.25 }}
>
Virtueller Bestand (aus Komponenten)
</Typography>
</Box>
</Tooltip>
) : isTrackStock ? (
<Tooltip title="Klicken, um Bestand anzupassen" arrow placement="top-start">
<Box
@@ -277,7 +317,7 @@ export default class ArticleViewStock extends PureComponent {
</Box>
</PanelTile>
{!isFather ? (
{!isFather && !isStueckliste ? (
<ArticleStockAdjustDialog
open={adjustOpen}
onClose={this.handleCloseAdjust}

View File

@@ -97,6 +97,11 @@ export default class ArticleSearchDialog extends PureComponent {
const { onAssign, line } = this.props;
if (!onAssign || !article?.sku) return;
if (article.isFather || article.is_father) {
this.setState({ error: 'Ein Vaterartikel kann nicht auf Belege gebucht werden.' });
return;
}
this.setState({ assigningSku: article.sku, error: null });
try {
await onAssign(article, line);
@@ -112,9 +117,10 @@ export default class ArticleSearchDialog extends PureComponent {
const { open, onClose, line } = this.props;
const { query, articles, loading, error, assigningSku } = this.state;
const bookableArticles = articles.filter((a) => !a.isFather && !a.is_father);
const trimmed = query.trim().toLowerCase();
const filtered = trimmed
? articles.filter((a) => {
? bookableArticles.filter((a) => {
const skuMatch = a.sku?.toLowerCase().includes(trimmed);
const nameMatch = a.name?.toLowerCase().includes(trimmed);
const eanMatch = Array.isArray(a.eans)
@@ -122,7 +128,7 @@ export default class ArticleSearchDialog extends PureComponent {
: false;
return skuMatch || nameMatch || eanMatch;
})
: articles.slice(0, 50);
: bookableArticles.slice(0, 50);
const rawSku = line?.sku != null ? String(line.sku).trim() : '';