233 lines
7.8 KiB
JavaScript
233 lines
7.8 KiB
JavaScript
import { PureComponent } from 'react';
|
|
import Autocomplete from '@mui/material/Autocomplete';
|
|
import Box from '@mui/material/Box';
|
|
import Button from '@mui/material/Button';
|
|
import AppDialog from '../AppDialog';
|
|
import DialogActions from '@mui/material/DialogActions';
|
|
import DialogContent from '@mui/material/DialogContent';
|
|
import DialogTitle from '@mui/material/DialogTitle';
|
|
import InputAdornment from '@mui/material/InputAdornment';
|
|
import MenuItem from '@mui/material/MenuItem';
|
|
import Stack from '@mui/material/Stack';
|
|
import TextInput from '../TextInput';
|
|
import Typography from '@mui/material/Typography';
|
|
import { companyTaxRateOptions } from '../../utils/articleDetail';
|
|
import { formatPriceAmount, parsePriceInput } from '../../utils/price';
|
|
|
|
function parseNumber(value, min = 0) {
|
|
if (value === '' || value == null) return null;
|
|
const num = Number(String(value).trim().replace(',', '.'));
|
|
return Number.isFinite(num) && num >= min ? num : null;
|
|
}
|
|
|
|
export default class ArticleSupplierDialog extends PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = this.initState(props);
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (this.props.open && !prevProps.open) {
|
|
this.setState(this.initState(this.props));
|
|
}
|
|
}
|
|
|
|
initState(props) {
|
|
const item = props.item;
|
|
const defaultTaxRate =
|
|
props.company?.taxrate != null ? Number(props.company.taxrate) : 19;
|
|
|
|
return {
|
|
entityId: item?.entityId ? Number(item.entityId) : null,
|
|
sku: item?.sku ?? '',
|
|
pu: item?.pu != null ? String(item.pu) : '1',
|
|
stock: item?.stock != null ? String(item.stock) : '',
|
|
netPrice:
|
|
item?.price?.net != null ? formatPriceAmount(item.price.net) : '',
|
|
taxRate:
|
|
item?.price?.taxRate != null
|
|
? Number(item.price.taxRate)
|
|
: defaultTaxRate,
|
|
submitting: false,
|
|
};
|
|
}
|
|
|
|
handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
const { entityId, sku, pu, stock, netPrice, taxRate } = this.state;
|
|
const isEdit = Boolean(this.props.item?.id);
|
|
|
|
if (!isEdit && !entityId) return;
|
|
|
|
const parsedPu = parseNumber(pu, 0.0001);
|
|
if (parsedPu == null || parsedPu <= 0) {
|
|
return;
|
|
}
|
|
|
|
const parsedStock = parseNumber(stock, 0);
|
|
const parsedNet = netPrice !== '' ? parsePriceInput(netPrice) : null;
|
|
|
|
this.setState({ submitting: true });
|
|
try {
|
|
await this.props.onSave({
|
|
id: this.props.item?.id,
|
|
entityId: isEdit ? this.props.item.entityId : entityId,
|
|
sku: sku.trim() || null,
|
|
pu: parsedPu,
|
|
stock: parsedStock,
|
|
netPrice: parsedNet,
|
|
taxRate: parsedNet != null ? taxRate : undefined,
|
|
});
|
|
} finally {
|
|
this.setState({ submitting: false });
|
|
}
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
open,
|
|
onClose,
|
|
suppliers = [],
|
|
item,
|
|
company,
|
|
assignedEntityIds = [],
|
|
} = this.props;
|
|
const { entityId, sku, pu, stock, netPrice, taxRate, submitting } =
|
|
this.state;
|
|
const isEdit = Boolean(item?.id);
|
|
|
|
const availableSuppliers = isEdit
|
|
? suppliers
|
|
: suppliers.filter(
|
|
(s) => !assignedEntityIds.includes(s.id) || s.id === entityId,
|
|
);
|
|
|
|
const selectedSupplier =
|
|
suppliers.find((s) => s.id === entityId) ?? null;
|
|
|
|
const taxOptions = companyTaxRateOptions(company, taxRate);
|
|
|
|
const parsedPu = parseNumber(pu, 0.0001);
|
|
const puValid = parsedPu != null && parsedPu > 0;
|
|
|
|
return (
|
|
<AppDialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
|
<form onSubmit={this.handleSubmit}>
|
|
<DialogTitle sx={{ fontWeight: 600 }}>
|
|
{isEdit ? 'Lieferant bearbeiten' : 'Lieferant zuweisen'}
|
|
</DialogTitle>
|
|
|
|
<DialogContent dividers>
|
|
<Stack spacing={2.5}>
|
|
{isEdit ? (
|
|
<Box>
|
|
<Typography variant="caption" color="text.secondary" display="block">
|
|
Lieferant
|
|
</Typography>
|
|
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
|
{item.supplierName ?? `Lieferant #${item.entityId}`}
|
|
</Typography>
|
|
</Box>
|
|
) : (
|
|
<Autocomplete
|
|
options={availableSuppliers}
|
|
getOptionLabel={(opt) => opt.name ?? `Lieferant #${opt.id}`}
|
|
value={selectedSupplier}
|
|
onChange={(_e, val) => this.setState({ entityId: val ? val.id : null })}
|
|
renderInput={(params) => (
|
|
<TextInput
|
|
{...params}
|
|
label="Lieferant auswählen"
|
|
required
|
|
placeholder="Lieferant suchen…"
|
|
/>
|
|
)}
|
|
isOptionEqualToValue={(opt, val) => opt.id === val.id}
|
|
/>
|
|
)}
|
|
|
|
<TextInput
|
|
label="Lieferanten-Artikelnummer (SKU)"
|
|
value={sku}
|
|
onChange={(e) => this.setState({ sku: e.target.value })}
|
|
placeholder="z. B. LF-09412"
|
|
helperText="Wird beim Abgleich von Lieferscheinen / Wareneingängen genutzt"
|
|
/>
|
|
|
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
|
<TextInput
|
|
label="Verpackungseinheit (VPE / PU)"
|
|
type="number"
|
|
required
|
|
error={!puValid}
|
|
helperText={!puValid ? 'Muss größer als 0 sein' : undefined}
|
|
slotProps={{ htmlInput: { step: 'any', min: 0.0001 } }}
|
|
value={pu}
|
|
onChange={(e) => this.setState({ pu: e.target.value })}
|
|
sx={{ flex: 1 }}
|
|
/>
|
|
|
|
<TextInput
|
|
label="Bestand beim Lieferanten"
|
|
type="number"
|
|
slotProps={{ htmlInput: { step: 'any', min: 0 } }}
|
|
value={stock}
|
|
onChange={(e) => this.setState({ stock: e.target.value })}
|
|
sx={{ flex: 1 }}
|
|
helperText="Optional (in Stk / Mengeneinheit)"
|
|
/>
|
|
</Stack>
|
|
|
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
|
<TextInput
|
|
label="Einkaufspreis Netto"
|
|
value={netPrice}
|
|
onChange={(e) => this.setState({ netPrice: e.target.value })}
|
|
placeholder="0,00"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">€</InputAdornment>
|
|
),
|
|
}}
|
|
sx={{ flex: 1 }}
|
|
helperText="Optionaler Einkaufspreis (EK)"
|
|
/>
|
|
|
|
<TextInput
|
|
select
|
|
label="MwSt.-Satz"
|
|
value={taxRate}
|
|
onChange={(e) =>
|
|
this.setState({ taxRate: Number(e.target.value) })
|
|
}
|
|
sx={{ width: { sm: 160 } }}
|
|
disabled={!netPrice}
|
|
>
|
|
{taxOptions.map((rate) => (
|
|
<MenuItem key={rate} value={rate}>
|
|
{rate} %
|
|
</MenuItem>
|
|
))}
|
|
</TextInput>
|
|
</Stack>
|
|
</Stack>
|
|
</DialogContent>
|
|
|
|
<DialogActions sx={{ px: 3, py: 2 }}>
|
|
<Button onClick={onClose} color="inherit" disabled={submitting}>
|
|
Abbrechen
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
variant="contained"
|
|
disabled={submitting || (!isEdit && !entityId) || !puValid}
|
|
>
|
|
{isEdit ? 'Aktualisieren' : 'Hinzufügen'}
|
|
</Button>
|
|
</DialogActions>
|
|
</form>
|
|
</AppDialog>
|
|
);
|
|
}
|
|
}
|