This commit is contained in:
seb
2026-10-05 17:13:52 +02:00
parent 5c48d1be65
commit f627ff5b5a
5 changed files with 1377 additions and 28 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1485,7 +1485,7 @@ export default class ArticleViewLists extends PureComponent {
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, fontSize: '0.88rem' }}>
Variantsvater für
Vater für Auswahl-Merkmal
</Typography>
<Chip
label={articleData.parentAttributeName || parentAttrDef?.name || 'Varianten'}

View File

@@ -5,3 +5,4 @@ export { default as ArticleImageSearchDialog } from './ArticleImageSearchDialog'
export { default as ArticleTileTab } from './ArticleTileTab';
export { default as NewArticleTile } from './NewArticleTile';
export { default as ArticleSearch } from './ArticleSearch';
export { default as ArticleUnifyDialog } from './ArticleUnifyDialog';

View File

@@ -1,16 +1,53 @@
import { Component } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Tooltip from '@mui/material/Tooltip';
import DeleteIcon from '@mui/icons-material/Delete';
import CallMergeIcon from '@mui/icons-material/CallMerge';
import { enqueueSnackbar } from 'notistack';
import { article as articleApi } from '../../../api';
import { artikelAutomat } from '../../../automats';
import { artikelAutomat, getArticle } from '../../../automats';
import { notifyError } from '../../../utils/notify';
import ArticleSearch from '../ArticleSearch';
import ArticleUnifyDialog from '../ArticleUnifyDialog';
export function isArticleFather(article) {
if (!article) return false;
return Boolean(
article.isParent ||
article.is_parent ||
(article.childCount ?? 0) > 0 ||
(Array.isArray(article.children) && article.children.length > 0) ||
article.parent_attribute_id != null ||
article.parentAttributeNameId != null
);
}
export function isArticleStueckliste(article) {
if (!article) return false;
return Boolean(
article.articlelistId != null ||
(article.articleListCount ?? 0) > 0 ||
(Array.isArray(article.articlelist) && article.articlelist.length > 0) ||
(Array.isArray(article.components) && article.components.length > 0) ||
(Array.isArray(article.stueckliste) && article.stueckliste.length > 0)
);
}
export default class ArticleSettingsTab extends Component {
constructor(props) {
super(props);
this.state = { busy: false };
this.state = {
busy: false,
searchOpen: false,
unifyOpen: false,
targetArticle: null,
};
this.handleDeleteArticle = this.handleDeleteArticle.bind(this);
this.handleOpenSearch = this.handleOpenSearch.bind(this);
this.handleSelectTargetArticle = this.handleSelectTargetArticle.bind(this);
}
async handleDeleteArticle() {
@@ -31,21 +68,131 @@ export default class ArticleSettingsTab extends Component {
}
}
handleOpenSearch() {
const { article: articleData } = this.props;
if (isArticleFather(articleData)) {
enqueueSnackbar('Ein Vaterartikel kann nicht vereint werden.', { variant: 'warning' });
return;
}
if (isArticleStueckliste(articleData)) {
enqueueSnackbar('Ein Stücklisten-Artikel kann nicht vereint werden.', { variant: 'warning' });
return;
}
this.setState({ searchOpen: true });
}
async handleSelectTargetArticle(selected) {
if (!selected?.sku) return;
this.setState({ searchOpen: false, busy: true });
try {
let fullTarget = getArticle(selected.sku);
if (!fullTarget || !fullTarget.warehouses || !fullTarget.prices) {
fullTarget = await artikelAutomat.actions.loadArticle(selected.sku);
}
if (isArticleFather(fullTarget)) {
enqueueSnackbar('Der ausgewählte Artikel ist ein Vaterartikel und kann nicht vereint werden.', {
variant: 'error',
});
this.setState({ busy: false });
return;
}
if (isArticleStueckliste(fullTarget)) {
enqueueSnackbar('Der ausgewählte Artikel ist ein Stücklisten-Artikel und kann nicht vereint werden.', {
variant: 'error',
});
this.setState({ busy: false });
return;
}
this.setState({
busy: false,
targetArticle: fullTarget,
unifyOpen: true,
});
} catch (err) {
this.setState({ busy: false });
notifyError(err);
}
}
render() {
const { busy } = this.state;
const { busy, searchOpen, unifyOpen, targetArticle } = this.state;
const { article: articleData } = this.props;
const isFather = isArticleFather(articleData);
const isStueckliste = isArticleStueckliste(articleData);
const cannotUnify = isFather || isStueckliste;
const unifyDisabledReason = isFather
? 'Ein Vaterartikel kann nicht vereint werden.'
: isStueckliste
? 'Ein Stücklisten-Artikel kann nicht vereint werden.'
: null;
return (
<Box>
<Button
type="button"
color="error"
variant="contained"
startIcon={<DeleteIcon />}
disabled={busy}
onClick={() => void this.handleDeleteArticle()}
>
Artikel löschen
</Button>
<Stack direction="row" spacing={1.5} alignItems="center" flexWrap="wrap">
<Button
type="button"
color="error"
variant="contained"
startIcon={<DeleteIcon />}
disabled={busy}
onClick={() => void this.handleDeleteArticle()}
>
Artikel löschen
</Button>
{unifyDisabledReason ? (
<Tooltip title={unifyDisabledReason}>
<span>
<Button
type="button"
color="primary"
variant="outlined"
startIcon={<CallMergeIcon />}
disabled
>
Artikel vereinen
</Button>
</span>
</Tooltip>
) : (
<Button
type="button"
color="primary"
variant="outlined"
startIcon={<CallMergeIcon />}
disabled={busy}
onClick={this.handleOpenSearch}
>
Artikel vereinen
</Button>
)}
</Stack>
<ArticleSearch
open={searchOpen}
allowParent={false}
excludeSkus={[articleData.sku]}
title="Artikel zum Vereinen auswählen"
placeholder="Artikel suchen…"
actionLabel="Auswählen"
onClose={() => this.setState({ searchOpen: false })}
onSelect={this.handleSelectTargetArticle}
/>
{unifyOpen && targetArticle && (
<ArticleUnifyDialog
open={unifyOpen}
articleLeft={articleData}
articleRight={targetArticle}
onClose={() => this.setState({ unifyOpen: false, targetArticle: null })}
/>
)}
</Box>
);
}
}

View File

@@ -13,7 +13,6 @@ import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
import { PanelTile } from 'dashgui/panels';
import { LINE_COLUMNS, hasArticleLines } from './manifestConfig';
import ManifestFormLine from './ManifestFormLine';
import ArticleSearch from '../article/ArticleSearch';
class ManifestLines extends PureComponent {
render() {
@@ -23,7 +22,6 @@ class ManifestLines extends PureComponent {
linesColor,
onLineChange,
onAddLine,
onAddArticleLine,
onRemoveLine,
onRemoveAllLines,
columns = LINE_COLUMNS,
@@ -120,24 +118,13 @@ class ManifestLines extends PureComponent {
</Table>
</TableContainer>
<Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center', flexWrap: 'wrap' }}>
{onAddArticleLine && (
<ArticleSearch
size="small"
clearOnSelect
allowParent={false}
placeholder="Artikel suchen und hinzufügen…"
disabled={saving}
onSelect={onAddArticleLine}
sx={{ minWidth: 260, flex: 1, maxWidth: 440 }}
/>
)}
<Button
size="small"
startIcon={<AddIcon />}
disabled={saving}
onClick={onAddLine}
>
{onAddArticleLine ? 'Leere Zeile' : 'Zeile hinzufügen'}
Zeile
</Button>
<Button
size="small"