Disable menu/popover animations; update manifest handling

This commit is contained in:
seb
2026-08-08 10:09:19 +02:00
parent 87acdd929e
commit 2cbe5dabb2
6 changed files with 109 additions and 17 deletions

View File

@@ -557,7 +557,9 @@ export const supplier = {
/**
* Company-scoped goods receipts (manifest type = delivery).
* DeliverySummary = { id, number, type: 'delivery', supplierId, date }
* DeliverySummary = { id, number, externalNumber, type: 'delivery', supplierId, date }
* - number: server-allocated, read-only manifest number (from nextNumber('delivery')).
* - externalNumber: user-entered reference (nullable).
* DeliveryLine = { id, articleId, sku, amount, net, transactedAmount, rebate, tax, comment }
* Delivery = DeliverySummary & { lines: DeliveryLine[] }
* DeliveryLineInput = { sku, amount, net, tax, rebate?, transactedAmount?, comment? }
@@ -575,7 +577,9 @@ export const delivery = {
/**
* POST /api/delivery
* Body: { number, date, supplierId?, lines: DeliveryLineInput[] }
* Body: { externalNumber?, date, supplierId?, lines: DeliveryLineInput[] }
* number is NOT sent — the server allocates it automatically. The
* caller-supplied reference goes in externalNumber (nullable).
* Line requires sku, amount, net, tax. rebate→0, transactedAmount→amount.
* → 201 { delivery: Delivery }
*/
@@ -585,7 +589,9 @@ export const delivery = {
/**
* PATCH /api/delivery/:id
* Body: partial { number?, date?, supplierId?, lines? }.
* Body: partial { externalNumber?, date?, supplierId?, lines? }.
* number is read-only and cannot be edited. externalNumber is editable and
* can be cleared by sending null or an empty string.
* If lines present, replaces all lines; if omitted, lines unchanged.
* → 200 { delivery: Delivery }
*/

View File

@@ -35,6 +35,7 @@ class ManifestForm extends PureComponent {
date: '',
partyId: null,
number: '',
externalNumber: '',
lines: [emptyLine()],
saving: false,
};
@@ -43,6 +44,8 @@ class ManifestForm extends PureComponent {
this.handlePartyChange = this.handlePartyChange.bind(this);
this.handleDateChange = this.handleDateChange.bind(this);
this.handleNumberChange = this.handleNumberChange.bind(this);
this.handleExternalNumberChange =
this.handleExternalNumberChange.bind(this);
this.handleLineChange = this.handleLineChange.bind(this);
this.handleAddLine = this.handleAddLine.bind(this);
this.handleRemoveLine = this.handleRemoveLine.bind(this);
@@ -122,6 +125,8 @@ class ManifestForm extends PureComponent {
date: dateInputValue(doc.date),
partyId: doc[cfg.partyField],
number: doc.number ?? '',
externalNumber:
doc.externalNumber != null ? String(doc.externalNumber) : '',
lines,
});
} catch (err) {
@@ -157,6 +162,10 @@ class ManifestForm extends PureComponent {
this.setState({ number });
}
handleExternalNumberChange(externalNumber) {
this.setState({ externalNumber });
}
handleLineChange(key, field, value) {
this.setState((prev) => ({
lines: prev.lines.map((line) =>
@@ -188,11 +197,16 @@ class ManifestForm extends PureComponent {
body = buildManifestBody(
{
number: this.state.number,
externalNumber: this.state.externalNumber,
date: this.state.date,
partyId: this.state.partyId,
lines: this.state.lines,
},
{ partyField: cfg.partyField, requireAtLeastOne },
{
partyField: cfg.partyField,
requireAtLeastOne,
externalNumber: cfg.externalNumber,
},
);
} catch (err) {
notifyError(err);
@@ -225,6 +239,8 @@ class ManifestForm extends PureComponent {
date: dateInputValue(doc.date),
partyId: doc[cfg.partyField],
number: doc.number ?? '',
externalNumber:
doc.externalNumber != null ? String(doc.externalNumber) : '',
lines:
apiLines.length > 0 ? apiLines.map(lineFromApi) : [emptyLine()],
saving: false,
@@ -280,6 +296,7 @@ class ManifestForm extends PureComponent {
date,
partyId,
number,
externalNumber,
saving,
} = this.state;
@@ -307,10 +324,12 @@ class ManifestForm extends PureComponent {
partyId={partyId}
date={date}
number={number}
externalNumber={externalNumber}
saving={saving}
onPartyChange={this.handlePartyChange}
onDateChange={this.handleDateChange}
onNumberChange={this.handleNumberChange}
onExternalNumberChange={this.handleExternalNumberChange}
/>
{status === 'ready' ? this.renderArticles() : null}
</PanelTileGrid>

View File

@@ -4,7 +4,7 @@ import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import SingleLineTextEdit from '../SingleLineTextEdit';
import { PanelTile } from '../panels';
import { partyDisplayName } from './manifestConfig';
import { manifestConfig, partyDisplayName } from './manifestConfig';
class ManifestHeader extends PureComponent {
constructor(props) {
@@ -27,12 +27,20 @@ class ManifestHeader extends PureComponent {
partyId,
date,
number,
externalNumber,
saving,
onDateChange,
onNumberChange,
onExternalNumberChange,
} = this.props;
const selected = parties.find((row) => row.id === partyId) ?? null;
// Delivery: number is server-allocated/read-only; the caller reference
// lives in the editable externalNumber field.
const cfg = manifestConfig(kind);
const useExternalNumber = Boolean(cfg.externalNumber);
const selected =
parties.find((row) => row.id === partyId) ?? null;
return (
<PanelTile wide={3} minHeight={160} color={headerColor} title="Kopfdaten">
@@ -80,10 +88,20 @@ class ManifestHeader extends PureComponent {
size="small"
label="Nummer"
value={number}
disabled={saving}
disabled={saving || useExternalNumber}
onChange={onNumberChange}
sx={{ minWidth: 180, flex: 1 }}
/>
{useExternalNumber ? (
<SingleLineTextEdit
size="small"
label="Referenz"
value={externalNumber ?? ''}
disabled={saving}
onChange={onExternalNumberChange}
sx={{ minWidth: 180, flex: 1 }}
/>
) : null}
</Stack>
</PanelTile>
);

View File

@@ -24,6 +24,7 @@ import {
formatDate,
loadParties,
manifestConfig,
numberDisplay,
partyDisplayName,
sortDocs,
} from './manifestConfig';
@@ -169,6 +170,9 @@ class ManifestList extends PureComponent {
<TableHead>
<TableRow>
<TableCell>Nummer</TableCell>
{cfg.externalNumber ? (
<TableCell>Referenz</TableCell>
) : null}
<TableCell>{cfg.dateLabel}</TableCell>
<TableCell>{cfg.partyLabel}</TableCell>
<TableCell padding="checkbox" />
@@ -183,6 +187,11 @@ class ManifestList extends PureComponent {
onClick={() => this.handleEdit(row)}
>
<TableCell>{row.number}</TableCell>
{cfg.externalNumber ? (
<TableCell>
{numberDisplay(row.externalNumber)}
</TableCell>
) : null}
<TableCell>{formatDate(row.date)}</TableCell>
<TableCell>{this.partyName(row[partyField])}</TableCell>
<TableCell

View File

@@ -44,6 +44,16 @@ export function formatDate(iso) {
return d.toLocaleDateString('de-DE');
}
/**
* Delivery documents have a server-allocated, read-only `number` plus an
* optional user-entered `externalNumber`. Returns the display value for a
* field, defaulting to a dash when empty.
*/
export function numberDisplay(value) {
if (value == null || String(value).trim() === '') return '–';
return String(value);
}
export function dateInputValue(iso) {
if (!iso) return '';
const d = new Date(iso);
@@ -105,20 +115,35 @@ export function buildLines(lines, { requireAtLeastOne }) {
}
/**
* @param {{ number: string, date: string, partyId: number|null, lines: object[] }} form
* @param {{ partyField: 'supplierId'|'entityId', requireAtLeastOne: boolean }} opts
* @param {{ number: string, externalNumber?: string, date: string, partyId: number|null, lines: object[] }} form
* @param {{ partyField: 'supplierId'|'entityId', requireAtLeastOne: boolean, externalNumber?: boolean }} opts
*/
export function buildManifestBody(form, { partyField, requireAtLeastOne }) {
const number = String(form.number ?? '').trim();
export function buildManifestBody(
form,
{ partyField, requireAtLeastOne, externalNumber: useExternalNumber },
) {
const date = form.date;
if (!number) throw new Error('Nummer fehlt.');
if (!date) throw new Error('Datum fehlt.');
const body = {
number,
date,
lines: buildLines(form.lines, { requireAtLeastOne }),
};
let body;
if (useExternalNumber) {
// Delivery: number is server-allocated/read-only. The caller-supplied
// reference goes in externalNumber (nullable; empty string → null).
const externalNumber = String(form.externalNumber ?? '').trim();
body = {
externalNumber: externalNumber || null,
date,
lines: buildLines(form.lines, { requireAtLeastOne }),
};
} else {
const number = String(form.number ?? '').trim();
if (!number) throw new Error('Nummer fehlt.');
body = {
number,
date,
lines: buildLines(form.lines, { requireAtLeastOne }),
};
}
if (form.partyId != null) body[partyField] = form.partyId;
else if (!requireAtLeastOne) body[partyField] = null;
return body;
@@ -192,6 +217,9 @@ export function manifestConfig(kind) {
listPromise: (ctx) => ctx.deliveries ?? ctx.deliveryPromise,
partiesByIdFromList: (parties) =>
Object.fromEntries(parties.map((row) => [row.id, row])),
// Delivery uses a server-allocated read-only number plus an optional
// caller-supplied external reference.
externalNumber: true,
};
}
if (kind === 'quote') {

View File

@@ -25,5 +25,17 @@ export function createDefaultTheme() {
},
},
nav,
components: {
MuiMenu: {
defaultProps: {
transitionDuration: 0,
},
},
MuiPopover: {
defaultProps: {
transitionDuration: 0,
},
},
},
});
}