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 DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import Divider from '@mui/material/Divider'; import FormControl from '@mui/material/FormControl'; import FormLabel from '@mui/material/FormLabel'; import InputAdornment from '@mui/material/InputAdornment'; import List from '@mui/material/List'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import Paper from '@mui/material/Paper'; 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 TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import TextInput from './TextInput'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Typography from '@mui/material/Typography'; import BusinessIcon from '@mui/icons-material/Business'; import Inventory2Icon from '@mui/icons-material/Inventory2'; import PeopleIcon from '@mui/icons-material/People'; import LocalShippingIcon from '@mui/icons-material/LocalShipping'; import TuneIcon from '@mui/icons-material/Tune'; import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; import RequestQuoteIcon from '@mui/icons-material/RequestQuote'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import AssignmentReturnIcon from '@mui/icons-material/AssignmentReturn'; import BackendError from './BackendError'; import authAutomat from '../automats/authAutomat'; import companyAutomat from '../automats/companyAutomat'; import { defaultIso639, ISO639_OPTIONS, parseTaxRate, } from '../utils/companyForm'; import { backendErrorFrom, isBackendFailure } from '../utils/backendError'; const NUMBER_SCHEMES = [ { type: 'sku', label: 'Artikelnummer (ArtNr)', icon: Inventory2Icon }, { type: 'entity', label: 'Kundennummer', icon: PeopleIcon }, { type: 'delivery', label: 'Lieferschein', icon: LocalShippingIcon }, { type: 'adjustment', label: 'Bestandskorrektur', icon: TuneIcon }, { type: 'quote', label: 'Angebot', icon: RequestQuoteIcon }, { type: 'order', label: 'Bestellung', icon: ShoppingCartIcon }, { type: 'return', label: 'Rücksendung (Lieferant)', icon: AssignmentReturnIcon }, { type: 'retoure', label: 'Retoure (Kunde)', icon: AssignmentReturnIcon }, ]; const PRESETS = [ { id: 'standard', label: 'Standard', hint: 'Klassische Kürzel (ART-, KD-, LS-, KORR-, ANG-, AUF-, RTN-, RET-)', values: { sku: { prefix: 'ART-', padding: 6, next: 1 }, entity: { prefix: 'KD-', padding: 6, next: 1001 }, delivery: { prefix: 'LS-', padding: 6, next: 1 }, adjustment: { prefix: 'KORR-', padding: 5, next: 1 }, quote: { prefix: 'ANG-', padding: 6, next: 1 }, order: { prefix: 'AUF-', padding: 6, next: 1 }, return: { prefix: 'RTN-', padding: 6, next: 1 }, retoure: { prefix: 'RET-', padding: 6, next: 1 }, }, }, { id: 'numeric', label: 'Rein numerisch', hint: 'Ohne Präfix, mit Nummernblöcken (10001, 20001…)', values: { sku: { prefix: '', padding: 5, next: 10001 }, entity: { prefix: '', padding: 5, next: 20001 }, delivery: { prefix: '', padding: 5, next: 30001 }, adjustment: { prefix: '', padding: 5, next: 90001 }, quote: { prefix: '', padding: 5, next: 40001 }, order: { prefix: '', padding: 5, next: 50001 }, return: { prefix: 'RTN', padding: 5, next: 1 }, retoure: { prefix: 'RET', padding: 5, next: 1 }, }, }, { id: 'compact', label: 'Kurzkürzel', hint: 'Einbuchstabige Präfixe (A-, K-, L-, B-, AG-, AU-, RTN-, RET-)', values: { sku: { prefix: 'A-', padding: 5, next: 1 }, entity: { prefix: 'K-', padding: 5, next: 1001 }, delivery: { prefix: 'L-', padding: 5, next: 1 }, adjustment: { prefix: 'B-', padding: 5, next: 1 }, quote: { prefix: 'AG-', padding: 5, next: 1 }, order: { prefix: 'AU-', padding: 5, next: 1 }, return: { prefix: 'RTN-', padding: 5, next: 1 }, retoure: { prefix: 'RET-', padding: 5, next: 1 }, }, }, { id: 'international', label: 'International', hint: 'Englische Kürzel (SKU-, C-, DEL-, ADJ-, Q-, ORD-, RTN-, RET-)', values: { sku: { prefix: 'SKU-', padding: 5, next: 1 }, entity: { prefix: 'C-', padding: 5, next: 1001 }, delivery: { prefix: 'DEL-', padding: 5, next: 1 }, adjustment: { prefix: 'ADJ-', padding: 4, next: 1 }, quote: { prefix: 'Q-', padding: 5, next: 1 }, order: { prefix: 'ORD-', padding: 5, next: 1 }, return: { prefix: 'RTN-', padding: 5, next: 1 }, retoure: { prefix: 'RET-', padding: 5, next: 1 }, }, }, ]; function formatNumberPreview(prefix, padding, next, offset = 0) { const pad = Math.max(0, Number(padding) || 0); const num = Math.max(0, (Number(next) || 1) + offset); const numStr = pad > 0 ? String(num).padStart(pad, '0') : String(num); return `${prefix ?? ''}${numStr}`; } function mapCompanyState(state) { return { companies: Array.isArray(state?.companies) ? state.companies : [], loading: state?.status === 'idle' || state?.status === 'loading', }; } class CompanySetupDialog extends PureComponent { constructor(props) { super(props); this.state = { ...mapCompanyState(companyAutomat.state), busy: false, error: null, name: '', iso639: defaultIso639(), taxrate: '19', reducedtaxrate: '7', selectedPreset: 'standard', numbers: { ...PRESETS[0].values }, }; this.handleSelect = this.handleSelect.bind(this); this.handleCreate = this.handleCreate.bind(this); this.handleLogout = this.handleLogout.bind(this); this.handleClose = this.handleClose.bind(this); this.applyPreset = this.applyPreset.bind(this); this.handleNumberChange = this.handleNumberChange.bind(this); } componentDidMount() { this.unsubscribe = companyAutomat.subscribe(this, mapCompanyState); this.setState(mapCompanyState(companyAutomat.state)); if (companyAutomat.state.status !== 'succeeded') { void companyAutomat.actions.load().catch(() => {}); } } componentWillUnmount() { this.unsubscribe?.(); } async handleSelect(companyId) { this.setState({ busy: true, error: null }); try { await authAutomat.actions.selectCompany(companyId); } catch (err) { this.setState({ error: backendErrorFrom(err) }); } finally { this.setState({ busy: false }); } } applyPreset(presetId) { const preset = PRESETS.find((p) => p.id === presetId); if (!preset) return; this.setState({ selectedPreset: presetId, numbers: { ...preset.values }, }); } handleNumberChange(type, field, val) { this.setState((prev) => ({ selectedPreset: 'custom', numbers: { ...prev.numbers, [type]: { ...prev.numbers[type], [field]: val, }, }, })); } async handleCreate(event) { event.preventDefault(); const { name, iso639, taxrate, reducedtaxrate, numbers } = this.state; const parsedTaxrate = parseTaxRate(taxrate); const parsedReducedtaxrate = parseTaxRate(reducedtaxrate); const canCreate = name.trim() !== '' && ISO639_OPTIONS.includes(iso639) && parsedTaxrate != null && parsedReducedtaxrate != null; if (!canCreate) { this.setState({ error: 'Bitte alle Pflichtfelder ausfüllen.' }); return; } this.setState({ busy: true, error: null }); try { await authAutomat.actions.createCompany({ name: name.trim(), iso639, taxrate: parsedTaxrate, reducedtaxrate: parsedReducedtaxrate, numbers, }); } catch (err) { this.setState({ error: backendErrorFrom(err) }); } finally { this.setState({ busy: false }); } } async handleLogout() { try { await authAutomat.actions.logout(); } catch (err) { this.setState({ error: backendErrorFrom(err) }); } } handleClose(_event, reason) { if (reason === 'backdropClick' || reason === 'escapeKeyDown') return; } render() { const { companies, loading, busy, error, name, iso639, taxrate, reducedtaxrate, selectedPreset, numbers, } = this.state; const hasCompanies = companies.length > 0; const parsedTaxrate = parseTaxRate(taxrate); const parsedReducedtaxrate = parseTaxRate(reducedtaxrate); const canCreate = name.trim() !== '' && ISO639_OPTIONS.includes(iso639) && parsedTaxrate != null && parsedReducedtaxrate != null; return ( {error ? ( isBackendFailure(error) ? ( ) : ( {typeof error === 'string' ? error : error.message} ) ) : null} {loading ? ( ) : ( {hasCompanies ? ( {companies.map((item) => ( this.handleSelect(item.id)} > ))} ) : null} {hasCompanies ? oder : null} {hasCompanies ? ( Neues Unternehmen anlegen ) : null} this.setState({ name: e.target.value })} autoFocus={!hasCompanies} /> Sprache { if (value) this.setState({ iso639: value }); }} > Deutsch English this.setState({ taxrate: e.target.value })} error={taxrate !== '' && parsedTaxrate == null} slotProps={{ input: { endAdornment: %, }, htmlInput: { min: 0, max: 100, step: 0.01, required: true }, }} /> this.setState({ reducedtaxrate: e.target.value })} error={reducedtaxrate !== '' && parsedReducedtaxrate == null} slotProps={{ input: { endAdornment: %, }, htmlInput: { min: 0, max: 100, step: 0.01, required: true }, }} /> {/* Nummernkreise Section (when no company was created yet or creating new) */} {PRESETS.map((p) => { const isSelected = selectedPreset === p.id; return ( ); })} {selectedPreset === 'custom' ? ( ) : null} Bereich Präfix Stellen Startnr. Vorschau {NUMBER_SCHEMES.map(({ type, label, icon: Icon }) => { const cfg = numbers[type] || { prefix: '', padding: 5, next: 1 }; return ( {label} this.handleNumberChange(type, 'prefix', e.target.value)} placeholder="z.B. ART-" sx={{ input: { fontFamily: 'monospace', fontSize: '0.85rem' } }} /> this.handleNumberChange(type, 'padding', e.target.value)} /> this.handleNumberChange(type, 'next', e.target.value)} /> → ); })}
)}
); } } export default CompanySetupDialog;