import { PureComponent } from 'react'; import Alert from '@mui/material/Alert'; 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 AppDialog from '../AppDialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import FormControl from '@mui/material/FormControl'; import FormHelperText from '@mui/material/FormHelperText'; import IconButton from '@mui/material/IconButton'; import InputAdornment from '@mui/material/InputAdornment'; import InputLabel from '@mui/material/InputLabel'; import MenuItem from '@mui/material/MenuItem'; import Select from '@mui/material/Select'; import Stack from '@mui/material/Stack'; import TextInput from '../TextInput'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Typography from '@mui/material/Typography'; import CloseIcon from '@mui/icons-material/Close'; import WarehouseIcon from '@mui/icons-material/Warehouse'; import TrendingUpIcon from '@mui/icons-material/TrendingUp'; import TrendingDownIcon from '@mui/icons-material/TrendingDown'; import TuneIcon from '@mui/icons-material/Tune'; import Switch from '@mui/material/Switch'; import { enqueueSnackbar } from 'notistack'; import { stock } from '../../api'; import { formatStock } from './searchFormat'; import { notifyError } from '../../utils/notify'; import artikelAutomat from '../../automats/artikelAutomat'; import companyAutomat from '../../automats/companyAutomat'; import { BORDER, GREEN_BG, GREEN_PANEL, RED_BG, RED_PANEL, SURFACE_BASE, SURFACE_RAISED, } from '../../colors'; export default class ArticleStockAdjustDialog extends PureComponent { constructor(props) { super(props); const initialWhId = this.resolveInitialWarehouseId(props); const isTrackStock = props.article?.track_stock !== false && props.article?.track_stock !== 0; this.state = { mode: 'diff', // 'diff' | 'target' warehouseId: initialWhId, diffValue: '', targetValue: '', reason: '', submitting: false, error: null, warehouses: companyAutomat.state.warehouses || [], trackStock: isTrackStock, initialTrackStock: isTrackStock, }; this.handleWarehouseChange = this.handleWarehouseChange.bind(this); this.handleModeChange = this.handleModeChange.bind(this); this.handleDiffChange = this.handleDiffChange.bind(this); this.handleTargetChange = this.handleTargetChange.bind(this); this.handleReasonChange = this.handleReasonChange.bind(this); this.handleTrackStockToggle = this.handleTrackStockToggle.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } componentDidMount() { this.unsubWarehouse = companyAutomat.subscribe((state) => { const warehouses = state.warehouses || []; this.setState((prev) => ({ warehouses, warehouseId: prev.warehouseId || (warehouses.length > 0 ? warehouses[0].id : ''), })); }, ['warehouses']); } componentWillUnmount() { this.unsubWarehouse?.(); } componentDidUpdate(prevProps) { if (this.props.open && !prevProps.open) { const initialWhId = this.resolveInitialWarehouseId(this.props); const isTrackStock = this.props.article?.track_stock !== false && this.props.article?.track_stock !== 0; this.setState({ mode: 'diff', warehouseId: initialWhId, diffValue: '', targetValue: '', reason: '', submitting: false, error: null, warehouses: companyAutomat.state.warehouses || [], trackStock: isTrackStock, initialTrackStock: isTrackStock, }); } } resolveInitialWarehouseId(props) { const articleWarehouses = Array.isArray(props.article?.warehouses) ? props.article.warehouses : []; if (articleWarehouses.length > 0) { return articleWarehouses[0].id; } const allWarehouses = companyAutomat.state.warehouses || []; return allWarehouses.length > 0 ? allWarehouses[0].id : ''; } getCurrentStock(warehouseId) { const { article } = this.props; const wh = article?.warehouses?.find((w) => Number(w.id) === Number(warehouseId)); return wh?.stock != null ? Number(wh.stock) : 0; } handleWarehouseChange(event) { const nextWhId = event.target.value ? Number(event.target.value) : ''; this.setState({ warehouseId: nextWhId }); } handleModeChange(_event, nextMode) { if (!nextMode) return; this.setState({ mode: nextMode, error: null }); } handleDiffChange(event) { this.setState({ diffValue: event.target.value, error: null }); } handleTargetChange(event) { this.setState({ targetValue: event.target.value, error: null }); } handleReasonChange(event) { this.setState({ reason: event.target.value }); } handleTrackStockToggle(event) { this.setState({ trackStock: event.target.checked, error: null }); } calculateAdjustment() { const { mode, warehouseId, diffValue, targetValue, trackStock } = this.state; if (!trackStock) { return { valid: false, amount: 0, newStock: 0 }; } const currentStock = this.getCurrentStock(warehouseId); if (mode === 'diff') { const parsed = parseFloat(String(diffValue).replace(',', '.')); if (!Number.isFinite(parsed) || parsed === 0) { return { valid: false, amount: 0, newStock: currentStock }; } return { valid: true, amount: parsed, newStock: currentStock + parsed, }; } else { const parsed = parseFloat(String(targetValue).replace(',', '.')); if (!Number.isFinite(parsed)) { return { valid: false, amount: 0, newStock: currentStock }; } const amount = parsed - currentStock; if (amount === 0) { return { valid: false, amount: 0, newStock: currentStock, isZeroChange: true }; } return { valid: true, amount, newStock: parsed, }; } } async handleSubmit(event) { event?.preventDefault(); const { article, onClose, onAdjustSuccess, onArticleUpdated } = this.props; const { warehouseId, reason, submitting, trackStock, initialTrackStock } = this.state; if (submitting) return; const trackStockChanged = trackStock !== initialTrackStock; const { valid, amount } = this.calculateAdjustment(); const hasAdjustment = trackStock && valid && amount !== 0; if (!trackStockChanged && !hasAdjustment) { if (trackStock && (!valid || amount === 0)) { this.setState({ error: 'Bitte geben Sie eine gültige Bestandsänderung ein (ungleich 0).' }); } return; } if (hasAdjustment && !warehouseId) { this.setState({ error: 'Bitte wählen Sie ein Lager aus.' }); return; } this.setState({ submitting: true, error: null }); try { let updatedArticle = null; // 1. Update track_stock if changed if (trackStockChanged) { const trackRes = await artikelAutomat.actions.setTrackStock(article.sku, { track_stock: trackStock, }); updatedArticle = trackRes?.article || null; } // 2. Adjust stock if an adjustment was entered and stock is tracked let adjustResult = null; if (hasAdjustment) { const payload = { sku: article.sku, warehouseId: Number(warehouseId), amount, text: reason.trim() || undefined, }; adjustResult = await stock.adjust(payload); } // User feedback if (trackStockChanged && hasAdjustment) { enqueueSnackbar( `Bestandsführung ${trackStock ? 'aktiviert' : 'deaktiviert'} und Bestand angepasst.`, { variant: 'success' } ); } else if (trackStockChanged) { enqueueSnackbar( `Bestandsführung für „${article.name || article.sku}“ ${trackStock ? 'aktiviert' : 'deaktiviert'}.`, { variant: 'success' } ); } else if (hasAdjustment) { enqueueSnackbar( `Bestand für „${article.name || article.sku}“ erfolgreich angepasst (${amount > 0 ? `+${amount}` : amount}).`, { variant: 'success' } ); } // Reload article in automat cache void artikelAutomat.actions.reloadArticle(article.sku).catch(() => { }); if (updatedArticle) { onArticleUpdated?.(updatedArticle); } if (adjustResult && onAdjustSuccess) { onAdjustSuccess(adjustResult); } if (onClose) { onClose(); } } catch (err) { console.error('[ArticleStockAdjustDialog] Adjust failed:', err); notifyError(err); this.setState({ submitting: false, error: err.message || 'Fehler beim Anpassen des Bestands.', }); } } render() { const { open, onClose, article } = this.props; const { mode, warehouseId, diffValue, targetValue, reason, submitting, error, warehouses, trackStock, initialTrackStock, } = this.state; const trackStockChanged = trackStock !== initialTrackStock; const currentStock = this.getCurrentStock(warehouseId); const { valid, amount, newStock, isZeroChange } = this.calculateAdjustment(); const hasAdjustment = trackStock && valid && amount !== 0; const canSubmit = !submitting && (trackStockChanged || (hasAdjustment && Boolean(warehouseId))); const selectedWarehouse = warehouses.find((w) => Number(w.id) === Number(warehouseId)); const unit = article?.unit?.symbol || article?.unit?.name || ''; return ( Bestand anpassen {/* Article overview */} Artikel ({article?.sku}) {article?.name || '–'} {/* Track Stock Toggle Card */} Bestand verfolgen {warehouses.length === 0 ? ( Es sind keine Lager vorhanden. Bitte legen Sie zuerst unter „Artikel → Lager“ ein Lager an. ) : ( {/* Warehouse selection & Current stock display */} Lager * Lagerort für die Korrektur Aktueller Bestand {formatStock(currentStock)} {unit && {unit}} {/* Adjustment Mode Toggle */} Art der Anpassung Zu-/abbuchen (+ / -) Bestand setzen {/* Amount input */} {mode === 'diff' ? ( {Number(diffValue) < 0 ? ( ) : ( )} ), }, }} /> ) : ( )} {/* Summary calculation pill */} {valid && amount !== 0 && ( 0 ? GREEN_BG : RED_BG, border: `1px solid ${amount > 0 ? GREEN_PANEL : RED_PANEL}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', }} > Lager: {selectedWarehouse?.name || '–'} {formatStock(currentStock)} → {formatStock(newStock)} 0 ? 'success' : 'error'} label={`${amount > 0 ? `+${formatStock(amount)}` : formatStock(amount)}${unit ? ` ${unit}` : ''}`} sx={{ fontWeight: 700 }} /> )} {/* Reason / Memo */} )} {error && ( {error} )} ); } }