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?**:
|
* **Hat Bestand?**:
|
||||||
- **Virtuell abgeleitet**: Ergibt sich dynamisch aus der kleinsten verfügbaren Komponentenmenge:
|
- **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)$$
|
$$\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)?**:
|
* **Eigene Eigenschaften (Hersteller, Kategorie)?**:
|
||||||
- **Ja**: Eigenständige Zuweisung von Marke (`brand_id`), Kategorien (`categoryarticle`), Name und Beschreibung.
|
- **Ja**: Eigenständige Zuweisung von Marke (`brand_id`), Kategorien (`categoryarticle`), Name und Beschreibung.
|
||||||
* **Hat Lieferanten?**:
|
* **Hat Lieferanten?**:
|
||||||
|
|||||||
@@ -32,8 +32,13 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
const { article: articleData } = this.props;
|
const { article: articleData } = this.props;
|
||||||
const { adjustOpen } = this.state;
|
const { adjustOpen } = this.state;
|
||||||
const isFather = Boolean(articleData?.isFather);
|
const isFather = Boolean(articleData?.isFather);
|
||||||
|
const isStueckliste = Boolean(
|
||||||
|
articleData?.articlelistId != null ||
|
||||||
|
(articleData?.articleListCount ?? 0) > 0 ||
|
||||||
|
articleData?.articlelist?.length > 0
|
||||||
|
);
|
||||||
const isTrackStock =
|
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)
|
const rawWarehouses = Array.isArray(articleData?.warehouses)
|
||||||
? articleData.warehouses
|
? articleData.warehouses
|
||||||
@@ -61,9 +66,9 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
<PanelTile
|
<PanelTile
|
||||||
wide={1}
|
wide={1}
|
||||||
minHeight={SECOND_ROW_MIN_HEIGHT}
|
minHeight={SECOND_ROW_MIN_HEIGHT}
|
||||||
color={isFather ? '#e2e8f0' : '#bbf7d0'}
|
color={isFather ? '#e2e8f0' : isStueckliste ? '#cffafe' : '#bbf7d0'}
|
||||||
action={
|
action={
|
||||||
isFather
|
isFather || isStueckliste
|
||||||
? null
|
? null
|
||||||
: {
|
: {
|
||||||
label: 'Bestand anpassen',
|
label: 'Bestand anpassen',
|
||||||
@@ -116,6 +121,41 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
Bestand wird auf Variantenebene geführt
|
Bestand wird auf Variantenebene geführt
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</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 ? (
|
) : isTrackStock ? (
|
||||||
<Tooltip title="Klicken, um Bestand anzupassen" arrow placement="top-start">
|
<Tooltip title="Klicken, um Bestand anzupassen" arrow placement="top-start">
|
||||||
<Box
|
<Box
|
||||||
@@ -277,7 +317,7 @@ export default class ArticleViewStock extends PureComponent {
|
|||||||
</Box>
|
</Box>
|
||||||
</PanelTile>
|
</PanelTile>
|
||||||
|
|
||||||
{!isFather ? (
|
{!isFather && !isStueckliste ? (
|
||||||
<ArticleStockAdjustDialog
|
<ArticleStockAdjustDialog
|
||||||
open={adjustOpen}
|
open={adjustOpen}
|
||||||
onClose={this.handleCloseAdjust}
|
onClose={this.handleCloseAdjust}
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
const { onAssign, line } = this.props;
|
const { onAssign, line } = this.props;
|
||||||
if (!onAssign || !article?.sku) return;
|
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 });
|
this.setState({ assigningSku: article.sku, error: null });
|
||||||
try {
|
try {
|
||||||
await onAssign(article, line);
|
await onAssign(article, line);
|
||||||
@@ -112,9 +117,10 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
const { open, onClose, line } = this.props;
|
const { open, onClose, line } = this.props;
|
||||||
const { query, articles, loading, error, assigningSku } = this.state;
|
const { query, articles, loading, error, assigningSku } = this.state;
|
||||||
|
|
||||||
|
const bookableArticles = articles.filter((a) => !a.isFather && !a.is_father);
|
||||||
const trimmed = query.trim().toLowerCase();
|
const trimmed = query.trim().toLowerCase();
|
||||||
const filtered = trimmed
|
const filtered = trimmed
|
||||||
? articles.filter((a) => {
|
? bookableArticles.filter((a) => {
|
||||||
const skuMatch = a.sku?.toLowerCase().includes(trimmed);
|
const skuMatch = a.sku?.toLowerCase().includes(trimmed);
|
||||||
const nameMatch = a.name?.toLowerCase().includes(trimmed);
|
const nameMatch = a.name?.toLowerCase().includes(trimmed);
|
||||||
const eanMatch = Array.isArray(a.eans)
|
const eanMatch = Array.isArray(a.eans)
|
||||||
@@ -122,7 +128,7 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
: false;
|
: false;
|
||||||
return skuMatch || nameMatch || eanMatch;
|
return skuMatch || nameMatch || eanMatch;
|
||||||
})
|
})
|
||||||
: articles.slice(0, 50);
|
: bookableArticles.slice(0, 50);
|
||||||
|
|
||||||
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user