import { PureComponent } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; import CircularProgress from '@mui/material/CircularProgress'; import IconButton from '@mui/material/IconButton'; import Stack from '@mui/material/Stack'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import AddIcon from '@mui/icons-material/Add'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined'; import { enqueueSnackbar } from 'notistack'; import { article as articleApi } from '../../../api'; import { notifyError } from '../../../utils/notify'; import { formatPrice } from '../../../utils/price'; import supplierAutomat from '../../../automats/supplierAutomat'; import { articleSupplierAutomat } from '../../../automats/articleDetailAutomat'; import sseAutomat from '../../../automats/sseAutomat'; import companyAutomat from '../../../automats/companyAutomat'; import ArticleSupplierDialog from '../ArticleSupplierDialog'; import { SORT_GHOST_BG, TINT_FAINT, } from '../../../colors'; export default class ArticleSupplierTab extends PureComponent { constructor(props) { super(props); this.state = { loading: true, assignedSuppliers: [], companySuppliers: [], company: null, dialogOpen: false, editItem: null, busyId: null, }; } componentDidMount() { this.unsubCompany = companyAutomat.subscribe(this, (state) => ({ company: state.current ?? null, })); this.initData(); } componentDidUpdate(prevProps) { if (prevProps.article?.sku !== this.props.article?.sku) { this.initData(); } } componentWillUnmount() { this.unsubSuppliers?.(); this.unsubSuppliers = null; this.unwatchSignal?.(); this.unwatchSignal = null; this.unsubCompany?.(); this.unsubCompany = null; } async initData() { const sku = this.props.article?.sku; if (!sku) return; // 1. Subscribe to the keyed supplier cache and the article SSE signal this.unsubSuppliers?.(); const handle = articleSupplierAutomat.key(String(sku)); this.unsubSuppliers = handle.subscribe(this, (item) => { this.setState({ assignedSuppliers: Array.isArray(item.data) ? item.data : [], loading: false, }); }); void articleSupplierAutomat.load(String(sku)).catch((err) => { if (this.unsubSuppliers) notifyError(err); }); this.unwatchSignal?.(); this.unwatchSignal = sseAutomat.actions.watchArticle(String(sku), () => { void articleSupplierAutomat.reload(String(sku)).catch(() => {}); }); // 2. Fetch available company suppliers try { const companySuppliers = await supplierAutomat.actions.load(); this.setState({ companySuppliers: companySuppliers ?? [] }); } catch (err) { notifyError(err); this.setState({ loading: false }); } } /** Re-fetches the assigned suppliers for the current article. */ refreshSuppliers = async () => { const sku = this.props.article?.sku; if (!sku) return; await articleSupplierAutomat.reload(String(sku)); }; handleOpenAdd = () => { this.setState({ dialogOpen: true, editItem: null }); }; handleOpenEdit = (item) => { this.setState({ dialogOpen: true, editItem: item }); }; handleCloseDialog = () => { this.setState({ dialogOpen: false, editItem: null }); }; handleSave = async (payload) => { const sku = this.props.article?.sku; if (!sku) return; try { if (payload.id) { await articleApi.updateSupplier(sku, payload.id, { sku: payload.sku, pu: payload.pu, stock: payload.stock, netPrice: payload.netPrice, taxRate: payload.taxRate, }); enqueueSnackbar('Lieferantendaten aktualisiert', { variant: 'success' }); } else { await articleApi.addSupplier(sku, { entityId: payload.entityId, sku: payload.sku, pu: payload.pu, stock: payload.stock, netPrice: payload.netPrice, taxRate: payload.taxRate, }); enqueueSnackbar('Lieferant erfolgreich zugewiesen', { variant: 'success' }); } this.handleCloseDialog(); await this.refreshSuppliers(); } catch (err) { notifyError(err); throw err; } }; handleDelete = async (item) => { const sku = this.props.article?.sku; if (!sku) return; const supplierName = item.supplierName ?? `Lieferant #${item.entityId}`; if (!window.confirm(`Lieferantenzuweisung zu „${supplierName}“ wirklich löschen?`)) { return; } this.setState({ busyId: item.id }); try { await articleApi.deleteSupplier(sku, item.id); enqueueSnackbar('Lieferant entfernt', { variant: 'success' }); await this.refreshSuppliers(); } catch (err) { notifyError(err); } finally { this.setState({ busyId: null }); } }; handleMove = async (index, direction) => { const sku = this.props.article?.sku; const { assignedSuppliers } = this.state; const targetIndex = index + direction; if (targetIndex < 0 || targetIndex >= assignedSuppliers.length || !sku) { return; } const nextList = [...assignedSuppliers]; const [moved] = nextList.splice(index, 1); nextList.splice(targetIndex, 0, moved); const ids = nextList.map((row) => row.id); this.setState({ assignedSuppliers: nextList, busyId: moved.id }); try { await articleApi.orderSuppliers(sku, ids); await this.refreshSuppliers(); } catch (err) { notifyError(err); await this.refreshSuppliers(); } finally { this.setState({ busyId: null }); } }; render() { const { loading, assignedSuppliers, companySuppliers, company, dialogOpen, editItem, busyId, } = this.state; if (this.props.article?.isParent) { return ( Lieferanten sind für Vaterartikel deaktiviert Lieferanten, Einkaufspreise und Lieferanten-Artikelnummern werden für jede Variante separat gepflegt. ); } if (loading) { return ( ); } const assignedEntityIds = assignedSuppliers.map((s) => s.entityId); return ( {/* Header Bar */} {/* Content Table */} Reihe Lieferant Lief.-Art.-Nr. VPE (PU) Lagerbestand Einkaufspreis Aktionen {assignedSuppliers.map((row, index) => { const isBusy = busyId === row.id; const netPrice = row.price?.net; const taxRate = row.price?.taxRate; return ( {/* Sort Order & Reorder Controls */} {index + 1} this.handleMove(index, -1)} sx={{ p: 0.2 }} > this.handleMove(index, 1)} sx={{ p: 0.2 }} > {/* Supplier Name */} {row.supplierName ?? `Lieferant #${row.entityId}`} {/* Supplier SKU */} {row.sku ? ( {row.sku} ) : ( — )} {/* PU / VPE */} {row.pu ?? 1} {/* Stock */} {row.stock != null ? ( 0 ? 'success' : 'default'} variant="outlined" sx={{ fontWeight: 500, height: 22, fontSize: '0.75rem' }} /> ) : ( — )} {/* Purchase Price Net & Tax */} {netPrice != null ? ( {formatPrice(netPrice)} {taxRate != null ? ( zzgl. {taxRate} % MwSt. ) : null} ) : ( — )} {/* Actions */} this.handleOpenEdit(row)} disabled={isBusy} > this.handleDelete(row)} disabled={isBusy} > ); })}
{/* Add / Edit Dialog */}
); } }