This commit is contained in:
seb
2026-10-17 18:09:55 +02:00
parent c0ae79ba6b
commit 582bf04476
2 changed files with 99 additions and 124 deletions

View File

@@ -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={{

View File

@@ -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>