u
This commit is contained in:
@@ -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?**:
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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() : '';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user