u
This commit is contained in:
@@ -30,8 +30,15 @@ import { articleSupplierAutomat } from '../../automats/articleDetailAutomat';
|
||||
import { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||
import {
|
||||
BORDER,
|
||||
GREEN,
|
||||
alpha,
|
||||
} from '../../colors';
|
||||
|
||||
const LOCKED_ROW_BG = alpha(GREEN, 0.08);
|
||||
const LOCKED_ROW_HOVER_BG = alpha(GREEN, 0.12);
|
||||
const LOCKED_CELL_BORDER = alpha(GREEN, 0.32);
|
||||
const LOCKED_CELL_BG = alpha(GREEN, 0.08);
|
||||
|
||||
class ManifestFormLine extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -60,6 +67,16 @@ class ManifestFormLine extends PureComponent {
|
||||
return props.line?.sku ? String(props.line.sku).trim() : null;
|
||||
}
|
||||
|
||||
isFullyLedgered(props = this.props) {
|
||||
const { ledgerLine, line } = props;
|
||||
return Boolean(
|
||||
ledgerLine?.isBooked ||
|
||||
(ledgerLine && ledgerLine.targetAmount > 0 && ledgerLine.remainingAmount <= 0) ||
|
||||
line?.isBooked ||
|
||||
line?.is_booked
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribeArticle(this.getArticleSku());
|
||||
}
|
||||
@@ -125,7 +142,7 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
|
||||
handleRemove() {
|
||||
if (this.props.isOnShipment) return;
|
||||
if (this.props.isOnShipment || this.isFullyLedgered()) return;
|
||||
this.props.onRemove(this.props.line.key);
|
||||
}
|
||||
|
||||
@@ -139,7 +156,8 @@ class ManifestFormLine extends PureComponent {
|
||||
|
||||
handleStartEdit(columnId) {
|
||||
const { saving } = this.props;
|
||||
const isDisabled = saving || (columnId === 'sku' && Boolean(this.props.isOnShipment));
|
||||
const isFullyLedgered = this.isFullyLedgered();
|
||||
const isDisabled = saving || isFullyLedgered || (columnId === 'sku' && Boolean(this.props.isOnShipment));
|
||||
if (isDisabled) return;
|
||||
if (this.state.editingField !== columnId) {
|
||||
this.setState({ editingField: columnId });
|
||||
@@ -162,6 +180,7 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
|
||||
handleCreateArticle() {
|
||||
if (this.isFullyLedgered()) return;
|
||||
const { line, onCreateArticle } = this.props;
|
||||
if (onCreateArticle) {
|
||||
onCreateArticle(line);
|
||||
@@ -169,6 +188,7 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
|
||||
handleSearchArticle() {
|
||||
if (this.isFullyLedgered()) return;
|
||||
const { line, onSearchArticle } = this.props;
|
||||
if (onSearchArticle) {
|
||||
onSearchArticle(line);
|
||||
@@ -176,107 +196,14 @@ class ManifestFormLine extends PureComponent {
|
||||
}
|
||||
|
||||
handleRemoveSupplierArticle() {
|
||||
if (this.isFullyLedgered()) return;
|
||||
const { line, supplierArticle, onRemoveSupplierArticle } = this.props;
|
||||
if (onRemoveSupplierArticle) {
|
||||
onRemoveSupplierArticle(line, supplierArticle);
|
||||
}
|
||||
}
|
||||
|
||||
renderLedgerChip() {
|
||||
const { ledgerLine, onBookLine } = this.props;
|
||||
if (!ledgerLine) return null;
|
||||
const isBooked = ledgerLine.isBooked;
|
||||
const isPartial = !isBooked && ledgerLine.bookedAmount > 0;
|
||||
const isClickable = Boolean(onBookLine);
|
||||
const isTracked = Boolean(ledgerLine.trackStock);
|
||||
|
||||
const tooltipTitle = isTracked
|
||||
? isBooked
|
||||
? `Vollständig im Lager gebucht: ${ledgerLine.bookedAmount} Stück (Klicken für Details)`
|
||||
: isPartial
|
||||
? `Teilweise im Lager gebucht: ${ledgerLine.bookedAmount} von ${ledgerLine.targetAmount} Stück (Klicken zum Einbuchen dieser Position)`
|
||||
: `Noch nicht im Lager gebucht (${ledgerLine.targetAmount} Stück – Klicken zum Einbuchen dieser Position)`
|
||||
: isBooked
|
||||
? `Vollständig im Ledger verbucht: ${ledgerLine.bookedAmount} Stück (Klicken für Details)`
|
||||
: isPartial
|
||||
? `Teilweise im Ledger verbucht: ${ledgerLine.bookedAmount} von ${ledgerLine.targetAmount} Stück (Klicken zum Verbuchen dieser Position)`
|
||||
: `Noch nicht im Ledger verbucht (${ledgerLine.targetAmount} Stück – Klicken zum Verbuchen dieser Position)`;
|
||||
|
||||
const label = isTracked
|
||||
? isBooked
|
||||
? `Lager: ${ledgerLine.bookedAmount} Stk. gebucht`
|
||||
: isPartial
|
||||
? `Lager: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.`
|
||||
: 'Lager: offen'
|
||||
: isBooked
|
||||
? `Gebucht: ${ledgerLine.bookedAmount} Stk.`
|
||||
: isPartial
|
||||
? `Gebucht: ${ledgerLine.bookedAmount}/${ledgerLine.targetAmount} Stk.`
|
||||
: null;
|
||||
|
||||
if (!label) return null;
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<Chip
|
||||
size="small"
|
||||
clickable={isClickable}
|
||||
onClick={
|
||||
isClickable
|
||||
? (e) => {
|
||||
e.stopPropagation();
|
||||
onBookLine(ledgerLine);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
icon={
|
||||
isBooked ? (
|
||||
<CheckCircleOutlineOutlinedIcon sx={{ '&&': { fontSize: 13, color: 'inherit' } }} />
|
||||
) : (
|
||||
<Inventory2OutlinedIcon sx={{ '&&': { fontSize: 13, color: 'inherit' } }} />
|
||||
)
|
||||
}
|
||||
label={label}
|
||||
variant={isBooked ? 'outlined' : 'filled'}
|
||||
sx={{
|
||||
height: 22,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 700,
|
||||
cursor: isClickable ? 'pointer' : 'default',
|
||||
...(isBooked
|
||||
? {
|
||||
bgcolor: 'rgba(21, 128, 61, 0.08)',
|
||||
color: '#15803d',
|
||||
borderColor: '#15803d',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
'&:hover': isClickable ? { bgcolor: 'rgba(21, 128, 61, 0.16)' } : undefined,
|
||||
}
|
||||
: isPartial
|
||||
? {
|
||||
bgcolor: '#d97706',
|
||||
color: '#ffffff',
|
||||
border: 'none',
|
||||
'&:hover': isClickable ? { bgcolor: '#b45309' } : undefined,
|
||||
}
|
||||
: {
|
||||
bgcolor: 'primary.main',
|
||||
color: '#ffffff',
|
||||
border: 'none',
|
||||
'&:hover': isClickable ? { bgcolor: 'primary.dark' } : undefined,
|
||||
}),
|
||||
'& .MuiChip-label': {
|
||||
color: 'inherit',
|
||||
px: 0.75,
|
||||
},
|
||||
'& .MuiChip-icon': {
|
||||
color: 'inherit',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
renderField(column, colIdx, totalCols) {
|
||||
const { id, align, type } = column;
|
||||
@@ -365,10 +292,13 @@ class ManifestFormLine extends PureComponent {
|
||||
);
|
||||
|
||||
const isEditing = this.state.editingField === id;
|
||||
const isDisabled = saving || (id === 'sku' && Boolean(this.props.isOnShipment));
|
||||
const titleText = id === 'sku' && this.props.isOnShipment
|
||||
? 'Artikel ist bereits auf einem Lieferschein und kann nicht geändert werden'
|
||||
: undefined;
|
||||
const isFullyLedgered = this.isFullyLedgered();
|
||||
const isDisabled = saving || isFullyLedgered || (id === 'sku' && Boolean(this.props.isOnShipment));
|
||||
const titleText = isFullyLedgered
|
||||
? 'Position ist bereits vollständig im Ledger verbucht und kann nicht geändert werden'
|
||||
: id === 'sku' && this.props.isOnShipment
|
||||
? 'Artikel ist bereits auf einem Lieferschein und kann nicht geändert werden'
|
||||
: undefined;
|
||||
|
||||
if (isEditing) {
|
||||
return (
|
||||
@@ -456,9 +386,10 @@ class ManifestFormLine extends PureComponent {
|
||||
<Box
|
||||
tabIndex={isDisabled ? -1 : 0}
|
||||
role={isDisabled ? undefined : 'button'}
|
||||
onClick={() => this.handleStartEdit(id)}
|
||||
onFocus={() => this.handleStartEdit(id)}
|
||||
onClick={isDisabled ? undefined : () => this.handleStartEdit(id)}
|
||||
onFocus={isDisabled ? undefined : () => this.handleStartEdit(id)}
|
||||
onKeyDown={(e) => {
|
||||
if (isDisabled) return;
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
this.handleStartEdit(id);
|
||||
@@ -485,11 +416,16 @@ class ManifestFormLine extends PureComponent {
|
||||
? 'warning.main'
|
||||
: isError
|
||||
? 'error.main'
|
||||
: 'rgba(0, 0, 0, 0.23)',
|
||||
: isFullyLedgered
|
||||
? LOCKED_CELL_BORDER
|
||||
: 'rgba(0, 0, 0, 0.23)',
|
||||
cursor: isDisabled ? 'default' : 'text',
|
||||
touchAction: 'manipulation',
|
||||
userSelect: 'none',
|
||||
zIndex: 1,
|
||||
...(isFullyLedgered && !isFieldDirty && !isError && {
|
||||
bgcolor: LOCKED_CELL_BG,
|
||||
}),
|
||||
...(isFieldDirty && {
|
||||
bgcolor: 'rgba(237, 108, 2, 0.08)',
|
||||
zIndex: 2,
|
||||
@@ -943,13 +879,19 @@ class ManifestFormLine extends PureComponent {
|
||||
>
|
||||
{/* Unlink button at the front */}
|
||||
{this.props.onRemoveSupplierArticle && supplierArticle.id != null && (
|
||||
<Tooltip title="Lieferanten-Verknüpfung aufheben">
|
||||
<Tooltip
|
||||
title={
|
||||
this.isFullyLedgered()
|
||||
? 'Position ist bereits vollständig im Ledger verbucht und Verknüpfung kann nicht aufgehoben werden'
|
||||
: 'Lieferanten-Verknüpfung aufheben'
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="error"
|
||||
aria-label="Lieferanten-Verknüpfung entfernen"
|
||||
disabled={this.props.saving || this.props.supplierArticleBusy}
|
||||
disabled={this.props.saving || this.props.supplierArticleBusy || this.isFullyLedgered()}
|
||||
onClick={this.handleRemoveSupplierArticle}
|
||||
sx={{ p: 0.25, mr: -0.25 }}
|
||||
>
|
||||
@@ -1095,8 +1037,6 @@ class ManifestFormLine extends PureComponent {
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{this.renderLedgerChip()}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1109,6 +1049,7 @@ class ManifestFormLine extends PureComponent {
|
||||
size="small"
|
||||
variant="text"
|
||||
startIcon={<AddIcon sx={{ fontSize: 14 }} />}
|
||||
disabled={this.isFullyLedgered()}
|
||||
onClick={this.handleCreateArticle}
|
||||
sx={{
|
||||
fontSize: '0.7rem',
|
||||
@@ -1131,6 +1072,7 @@ class ManifestFormLine extends PureComponent {
|
||||
size="small"
|
||||
variant="text"
|
||||
startIcon={<SearchIcon sx={{ fontSize: 14 }} />}
|
||||
disabled={this.isFullyLedgered()}
|
||||
onClick={this.handleSearchArticle}
|
||||
sx={{
|
||||
fontSize: '0.7rem',
|
||||
@@ -1154,17 +1096,6 @@ class ManifestFormLine extends PureComponent {
|
||||
? `Kein Lieferanten-Artikel für SKU „${rawSku}“ gefunden.`
|
||||
: `Kein Artikel für SKU „${rawSku}“ gefunden.`}
|
||||
</Typography>
|
||||
{this.renderLedgerChip()}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.ledgerLine) {
|
||||
const chip = this.renderLedgerChip();
|
||||
if (!chip) return null;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, py: 0.25 }}>
|
||||
{chip}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1174,12 +1105,24 @@ class ManifestFormLine extends PureComponent {
|
||||
|
||||
render() {
|
||||
const { columns = LINE_COLUMNS, saving, ledgerLine } = this.props;
|
||||
const isFullyLedgered = this.isFullyLedgered();
|
||||
const secondLine = this.renderSecondLine();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{/* Row 1: Editable Line Inputs */}
|
||||
<TableRow hover sx={{ '& > td': { borderBottom: 'none', p: 0 } }}>
|
||||
<TableRow
|
||||
hover
|
||||
sx={{
|
||||
'& > td': { borderBottom: 'none', p: 0 },
|
||||
...(isFullyLedgered && {
|
||||
bgcolor: LOCKED_ROW_BG,
|
||||
'&:hover': {
|
||||
bgcolor: LOCKED_ROW_HOVER_BG,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{columns.map((col, colIdx) => (
|
||||
<TableCell
|
||||
key={col.id}
|
||||
@@ -1206,6 +1149,9 @@ class ManifestFormLine extends PureComponent {
|
||||
verticalAlign: 'middle',
|
||||
width: (this.props.onMove ? 80 : 36) + (this.props.kind === 'delivery' && Boolean(this.props.onBookLine) ? 36 : 0),
|
||||
whiteSpace: 'nowrap',
|
||||
...(isFullyLedgered && {
|
||||
bgcolor: LOCKED_ROW_BG,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
||||
@@ -1269,9 +1215,15 @@ class ManifestFormLine extends PureComponent {
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label="Zeile entfernen"
|
||||
disabled={saving || Boolean(this.props.isOnShipment)}
|
||||
disabled={saving || Boolean(this.props.isOnShipment) || isFullyLedgered}
|
||||
onClick={this.handleRemove}
|
||||
title={this.props.isOnShipment ? 'Artikel ist bereits auf einem Lieferschein und kann nicht entfernt werden' : 'Zeile entfernen'}
|
||||
title={
|
||||
isFullyLedgered
|
||||
? 'Position ist bereits vollständig im Ledger verbucht und kann nicht entfernt werden'
|
||||
: this.props.isOnShipment
|
||||
? 'Artikel ist bereits auf einem Lieferschein und kann nicht entfernt werden'
|
||||
: 'Zeile entfernen'
|
||||
}
|
||||
sx={{ p: 0.35 }}
|
||||
>
|
||||
<DeleteOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
@@ -1282,7 +1234,14 @@ class ManifestFormLine extends PureComponent {
|
||||
|
||||
{/* Row 2: Matched Supplier Article Details */}
|
||||
{secondLine && (
|
||||
<TableRow sx={{ '& > td': { pt: 0.25, pb: 0.75, borderBottom: 'none' } }}>
|
||||
<TableRow
|
||||
sx={{
|
||||
'& > td': { pt: 0.25, pb: 0.75, borderBottom: 'none' },
|
||||
...(isFullyLedgered && {
|
||||
bgcolor: LOCKED_ROW_BG,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<TableCell
|
||||
colSpan={columns.length + 1}
|
||||
sx={{
|
||||
|
||||
@@ -200,13 +200,29 @@ class ManifestLines extends PureComponent {
|
||||
})()}
|
||||
{(() => {
|
||||
const hasShippedLines = kind === 'order' && Array.isArray(shipments) && shipments.some((sh) => (sh.lines?.length || 0) > 0);
|
||||
const hasLedgeredLines = kind === 'delivery' && Array.isArray(lines) && lines.some((line) => {
|
||||
const ledgerLine = line?.id != null ? ledgerLinesById?.[line.id] : null;
|
||||
return Boolean(
|
||||
ledgerLine?.isBooked ||
|
||||
(ledgerLine && ledgerLine.targetAmount > 0 && ledgerLine.remainingAmount <= 0) ||
|
||||
(ledgerLine && ledgerLine.bookedAmount > 0) ||
|
||||
line?.isBooked ||
|
||||
line?.is_booked
|
||||
);
|
||||
});
|
||||
const disabled = saving || !lines || lines.length === 0 || hasShippedLines || hasLedgeredLines;
|
||||
const title = hasLedgeredLines
|
||||
? 'Lieferung enthält bereits im Ledger verbuchte Positionen'
|
||||
: hasShippedLines
|
||||
? 'Auftrag enthält bereits ausgelieferte Positionen'
|
||||
: 'Alle Zeilen entfernen';
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={<DeleteSweepOutlinedIcon />}
|
||||
disabled={saving || !lines || lines.length === 0 || hasShippedLines}
|
||||
disabled={disabled}
|
||||
onClick={onRemoveAllLines}
|
||||
title={hasShippedLines ? 'Auftrag enthält bereits ausgelieferte Positionen' : 'Alle Zeilen entfernen'}
|
||||
title={title}
|
||||
>
|
||||
Alle Zeilen entfernen
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user