This commit is contained in:
seb
2026-10-04 22:47:26 +02:00
parent 8b401fa50b
commit a90069d340
44 changed files with 994 additions and 268 deletions

View File

@@ -79,9 +79,9 @@ const SETTINGS_TAB = {
};
function visibleTabs(article) {
const isFather = Boolean(article?.isFather);
const isParent = Boolean(article?.isParent);
const tabs = ALWAYS_TABS.map((row) => {
if (row.id === ARTICLE_TAB.supplier && isFather) {
if (row.id === ARTICLE_TAB.supplier && isParent) {
return {
...row,
disabled: true,

View File

@@ -32,7 +32,7 @@ export function articleSku(article) {
export function articleTabIds(article) {
let ids = [...BASE_ARTICLE_TAB_IDS];
if (article?.isFather) {
if (article?.isParent) {
ids = ids.filter((id) => id !== ARTICLE_TAB.supplier);
}
return [...ids, ARTICLE_TAB.settings];

View File

@@ -61,10 +61,10 @@ export default class ArticleViewLists extends PureComponent {
// Active sub-tab for compound article: 'stueckliste' | 'options'
activeListTab,
// Creator mode for standard article: null | 'father' | 'stueckliste' | 'options'
// Creator mode for standard article: null | 'parent' | 'stueckliste' | 'options'
activeCreatorMode: null,
// Father mode state
// Parent mode state
selectedAttributeId: '',
selectedChildValueId: '',
newChildText: '',
@@ -117,8 +117,8 @@ export default class ArticleViewLists extends PureComponent {
void this.loadAssignedAttributes(this.props.article.sku);
}
if (this.props.article?.fatherSku) {
void artikelAutomat.actions.loadArticle(this.props.article.fatherSku);
if (this.props.article?.parentSku) {
void artikelAutomat.actions.loadArticle(this.props.article.parentSku);
}
void this.loadAllArticles();
@@ -129,8 +129,8 @@ export default class ArticleViewLists extends PureComponent {
if (this.props.article?.sku) {
void this.loadAssignedAttributes(this.props.article.sku);
}
if (this.props.article?.fatherSku) {
void artikelAutomat.actions.loadArticle(this.props.article.fatherSku);
if (this.props.article?.parentSku) {
void artikelAutomat.actions.loadArticle(this.props.article.parentSku);
}
const nextArticle = this.props.article;
const hasStueckliste = Boolean(
@@ -205,9 +205,9 @@ export default class ArticleViewLists extends PureComponent {
}
}
// ── Father Actions ──────────────────────────────────────────────────────────
// ── Parent Actions ──────────────────────────────────────────────────────────
handleEnableFather = async () => {
handleEnableParent = async () => {
const { article } = this.props;
const { selectedAttributeId, assignedAttributes, busy } = this.state;
if (busy || !article?.sku || !selectedAttributeId) return;
@@ -232,8 +232,8 @@ export default class ArticleViewLists extends PureComponent {
this.setState({ busy: true });
try {
const api = this.getApi();
const res = await api.toggleFather(article.sku, {
isFather: true,
const res = await api.toggleParent(article.sku, {
isParent: true,
attributeNameId: Number(selectedAttributeId),
});
if (this._cancelled) return;
@@ -253,7 +253,7 @@ export default class ArticleViewLists extends PureComponent {
}
};
handleDisableFather = async () => {
handleDisableParent = async () => {
const { article } = this.props;
const { busy } = this.state;
if (busy || !article?.sku) return;
@@ -261,7 +261,7 @@ export default class ArticleViewLists extends PureComponent {
this.setState({ busy: true });
try {
const api = this.getApi();
const res = await api.toggleFather(article.sku, { isFather: false });
const res = await api.toggleParent(article.sku, { isParent: false });
if (this._cancelled) return;
if (res?.article) {
@@ -365,12 +365,12 @@ export default class ArticleViewLists extends PureComponent {
return;
}
const isTargetFather = Boolean(
stuecklisteTargetArticle.isFather ||
stuecklisteTargetArticle.is_father ||
getArticle(stuecklisteTargetArticle.sku)?.isFather
const isTargetParent = Boolean(
stuecklisteTargetArticle.isParent ||
stuecklisteTargetArticle.is_parent ||
getArticle(stuecklisteTargetArticle.sku)?.isParent
);
if (isTargetFather) {
if (isTargetParent) {
enqueueSnackbar('Ein Vaterartikel kann nicht als Komponente einer Stückliste hinzugefügt werden.', { variant: 'error' });
return;
}
@@ -489,12 +489,12 @@ export default class ArticleViewLists extends PureComponent {
return;
}
const isTargetFather = Boolean(
optionTargetArticle.isFather ||
optionTargetArticle.is_father ||
getArticle(optionTargetArticle.sku)?.isFather
const isTargetParent = Boolean(
optionTargetArticle.isParent ||
optionTargetArticle.is_parent ||
getArticle(optionTargetArticle.sku)?.isParent
);
if (isTargetFather) {
if (isTargetParent) {
enqueueSnackbar('Ein Vaterartikel kann nicht als Option hinzugefügt werden.', { variant: 'error' });
return;
}
@@ -1081,33 +1081,33 @@ export default class ArticleViewLists extends PureComponent {
);
}
renderChildView(fatherSku, fatherArticle, attributes, assignedAttributes) {
renderChildView(parentSku, parentArticle, attributes, assignedAttributes) {
const { article: articleData } = this.props;
const fatherAttrName =
fatherArticle?.fatherAttributeName ||
(fatherArticle?.fatherAttributeNameId
? attributes.find((a) => a.id === fatherArticle.fatherAttributeNameId)?.name
const parentAttrName =
parentArticle?.parentAttributeName ||
(parentArticle?.parentAttributeNameId
? attributes.find((a) => a.id === parentArticle.parentAttributeNameId)?.name
: null) ||
'Varianten';
const selfInFather = (fatherArticle?.children || []).find(
const selfInParent = (parentArticle?.children || []).find(
(c) => c.sku === articleData.sku || c.id === articleData.id
);
const assignedMatch = (assignedAttributes || []).find(
(a) =>
(fatherArticle?.fatherAttributeNameId != null &&
Number(a.attributeId) === Number(fatherArticle.fatherAttributeNameId)) ||
(parentArticle?.parentAttributeNameId != null &&
Number(a.attributeId) === Number(parentArticle.parentAttributeNameId)) ||
(a.attributeName &&
fatherAttrName &&
String(a.attributeName).toLowerCase() === String(fatherAttrName).toLowerCase())
parentAttrName &&
String(a.attributeName).toLowerCase() === String(parentAttrName).toLowerCase())
);
const variationValue =
selfInFather?.attributeValue ||
selfInParent?.attributeValue ||
assignedMatch?.attributeValue ||
null;
const siblings = (fatherArticle?.children || []).filter(
const siblings = (parentArticle?.children || []).filter(
(c) => c.sku !== articleData.sku && c.id !== articleData.id
);
@@ -1130,7 +1130,7 @@ export default class ArticleViewLists extends PureComponent {
für
</Typography>
<Chip
label={fatherAttrName}
label={parentAttrName}
size="small"
variant="outlined"
color="primary"
@@ -1163,7 +1163,7 @@ export default class ArticleViewLists extends PureComponent {
>
<Link
component={RouterLink}
to={artikelDetailPath(fatherSku)}
to={artikelDetailPath(parentSku)}
sx={{
minWidth: 0,
flex: 1,
@@ -1196,7 +1196,7 @@ export default class ArticleViewLists extends PureComponent {
transition: 'color 0.15s ease',
}}
>
{fatherArticle?.name || fatherSku}
{parentArticle?.name || parentSku}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, mt: 0.2 }}>
<Typography
@@ -1210,7 +1210,7 @@ export default class ArticleViewLists extends PureComponent {
letterSpacing: '0.02em',
}}
>
{fatherSku}
{parentSku}
</Typography>
<OpenInNewIcon sx={{ fontSize: 13, color: 'primary.main', opacity: 0.8 }} />
</Box>
@@ -1316,7 +1316,7 @@ export default class ArticleViewLists extends PureComponent {
const { busy, activeCreatorMode, selectedAttributeId } = this.state;
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{activeCreatorMode === 'father' ? (
{activeCreatorMode === 'parent' ? (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
@@ -1367,7 +1367,7 @@ export default class ArticleViewLists extends PureComponent {
assignedAttributeIds.has(Number(selectedAttributeId)) ||
selectableAttributes.length === 0
}
onClick={this.handleEnableFather}
onClick={this.handleEnableParent}
sx={{ height: 38, flexShrink: 0 }}
>
{busy ? <CircularProgress size={14} color="inherit" /> : 'Aktivieren'}
@@ -1381,7 +1381,7 @@ export default class ArticleViewLists extends PureComponent {
variant="outlined"
color="primary"
startIcon={<AccountTreeIcon sx={{ fontSize: 16 }} />}
onClick={() => this.setState({ activeCreatorMode: 'father' })}
onClick={() => this.setState({ activeCreatorMode: 'parent' })}
>
Als Vaterartikel aktivieren
</Button>
@@ -1428,8 +1428,8 @@ export default class ArticleViewLists extends PureComponent {
if (!articleData) return null;
const isChild = articleData.fatherArticleId != null || Boolean(articleData.fatherSku);
const isFather = Boolean(articleData.isFather);
const isChild = articleData.parentArticleId != null || Boolean(articleData.parentSku);
const isParent = Boolean(articleData.isParent);
const children = Array.isArray(articleData.children) ? articleData.children : [];
const stuecklisteItems = Array.isArray(articleData.articlelist) ? articleData.articlelist : [];
@@ -1438,7 +1438,7 @@ export default class ArticleViewLists extends PureComponent {
const optionsItems = Array.isArray(articleData.options) ? articleData.options : [];
const hasOptions = articleData.optionslistId != null || optionsItems.length > 0;
// Attributes assigned to this article that cannot be selected for father mode
// Attributes assigned to this article that cannot be selected for parent mode
const assignedAttributeIds = new Set(
(assignedAttributes || []).map((a) => a.attributeId)
);
@@ -1446,31 +1446,31 @@ export default class ArticleViewLists extends PureComponent {
(a) => !assignedAttributeIds.has(a.id)
);
// Filter available articles for Autocomplete (cannot select self, and cannot select father articles)
// Filter available articles for Autocomplete (cannot select self, and cannot select parent articles)
const selectableOtherArticles = allArticles.filter(
(a) => a.sku !== articleData.sku && !a.isFather && !a.is_father
(a) => a.sku !== articleData.sku && !a.isParent && !a.is_parent
);
// Find father attribute definition & values
const fatherAttrDef = isFather
? attributes.find((a) => a.id === articleData.fatherAttributeNameId)
// Find parent attribute definition & values
const parentAttrDef = isParent
? attributes.find((a) => a.id === articleData.parentAttributeNameId)
: null;
const allFatherValues = fatherAttrDef?.values || [];
const allParentValues = parentAttrDef?.values || [];
const assignedValueIds = new Set(
children.map((c) => c.attributeValueId).filter((id) => id != null)
);
const availableFatherValues = allFatherValues.filter((v) => !assignedValueIds.has(v.id));
const availableParentValues = allParentValues.filter((v) => !assignedValueIds.has(v.id));
const fatherSku = articleData.fatherSku;
const fatherArticle = fatherSku ? getArticle(fatherSku) : null;
const parentSku = articleData.parentSku;
const parentArticle = parentSku ? getArticle(parentSku) : null;
return (
<PanelTile wide={1} minHeight={200} color="#fbcfe8">
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', gap: 1 }}>
{/* ── Mode 1: Father Article ──────────────────────────────────────── */}
{isFather ? (
{/* ── Mode 1: Parent Article ──────────────────────────────────────── */}
{isParent ? (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75, flex: 1, minHeight: 0 }}>
{isChild && fatherSku ? (
{isChild && parentSku ? (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}>
<AccountTreeIcon sx={{ color: 'text.secondary', fontSize: 16 }} />
<Typography variant="caption" color="text.secondary">
@@ -1478,7 +1478,7 @@ export default class ArticleViewLists extends PureComponent {
</Typography>
<Link
component={RouterLink}
to={artikelDetailPath(fatherSku)}
to={artikelDetailPath(parentSku)}
sx={{
fontWeight: 600,
fontSize: '0.82rem',
@@ -1488,7 +1488,7 @@ export default class ArticleViewLists extends PureComponent {
'&:hover': { textDecoration: 'underline' },
}}
>
{fatherSku}
{parentSku}
</Link>
</Box>
) : null}
@@ -1499,7 +1499,7 @@ export default class ArticleViewLists extends PureComponent {
Variantsvater für
</Typography>
<Chip
label={articleData.fatherAttributeName || fatherAttrDef?.name || 'Varianten'}
label={articleData.parentAttributeName || parentAttrDef?.name || 'Varianten'}
size="small"
color="primary"
sx={{ height: 20, fontSize: '0.72rem', fontWeight: 600 }}
@@ -1511,7 +1511,7 @@ export default class ArticleViewLists extends PureComponent {
<span>
<IconButton
size="small"
onClick={this.handleDisableFather}
onClick={this.handleDisableParent}
disabled={busy}
sx={{ p: 0.4, color: 'text.secondary', '&:hover': { color: 'error.main' } }}
>
@@ -1524,7 +1524,7 @@ export default class ArticleViewLists extends PureComponent {
{/* Add Child Variant Row */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 0.5 }}>
{isCustomChildValue || availableFatherValues.length === 0 ? (
{isCustomChildValue || availableParentValues.length === 0 ? (
<TextInput
label="Neuer Wert"
placeholder="Neuer Wert..."
@@ -1551,7 +1551,7 @@ export default class ArticleViewLists extends PureComponent {
<MenuItem value="" disabled>
<em>Variante wählen...</em>
</MenuItem>
{availableFatherValues.map((v) => (
{availableParentValues.map((v) => (
<MenuItem key={v.id} value={v.id}>
{v.value}
</MenuItem>
@@ -1566,7 +1566,7 @@ export default class ArticleViewLists extends PureComponent {
color="primary"
disabled={
busy ||
(isCustomChildValue || availableFatherValues.length === 0
(isCustomChildValue || availableParentValues.length === 0
? !newChildText.trim()
: !selectedChildValueId)
}
@@ -1590,7 +1590,7 @@ export default class ArticleViewLists extends PureComponent {
</Box>
{/* Toggle custom value / predefined value if values exist */}
{allFatherValues.length > 0 && availableFatherValues.length > 0 ? (
{allParentValues.length > 0 && availableParentValues.length > 0 ? (
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: -0.25 }}>
<Link
component="button"
@@ -1721,12 +1721,12 @@ export default class ArticleViewLists extends PureComponent {
)}
</Box>
</Box>
) : isChild && fatherSku ? (
) : isChild && parentSku ? (
/* ── Mode 2: Child Variant Article ───────────────────────────────── */
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, flex: 1, minHeight: 0 }}>
{this.renderChildView(
fatherSku,
fatherArticle,
parentSku,
parentArticle,
attributes,
assignedAttributes
)}

View File

@@ -476,7 +476,7 @@ export default class ArticleViewPrice extends PureComponent {
render() {
const { article: articleData } = this.props;
if (articleData?.isFather) {
if (articleData?.isParent) {
return (
<PanelTile
wide={1}

View File

@@ -31,14 +31,14 @@ export default class ArticleViewStock extends PureComponent {
render() {
const { article: articleData } = this.props;
const { adjustOpen } = this.state;
const isFather = Boolean(articleData?.isFather);
const isParent = Boolean(articleData?.isParent);
const isStueckliste = Boolean(
articleData?.articlelistId != null ||
(articleData?.articleListCount ?? 0) > 0 ||
articleData?.articlelist?.length > 0
);
const isTrackStock =
!isFather && !isStueckliste && articleData?.track_stock !== false && articleData?.track_stock !== 0;
!isParent && !isStueckliste && articleData?.track_stock !== false && articleData?.track_stock !== 0;
const rawWarehouses = Array.isArray(articleData?.warehouses)
? articleData.warehouses
@@ -66,9 +66,9 @@ export default class ArticleViewStock extends PureComponent {
<PanelTile
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color={isFather ? '#e2e8f0' : isStueckliste ? '#cffafe' : '#bbf7d0'}
color={isParent ? '#e2e8f0' : isStueckliste ? '#cffafe' : '#bbf7d0'}
action={
isFather || isStueckliste
isParent || isStueckliste
? null
: {
label: 'Bestand anpassen',
@@ -100,7 +100,7 @@ export default class ArticleViewStock extends PureComponent {
Bestand
</Typography>
{isFather ? (
{isParent ? (
<Box sx={{ my: 0.75 }}>
<Typography
component="div"
@@ -317,7 +317,7 @@ export default class ArticleViewStock extends PureComponent {
</Box>
</PanelTile>
{!isFather && !isStueckliste ? (
{!isParent && !isStueckliste ? (
<ArticleStockAdjustDialog
open={adjustOpen}
onClose={this.handleCloseAdjust}

View File

@@ -20,15 +20,15 @@ export default class ArticleAttributesTab extends PureComponent {
return this.props.article || getArticle(this.props.sku);
}
isFatherVariantAttribute(attr) {
isParentVariantAttribute(attr) {
const article = this.getArticle();
const isFather = Boolean(article?.isFather ?? article?.is_father);
if (!isFather || !attr) return false;
const fatherAttrId = article?.fatherAttributeNameId ?? article?.attributeNameId ?? null;
if (fatherAttrId != null && Number(attr.id) === Number(fatherAttrId)) return true;
const isParent = Boolean(article?.isParent ?? article?.is_parent);
if (!isParent || !attr) return false;
const parentAttrId = article?.parentAttributeNameId ?? article?.attributeNameId ?? null;
if (parentAttrId != null && Number(attr.id) === Number(parentAttrId)) return true;
if (
article?.fatherAttributeName &&
String(attr.name).trim().toLowerCase() === String(article.fatherAttributeName).trim().toLowerCase()
article?.parentAttributeName &&
String(attr.name).trim().toLowerCase() === String(article.parentAttributeName).trim().toLowerCase()
) {
return true;
}
@@ -41,16 +41,16 @@ export default class ArticleAttributesTab extends PureComponent {
const initialProperties = selectProperties() || [];
const article = props.article || getArticle(props.sku);
const isFather = Boolean(article?.isFather ?? article?.is_father);
const fatherAttrId = article?.fatherAttributeNameId ?? article?.attributeNameId ?? null;
const fatherAttrName = article?.fatherAttributeName;
const isParent = Boolean(article?.isParent ?? article?.is_parent);
const parentAttrId = article?.parentAttributeNameId ?? article?.attributeNameId ?? null;
const parentAttrName = article?.parentAttributeName;
const selectableAttrs = initialAttributes.filter((a) => {
if (!isFather) return true;
if (fatherAttrId != null && Number(a.id) === Number(fatherAttrId)) return false;
if (!isParent) return true;
if (parentAttrId != null && Number(a.id) === Number(parentAttrId)) return false;
if (
fatherAttrName &&
String(a.name).trim().toLowerCase() === String(fatherAttrName).trim().toLowerCase()
parentAttrName &&
String(a.name).trim().toLowerCase() === String(parentAttrName).trim().toLowerCase()
) {
return false;
}
@@ -231,7 +231,7 @@ export default class ArticleAttributesTab extends PureComponent {
if (effectiveKey.startsWith('attr-')) {
const attrId = Number(effectiveKey.replace('attr-', ''));
const targetAttr = selectableAttributes.find((a) => a.id === attrId);
if (!targetAttr || this.isFatherVariantAttribute(targetAttr)) return;
if (!targetAttr || this.isParentVariantAttribute(targetAttr)) return;
const effectiveItemId = targetAttr.values?.some((v) => v.id === selectedItemId)
? selectedItemId
@@ -411,7 +411,7 @@ export default class ArticleAttributesTab extends PureComponent {
merkmalOptions.find((m) => m.key === selectedMerkmalKey) ?? merkmalOptions[0] ?? null;
const currentMerkmalKey = selectedOption?.key ?? '';
const isAttr = selectedOption?.type === 'attr';
const isVariantBlocked = isAttr && this.isFatherVariantAttribute(selectedOption);
const isVariantBlocked = isAttr && this.isParentVariantAttribute(selectedOption);
const availableItems = selectedOption?.values || [];
const currentItemId = isAttr
? (availableItems.some((v) => v.id === selectedItemId)

View File

@@ -207,7 +207,7 @@ export default class ArticleSupplierTab extends PureComponent {
busyId,
} = this.state;
if (this.props.article?.isFather) {
if (this.props.article?.isParent) {
return (
<Box
sx={{