u
This commit is contained in:
@@ -7,7 +7,7 @@ description: >-
|
|||||||
|
|
||||||
# Dash API Routes Overview (All)
|
# Dash API Routes Overview (All)
|
||||||
|
|
||||||
Overview of all 233 API endpoints across all modules.
|
Overview of all 235 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -347,6 +347,12 @@ _none_
|
|||||||
#### Create company — `POST /company`
|
#### Create company — `POST /company`
|
||||||
- Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
|
- Params: `body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
|
||||||
- Returns: `201 { company: T23, user: T45 }`
|
- Returns: `201 { company: T23, user: T45 }`
|
||||||
|
#### Export company catalog as zip archive (SSE) — `GET /company/export`
|
||||||
|
- Params: `query: stream?: str`
|
||||||
|
- Returns: `200 SSE`
|
||||||
|
#### Export company catalog as zip archive (SSE) — `POST /company/export`
|
||||||
|
_none_
|
||||||
|
- Returns: `200 SSE`
|
||||||
#### Get company history — `GET /company/history`
|
#### Get company history — `GET /company/history`
|
||||||
- Params: `query: limit?: int`
|
- Params: `query: limit?: int`
|
||||||
- Returns: `200 { history: { id: int, date: str, text: str, companyuserId?: int|null }[], truncated: bool }`
|
- Returns: `200 { history: { id: int, date: str, text: str, companyuserId?: int|null }[], truncated: bool }`
|
||||||
|
|||||||
@@ -233,3 +233,307 @@ export function getManifestDraftAutomat(kind) {
|
|||||||
if (kind === 'order') return orderDraftAutomat;
|
if (kind === 'order') return orderDraftAutomat;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes detailed diff between current manifest draft/edit state and baseline snapshot.
|
||||||
|
*
|
||||||
|
* @param {object} current
|
||||||
|
* @param {object|null} baseline
|
||||||
|
* @returns {{ isDirty: boolean, fields: { date: boolean, partyId: boolean, externalNumber: boolean, number: boolean, lines: boolean, attachments: boolean }, dirtyLineKeys: string[], summary: string[] }}
|
||||||
|
*/
|
||||||
|
export function getManifestDirtyDetails(current, baseline) {
|
||||||
|
if (!baseline) {
|
||||||
|
return {
|
||||||
|
isDirty: false,
|
||||||
|
fields: {
|
||||||
|
date: false,
|
||||||
|
partyId: false,
|
||||||
|
externalNumber: false,
|
||||||
|
number: false,
|
||||||
|
lines: false,
|
||||||
|
attachments: false,
|
||||||
|
},
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
summary: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
date: (current.date || '') !== (baseline.date || ''),
|
||||||
|
partyId:
|
||||||
|
(current.partyId != null && current.partyId !== '' ? Number(current.partyId) : null) !==
|
||||||
|
(baseline.partyId != null && baseline.partyId !== '' ? Number(baseline.partyId) : null),
|
||||||
|
externalNumber:
|
||||||
|
String(current.externalNumber || '').trim() !== String(baseline.externalNumber || '').trim(),
|
||||||
|
number: String(current.number || '').trim() !== String(baseline.number || '').trim(),
|
||||||
|
lines: false,
|
||||||
|
attachments: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dirtyLineKeys = [];
|
||||||
|
const curLines = current.lines || [];
|
||||||
|
const baseLines = baseline.lines || [];
|
||||||
|
|
||||||
|
const baseLinesByKey = new Map();
|
||||||
|
for (let i = 0; i < baseLines.length; i++) {
|
||||||
|
const bl = baseLines[i];
|
||||||
|
if (bl.key) baseLinesByKey.set(bl.key, bl);
|
||||||
|
if (bl.id != null) baseLinesByKey.set(`id-${bl.id}`, bl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curLines.length !== baseLines.length) {
|
||||||
|
fields.lines = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < curLines.length; i++) {
|
||||||
|
const cl = curLines[i];
|
||||||
|
const key = cl.key ?? `idx-${i}`;
|
||||||
|
const bl =
|
||||||
|
(cl.key && baseLinesByKey.get(cl.key)) ||
|
||||||
|
(cl.id != null && baseLinesByKey.get(`id-${cl.id}`)) ||
|
||||||
|
baseLines[i];
|
||||||
|
|
||||||
|
if (!bl) {
|
||||||
|
if (cl.sku || cl.name || (cl.amount && cl.amount !== '1') || cl.net || cl.gross) {
|
||||||
|
dirtyLineKeys.push(key);
|
||||||
|
fields.lines = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const lineChanged =
|
||||||
|
String(cl.sku ?? '').trim() !== String(bl.sku ?? '').trim() ||
|
||||||
|
String(cl.name ?? '').trim() !== String(bl.name ?? '').trim() ||
|
||||||
|
String(cl.amount ?? '').trim() !== String(bl.amount ?? '').trim() ||
|
||||||
|
String(cl.net ?? cl.netprice ?? '').trim() !== String(bl.net ?? bl.netprice ?? '').trim() ||
|
||||||
|
String(cl.gross ?? '').trim() !== String(bl.gross ?? '').trim() ||
|
||||||
|
String(cl.rebate ?? '').trim() !== String(bl.rebate ?? '').trim() ||
|
||||||
|
String(cl.tax ?? cl.taxrate ?? '').trim() !== String(bl.tax ?? bl.taxrate ?? '').trim() ||
|
||||||
|
String(cl.pu ?? '').trim() !== String(bl.pu ?? '').trim() ||
|
||||||
|
String(cl.unit ?? '').trim() !== String(bl.unit ?? '').trim();
|
||||||
|
|
||||||
|
if (lineChanged) {
|
||||||
|
dirtyLineKeys.push(key);
|
||||||
|
fields.lines = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check attachments
|
||||||
|
const curAtts = current.attachments || [];
|
||||||
|
const baseAtts = baseline.attachments || [];
|
||||||
|
if (curAtts.length !== baseAtts.length) {
|
||||||
|
fields.attachments = true;
|
||||||
|
} else {
|
||||||
|
for (const a of curAtts) {
|
||||||
|
if (!a.isPersisted || a.isDirty) {
|
||||||
|
fields.attachments = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const summary = [];
|
||||||
|
if (fields.date) summary.push('Datum');
|
||||||
|
if (fields.partyId) summary.push(current.kind === 'delivery' ? 'Lieferant' : 'Kunde');
|
||||||
|
if (fields.externalNumber) summary.push('Referenz');
|
||||||
|
if (fields.number) summary.push('Nummer');
|
||||||
|
if (fields.lines) {
|
||||||
|
const count = dirtyLineKeys.length;
|
||||||
|
summary.push(count > 0 ? `${count} Artikelzeile${count === 1 ? '' : 'n'}` : 'Artikelzeilen');
|
||||||
|
}
|
||||||
|
if (fields.attachments) summary.push('Dateien');
|
||||||
|
|
||||||
|
const isDirty = summary.length > 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDirty,
|
||||||
|
fields,
|
||||||
|
dirtyLineKeys,
|
||||||
|
summary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares current manifest draft/edit state against a baseline snapshot.
|
||||||
|
*
|
||||||
|
* @param {object} current
|
||||||
|
* @param {object|null} baseline
|
||||||
|
* @returns {boolean} True if any field or line has changed from baseline
|
||||||
|
*/
|
||||||
|
export function checkManifestDirty(current, baseline) {
|
||||||
|
return getManifestDirtyDetails(current, baseline).isDirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an Automat managing the single last-edited manifest for that kind.
|
||||||
|
*
|
||||||
|
* @param {{ kind: 'quote'|'order', name: string }} options
|
||||||
|
* @returns {Automat}
|
||||||
|
*/
|
||||||
|
export function createManifestEditAutomat({ kind, name }) {
|
||||||
|
let automat;
|
||||||
|
|
||||||
|
const emptyFields = {
|
||||||
|
date: false,
|
||||||
|
partyId: false,
|
||||||
|
externalNumber: false,
|
||||||
|
number: false,
|
||||||
|
lines: false,
|
||||||
|
attachments: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
loadDoc(id, doc) {
|
||||||
|
const date = dateInputValue(doc.date);
|
||||||
|
const partyId = doc.partyId ?? null;
|
||||||
|
const number = doc.number ?? '';
|
||||||
|
const externalNumber = doc.externalNumber != null ? String(doc.externalNumber) : '';
|
||||||
|
const lines = Array.isArray(doc.lines) && doc.lines.length > 0 ? doc.lines : [emptyLine()];
|
||||||
|
const attachments = Array.isArray(doc.attachments) ? doc.attachments : [];
|
||||||
|
|
||||||
|
const baseline = {
|
||||||
|
date,
|
||||||
|
partyId,
|
||||||
|
number,
|
||||||
|
externalNumber,
|
||||||
|
lines: lines.map((l) => ({ ...l })),
|
||||||
|
attachments: [...attachments],
|
||||||
|
};
|
||||||
|
|
||||||
|
automat.setState({
|
||||||
|
kind,
|
||||||
|
mode: 'edit',
|
||||||
|
id,
|
||||||
|
date,
|
||||||
|
partyId,
|
||||||
|
number,
|
||||||
|
externalNumber,
|
||||||
|
lines,
|
||||||
|
attachments,
|
||||||
|
baseline,
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: { ...emptyFields },
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
dirtySummary: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
update(updates) {
|
||||||
|
const next = { ...automat.state, ...updates };
|
||||||
|
const dirtyInfo = getManifestDirtyDetails(next, automat.state.baseline);
|
||||||
|
automat.setState({
|
||||||
|
...updates,
|
||||||
|
isDirty: dirtyInfo.isDirty,
|
||||||
|
dirtyFields: dirtyInfo.fields,
|
||||||
|
dirtyLineKeys: dirtyInfo.dirtyLineKeys,
|
||||||
|
dirtySummary: dirtyInfo.summary,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
restoreBaseline() {
|
||||||
|
if (!automat.state.baseline) return;
|
||||||
|
const base = automat.state.baseline;
|
||||||
|
automat.setState({
|
||||||
|
date: base.date,
|
||||||
|
partyId: base.partyId,
|
||||||
|
number: base.number,
|
||||||
|
externalNumber: base.externalNumber,
|
||||||
|
lines: base.lines.map((l) => ({ ...l })),
|
||||||
|
attachments: [...base.attachments],
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: { ...emptyFields },
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
dirtySummary: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
commitSaved(savedDoc) {
|
||||||
|
const date = dateInputValue(savedDoc.date);
|
||||||
|
const partyId = savedDoc.partyId ?? null;
|
||||||
|
const number = savedDoc.number ?? automat.state.number;
|
||||||
|
const externalNumber = savedDoc.externalNumber != null ? String(savedDoc.externalNumber) : '';
|
||||||
|
const lines = Array.isArray(savedDoc.lines) && savedDoc.lines.length > 0 ? savedDoc.lines : [emptyLine()];
|
||||||
|
const attachments = Array.isArray(savedDoc.attachments) ? savedDoc.attachments : automat.state.attachments;
|
||||||
|
|
||||||
|
const baseline = {
|
||||||
|
date,
|
||||||
|
partyId,
|
||||||
|
number,
|
||||||
|
externalNumber,
|
||||||
|
lines: lines.map((l) => ({ ...l })),
|
||||||
|
attachments: [...attachments],
|
||||||
|
};
|
||||||
|
|
||||||
|
automat.setState({
|
||||||
|
date,
|
||||||
|
partyId,
|
||||||
|
number,
|
||||||
|
externalNumber,
|
||||||
|
lines,
|
||||||
|
attachments,
|
||||||
|
baseline,
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: { ...emptyFields },
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
dirtySummary: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
automat.setState({
|
||||||
|
kind,
|
||||||
|
mode: 'edit',
|
||||||
|
id: null,
|
||||||
|
date: '',
|
||||||
|
partyId: null,
|
||||||
|
number: '',
|
||||||
|
externalNumber: '',
|
||||||
|
lines: [emptyLine()],
|
||||||
|
attachments: [],
|
||||||
|
baseline: null,
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: { ...emptyFields },
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
dirtySummary: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
automat = new Automat(
|
||||||
|
{
|
||||||
|
kind,
|
||||||
|
mode: 'edit',
|
||||||
|
id: null,
|
||||||
|
date: '',
|
||||||
|
partyId: null,
|
||||||
|
number: '',
|
||||||
|
externalNumber: '',
|
||||||
|
lines: [emptyLine()],
|
||||||
|
attachments: [],
|
||||||
|
baseline: null,
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: { ...emptyFields },
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
dirtySummary: [],
|
||||||
|
},
|
||||||
|
actions,
|
||||||
|
{ name, persist: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
return automat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shared singleton automat for the single last edited quote. */
|
||||||
|
export const quoteEditAutomat = createManifestEditAutomat({
|
||||||
|
kind: 'quote',
|
||||||
|
name: 'quote-edit',
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the edit automat for a given manifest kind.
|
||||||
|
* @param {'quote'|'order'} kind
|
||||||
|
* @returns {Automat|null}
|
||||||
|
*/
|
||||||
|
export function getManifestEditAutomat(kind) {
|
||||||
|
if (kind === 'quote') return quoteEditAutomat;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ import {
|
|||||||
parseNum,
|
parseNum,
|
||||||
} from './manifestConfig';
|
} from './manifestConfig';
|
||||||
import { ensureDocumentNumber, getNextDocumentNumber } from '../../utils/documentNumber';
|
import { ensureDocumentNumber, getNextDocumentNumber } from '../../utils/documentNumber';
|
||||||
import { getManifestDraftAutomat } from '../../automats/manifestDraftAutomat';
|
import {
|
||||||
|
getManifestDraftAutomat,
|
||||||
|
getManifestEditAutomat,
|
||||||
|
} from '../../automats/manifestDraftAutomat';
|
||||||
|
|
||||||
class ManifestForm extends PureComponent {
|
class ManifestForm extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -44,19 +47,29 @@ class ManifestForm extends PureComponent {
|
|||||||
: entityAutomat.state.status === 'ready';
|
: entityAutomat.state.status === 'ready';
|
||||||
|
|
||||||
this.draftAutomat = props.mode === 'create' ? getManifestDraftAutomat(props.kind) : null;
|
this.draftAutomat = props.mode === 'create' ? getManifestDraftAutomat(props.kind) : null;
|
||||||
|
this.editAutomat = props.mode === 'edit' ? getManifestEditAutomat(props.kind) : null;
|
||||||
|
|
||||||
const initialDraft = this.draftAutomat?.state;
|
const initialDraft = this.draftAutomat?.state;
|
||||||
|
const initialEdit = (this.editAutomat && String(this.editAutomat.state?.id) === String(props.docId))
|
||||||
|
? this.editAutomat.state
|
||||||
|
: null;
|
||||||
|
const initialDoc = initialDraft || initialEdit;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
status: props.mode === 'edit' ? 'loading' : (partiesReady ? 'ready' : 'loadingParties'),
|
status: props.mode === 'edit' && !initialEdit ? 'loading' : (partiesReady ? 'ready' : 'loadingParties'),
|
||||||
error: null,
|
error: null,
|
||||||
parties: initialParties,
|
parties: initialParties,
|
||||||
partiesLoading: !partiesReady,
|
partiesLoading: !partiesReady,
|
||||||
date: initialDraft?.date || '',
|
date: initialDoc?.date || '',
|
||||||
partyId: initialDraft?.partyId ?? null,
|
partyId: initialDoc?.partyId ?? null,
|
||||||
number: initialDraft?.number || '',
|
number: initialDoc?.number || '',
|
||||||
externalNumber: initialDraft?.externalNumber || '',
|
externalNumber: initialDoc?.externalNumber || '',
|
||||||
lines: initialDraft?.lines?.length > 0 ? initialDraft.lines : [emptyLine()],
|
lines: initialDoc?.lines?.length > 0 ? initialDoc.lines : [emptyLine()],
|
||||||
attachments: initialDraft?.attachments || [],
|
attachments: initialDoc?.attachments || [],
|
||||||
|
isDirty: Boolean(initialEdit?.isDirty),
|
||||||
|
dirtyFields: initialEdit?.dirtyFields || null,
|
||||||
|
dirtyLineKeys: initialEdit?.dirtyLineKeys || [],
|
||||||
|
dirtySummary: initialEdit?.dirtySummary || [],
|
||||||
saving: false,
|
saving: false,
|
||||||
pdfUploading: false,
|
pdfUploading: false,
|
||||||
pdfDialogOpen: false,
|
pdfDialogOpen: false,
|
||||||
@@ -67,6 +80,7 @@ class ManifestForm extends PureComponent {
|
|||||||
};
|
};
|
||||||
this._cancelled = false;
|
this._cancelled = false;
|
||||||
this.handleRetry = this.handleRetry.bind(this);
|
this.handleRetry = this.handleRetry.bind(this);
|
||||||
|
this.handleRestore = this.handleRestore.bind(this);
|
||||||
this.handlePartyChange = this.handlePartyChange.bind(this);
|
this.handlePartyChange = this.handlePartyChange.bind(this);
|
||||||
this.handleDateChange = this.handleDateChange.bind(this);
|
this.handleDateChange = this.handleDateChange.bind(this);
|
||||||
this.handleNumberChange = this.handleNumberChange.bind(this);
|
this.handleNumberChange = this.handleNumberChange.bind(this);
|
||||||
@@ -145,6 +159,25 @@ class ManifestForm extends PureComponent {
|
|||||||
attachments: state.attachments || [],
|
attachments: state.attachments || [],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.editAutomat) {
|
||||||
|
this.unsubEdit = this.editAutomat.subscribe(this, (state) => {
|
||||||
|
if (String(state.id) === String(this.props.docId)) {
|
||||||
|
return {
|
||||||
|
date: state.date || '',
|
||||||
|
partyId: state.partyId,
|
||||||
|
externalNumber: state.externalNumber || '',
|
||||||
|
lines: state.lines?.length > 0 ? state.lines : [emptyLine()],
|
||||||
|
attachments: state.attachments || [],
|
||||||
|
isDirty: Boolean(state.isDirty),
|
||||||
|
dirtyFields: state.dirtyFields,
|
||||||
|
dirtyLineKeys: state.dirtyLineKeys,
|
||||||
|
dirtySummary: state.dirtySummary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
@@ -172,11 +205,42 @@ class ManifestForm extends PureComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.props.mode === 'edit' &&
|
||||||
|
this.editAutomat &&
|
||||||
|
String(this.editAutomat.state.id) === String(this.props.docId)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
prevState.date !== this.state.date ||
|
||||||
|
prevState.partyId !== this.state.partyId ||
|
||||||
|
prevState.externalNumber !== this.state.externalNumber ||
|
||||||
|
prevState.lines !== this.state.lines ||
|
||||||
|
prevState.attachments !== this.state.attachments
|
||||||
|
) {
|
||||||
|
this.editAutomat.actions.update({
|
||||||
|
date: this.state.date,
|
||||||
|
partyId: this.state.partyId,
|
||||||
|
externalNumber: this.state.externalNumber,
|
||||||
|
lines: this.state.lines,
|
||||||
|
attachments: this.state.attachments,
|
||||||
|
});
|
||||||
|
const { isDirty, dirtyFields, dirtyLineKeys, dirtySummary } = this.editAutomat.state;
|
||||||
|
if (
|
||||||
|
this.state.isDirty !== isDirty ||
|
||||||
|
this.state.dirtyFields !== dirtyFields ||
|
||||||
|
this.state.dirtySummary !== dirtySummary
|
||||||
|
) {
|
||||||
|
this.setState({ isDirty, dirtyFields, dirtyLineKeys, dirtySummary });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._cancelled = true;
|
this._cancelled = true;
|
||||||
this.unsubDraft?.();
|
this.unsubDraft?.();
|
||||||
|
this.unsubEdit?.();
|
||||||
this.unsubParties?.();
|
this.unsubParties?.();
|
||||||
this.unsubCompany?.();
|
this.unsubCompany?.();
|
||||||
if (this._parseAbortController) {
|
if (this._parseAbortController) {
|
||||||
@@ -221,6 +285,29 @@ class ManifestForm extends PureComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If editAutomat already holds this exact document in memory, preserve in-progress edits
|
||||||
|
if (
|
||||||
|
this.editAutomat &&
|
||||||
|
String(this.editAutomat.state.id) === String(docId) &&
|
||||||
|
this.editAutomat.state.baseline != null
|
||||||
|
) {
|
||||||
|
void this.loadPartiesOnly();
|
||||||
|
this.setState({
|
||||||
|
status: 'ready',
|
||||||
|
isDirty: Boolean(this.editAutomat.state.isDirty),
|
||||||
|
dirtyFields: this.editAutomat.state.dirtyFields,
|
||||||
|
dirtyLineKeys: this.editAutomat.state.dirtyLineKeys,
|
||||||
|
dirtySummary: this.editAutomat.state.dirtySummary,
|
||||||
|
date: this.editAutomat.state.date,
|
||||||
|
partyId: this.editAutomat.state.partyId,
|
||||||
|
number: this.editAutomat.state.number,
|
||||||
|
externalNumber: this.editAutomat.state.externalNumber,
|
||||||
|
lines: this.editAutomat.state.lines,
|
||||||
|
attachments: this.editAutomat.state.attachments,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ status: 'loading', error: null, partiesLoading: true });
|
this.setState({ status: 'loading', error: null, partiesLoading: true });
|
||||||
try {
|
try {
|
||||||
const [docRes, parties] = await Promise.all([
|
const [docRes, parties] = await Promise.all([
|
||||||
@@ -240,10 +327,7 @@ class ManifestForm extends PureComponent {
|
|||||||
const apiFiles = doc.files ?? [];
|
const apiFiles = doc.files ?? [];
|
||||||
const attachments = await restoreAttachmentsFromApi(apiFiles);
|
const attachments = await restoreAttachmentsFromApi(apiFiles);
|
||||||
|
|
||||||
this.setState({
|
const loadedDoc = {
|
||||||
status: 'ready',
|
|
||||||
parties,
|
|
||||||
partiesLoading: false,
|
|
||||||
date: dateInputValue(doc.date),
|
date: dateInputValue(doc.date),
|
||||||
partyId: doc[cfg.partyField],
|
partyId: doc[cfg.partyField],
|
||||||
number: doc.number ?? '',
|
number: doc.number ?? '',
|
||||||
@@ -251,6 +335,21 @@ class ManifestForm extends PureComponent {
|
|||||||
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
||||||
lines,
|
lines,
|
||||||
attachments,
|
attachments,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.editAutomat) {
|
||||||
|
this.editAutomat.actions.loadDoc(docId, loadedDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
status: 'ready',
|
||||||
|
parties,
|
||||||
|
partiesLoading: false,
|
||||||
|
...loadedDoc,
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: null,
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
dirtySummary: [],
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (this._cancelled) return;
|
if (this._cancelled) return;
|
||||||
@@ -267,6 +366,25 @@ class ManifestForm extends PureComponent {
|
|||||||
else void this.loadPartiesOnly();
|
else void this.loadPartiesOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRestore() {
|
||||||
|
if (!this.editAutomat) return;
|
||||||
|
this.editAutomat.actions.restoreBaseline();
|
||||||
|
const state = this.editAutomat.state;
|
||||||
|
this.setState({
|
||||||
|
date: state.date || '',
|
||||||
|
partyId: state.partyId,
|
||||||
|
number: state.number || '',
|
||||||
|
externalNumber: state.externalNumber || '',
|
||||||
|
lines: state.lines?.length > 0 ? state.lines : [emptyLine()],
|
||||||
|
attachments: state.attachments || [],
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: state.dirtyFields,
|
||||||
|
dirtyLineKeys: state.dirtyLineKeys,
|
||||||
|
dirtySummary: state.dirtySummary,
|
||||||
|
});
|
||||||
|
enqueueSnackbar('Änderungen verworfen und ursprünglicher Stand wiederhergestellt.', { variant: 'info' });
|
||||||
|
}
|
||||||
|
|
||||||
handlePartyChange(partyId) {
|
handlePartyChange(partyId) {
|
||||||
const { mode } = this.props;
|
const { mode } = this.props;
|
||||||
const { requirePartyBeforeLines } = this.cfg;
|
const { requirePartyBeforeLines } = this.cfg;
|
||||||
@@ -736,7 +854,7 @@ class ManifestForm extends PureComponent {
|
|||||||
if (this._cancelled) return;
|
if (this._cancelled) return;
|
||||||
const doc = data[cfg.docField];
|
const doc = data[cfg.docField];
|
||||||
const apiLines = requireArray(doc.lines, `${cfg.docField}.lines`);
|
const apiLines = requireArray(doc.lines, `${cfg.docField}.lines`);
|
||||||
this.setState({
|
const savedDoc = {
|
||||||
date: dateInputValue(doc.date),
|
date: dateInputValue(doc.date),
|
||||||
partyId: doc[cfg.partyField],
|
partyId: doc[cfg.partyField],
|
||||||
number: doc.number ?? '',
|
number: doc.number ?? '',
|
||||||
@@ -744,6 +862,19 @@ class ManifestForm extends PureComponent {
|
|||||||
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
||||||
lines:
|
lines:
|
||||||
apiLines.length > 0 ? apiLines.map(lineFromApi) : [emptyLine()],
|
apiLines.length > 0 ? apiLines.map(lineFromApi) : [emptyLine()],
|
||||||
|
attachments: this.state.attachments,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.editAutomat && String(this.editAutomat.state.id) === String(docId)) {
|
||||||
|
this.editAutomat.actions.commitSaved(savedDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
...savedDoc,
|
||||||
|
isDirty: false,
|
||||||
|
dirtyFields: null,
|
||||||
|
dirtyLineKeys: [],
|
||||||
|
dirtySummary: [],
|
||||||
saving: false,
|
saving: false,
|
||||||
});
|
});
|
||||||
enqueueSnackbar(cfg.savedMsg, { variant: 'success' });
|
enqueueSnackbar(cfg.savedMsg, { variant: 'success' });
|
||||||
@@ -756,7 +887,7 @@ class ManifestForm extends PureComponent {
|
|||||||
|
|
||||||
renderArticles() {
|
renderArticles() {
|
||||||
const cfg = this.cfg;
|
const cfg = this.cfg;
|
||||||
const { lines, saving } = this.state;
|
const { lines, saving, dirtyFields, dirtyLineKeys } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManifestLines
|
<ManifestLines
|
||||||
@@ -766,6 +897,8 @@ class ManifestForm extends PureComponent {
|
|||||||
saving={saving}
|
saving={saving}
|
||||||
wide={4}
|
wide={4}
|
||||||
linesColor={cfg.linesColor}
|
linesColor={cfg.linesColor}
|
||||||
|
isLinesDirty={Boolean(dirtyFields?.lines)}
|
||||||
|
dirtyLineKeys={dirtyLineKeys || []}
|
||||||
onLineChange={this.handleLineChange}
|
onLineChange={this.handleLineChange}
|
||||||
onSkuBlur={this.handleSkuBlur}
|
onSkuBlur={this.handleSkuBlur}
|
||||||
onAddLine={this.handleAddLine}
|
onAddLine={this.handleAddLine}
|
||||||
@@ -827,6 +960,10 @@ class ManifestForm extends PureComponent {
|
|||||||
saving={saving}
|
saving={saving}
|
||||||
pdfUploading={pdfUploading}
|
pdfUploading={pdfUploading}
|
||||||
lines={lines}
|
lines={lines}
|
||||||
|
isDirty={this.state.isDirty}
|
||||||
|
dirtyFields={this.state.dirtyFields}
|
||||||
|
dirtySummary={this.state.dirtySummary}
|
||||||
|
onRestore={this.props.mode === 'edit' && this.state.isDirty ? this.handleRestore : undefined}
|
||||||
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
||||||
onSave={this.handleSave}
|
onSave={this.handleSave}
|
||||||
onPartyChange={this.handlePartyChange}
|
onPartyChange={this.handlePartyChange}
|
||||||
|
|||||||
@@ -838,18 +838,29 @@ class ManifestFormLine extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { columns = LINE_COLUMNS, saving } = this.props;
|
const { columns = LINE_COLUMNS, saving, isDirty } = this.props;
|
||||||
const secondLine = this.renderSecondLine();
|
const secondLine = this.renderSecondLine();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{/* Row 1: Editable Line Inputs */}
|
{/* Row 1: Editable Line Inputs */}
|
||||||
<TableRow hover sx={{ '& > td': { borderBottom: 'none', p: 0 } }}>
|
<TableRow
|
||||||
|
hover
|
||||||
|
sx={{
|
||||||
|
'& > td': { borderBottom: 'none', p: 0 },
|
||||||
|
...(isDirty ? { bgcolor: 'rgba(237, 108, 2, 0.08)' } : {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
{columns.map((col, colIdx) => (
|
{columns.map((col, colIdx) => (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={col.id}
|
key={col.id}
|
||||||
align={col.align}
|
align={col.align}
|
||||||
sx={{ p: 0, width: col.width, verticalAlign: 'middle' }}
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
width: col.width,
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
...(isDirty && colIdx === 0 ? { borderLeft: '3px solid', borderColor: 'warning.main' } : {}),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{this.renderField(col, colIdx, columns.length)}
|
{this.renderField(col, colIdx, columns.length)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -868,13 +879,19 @@ class ManifestFormLine extends PureComponent {
|
|||||||
|
|
||||||
{/* Row 2: Matched Supplier Article Details */}
|
{/* Row 2: Matched Supplier Article Details */}
|
||||||
{secondLine && (
|
{secondLine && (
|
||||||
<TableRow sx={{ '& > td': { pt: 0.25, pb: 0.75, borderBottom: 'none' } }}>
|
<TableRow
|
||||||
|
sx={{
|
||||||
|
'& > td': { pt: 0.25, pb: 0.75, borderBottom: 'none' },
|
||||||
|
...(isDirty ? { bgcolor: 'rgba(237, 108, 2, 0.04)' } : {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={columns.length + 1}
|
colSpan={columns.length + 1}
|
||||||
sx={{
|
sx={{
|
||||||
py: 0.4,
|
py: 0.4,
|
||||||
px: 1,
|
px: 1,
|
||||||
bgcolor: 'transparent',
|
bgcolor: 'transparent',
|
||||||
|
...(isDirty ? { borderLeft: '3px solid', borderColor: 'warning.main' } : {}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{secondLine}
|
{secondLine}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import CircularProgress from '@mui/material/CircularProgress';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||||
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined';
|
||||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||||
import { PanelTile } from 'dashgui/panels';
|
import { PanelTile } from 'dashgui/panels';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
|
import RestoreIcon from '@mui/icons-material/Restore';
|
||||||
import FileDropzone from '../FileDropzone';
|
import FileDropzone from '../FileDropzone';
|
||||||
import AttachmentList from './AttachmentList';
|
import AttachmentList from './AttachmentList';
|
||||||
import { hasArticleLines, manifestConfig, partyDisplayName } from './manifestConfig';
|
import { hasArticleLines, manifestConfig, partyDisplayName } from './manifestConfig';
|
||||||
@@ -232,10 +234,14 @@ class ManifestHeader extends PureComponent {
|
|||||||
onNumberChange,
|
onNumberChange,
|
||||||
onExternalNumberChange,
|
onExternalNumberChange,
|
||||||
onSave,
|
onSave,
|
||||||
|
onRestore,
|
||||||
actionsColor,
|
actionsColor,
|
||||||
docId,
|
docId,
|
||||||
ledgerSummary,
|
ledgerSummary,
|
||||||
onOpenBookDialog,
|
onOpenBookDialog,
|
||||||
|
isDirty,
|
||||||
|
dirtyFields,
|
||||||
|
dirtySummary,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const hasArticles = this.hasArticles();
|
const hasArticles = this.hasArticles();
|
||||||
@@ -250,7 +256,7 @@ class ManifestHeader extends PureComponent {
|
|||||||
(this.state.createdParty?.id === partyId ? this.state.createdParty : null);
|
(this.state.createdParty?.id === partyId ? this.state.createdParty : null);
|
||||||
|
|
||||||
const isDateEmpty = !date || String(date).trim() === '';
|
const isDateEmpty = !date || String(date).trim() === '';
|
||||||
const isPartyEmpty = partyId == null || partyId === '';
|
const isPartyEmpty = kind !== 'quote' && (partyId == null || partyId === '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -267,7 +273,7 @@ class ManifestHeader extends PureComponent {
|
|||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
type="date"
|
type="date"
|
||||||
label={dateLabel}
|
label={dirtyFields?.date ? `${dateLabel} *` : dateLabel}
|
||||||
value={date}
|
value={date}
|
||||||
error={isDateEmpty}
|
error={isDateEmpty}
|
||||||
disabled={saving || pdfUploading}
|
disabled={saving || pdfUploading}
|
||||||
@@ -279,6 +285,12 @@ class ManifestHeader extends PureComponent {
|
|||||||
sx={{
|
sx={{
|
||||||
flex: { xs: '1 1 100%', sm: '1 1 150px' },
|
flex: { xs: '1 1 100%', sm: '1 1 150px' },
|
||||||
minWidth: { sm: 130 },
|
minWidth: { sm: 130 },
|
||||||
|
...(dirtyFields?.date && {
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: 'warning.main',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
@@ -328,7 +340,7 @@ class ManifestHeader extends PureComponent {
|
|||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label={partyLabel}
|
label={dirtyFields?.partyId ? `${partyLabel} *` : partyLabel}
|
||||||
error={isPartyEmpty}
|
error={isPartyEmpty}
|
||||||
onKeyDown={this.handlePartyKeyDown}
|
onKeyDown={this.handlePartyKeyDown}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
@@ -338,6 +350,14 @@ class ManifestHeader extends PureComponent {
|
|||||||
autoComplete: 'off',
|
autoComplete: 'off',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
sx={{
|
||||||
|
...(dirtyFields?.partyId && {
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: 'warning.main',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -355,13 +375,19 @@ class ManifestHeader extends PureComponent {
|
|||||||
{useExternalNumber ? (
|
{useExternalNumber ? (
|
||||||
<SingleLineTextEdit
|
<SingleLineTextEdit
|
||||||
size="small"
|
size="small"
|
||||||
label="Referenz"
|
label={dirtyFields?.externalNumber ? 'Referenz *' : 'Referenz'}
|
||||||
value={externalNumber ?? ''}
|
value={externalNumber ?? ''}
|
||||||
disabled={saving || pdfUploading}
|
disabled={saving || pdfUploading}
|
||||||
onChange={onExternalNumberChange}
|
onChange={onExternalNumberChange}
|
||||||
sx={{
|
sx={{
|
||||||
flex: { xs: '1 1 100%', sm: '1 1 120px' },
|
flex: { xs: '1 1 100%', sm: '1 1 120px' },
|
||||||
minWidth: { sm: 100 },
|
minWidth: { sm: 100 },
|
||||||
|
...(dirtyFields?.externalNumber && {
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: 'warning.main',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -424,7 +450,7 @@ class ManifestHeader extends PureComponent {
|
|||||||
<Stack spacing={1.5} sx={{ pt: 0.5 }}>
|
<Stack spacing={1.5} sx={{ pt: 0.5 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color={isDirty ? 'warning' : 'primary'}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -432,10 +458,61 @@ class ManifestHeader extends PureComponent {
|
|||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
|
...(isDirty && {
|
||||||
|
boxShadow: (theme) => `0 0 0 2px ${theme.palette.warning.main}`,
|
||||||
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{saving ? 'Speichern…' : 'Speichern'}
|
{saving ? 'Speichern…' : isDirty ? 'Speichern *' : 'Speichern'}
|
||||||
</Button>
|
</Button>
|
||||||
|
{isDirty && onRestore && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="warning"
|
||||||
|
size="small"
|
||||||
|
startIcon={<RestoreIcon />}
|
||||||
|
disabled={saving}
|
||||||
|
onClick={onRestore}
|
||||||
|
sx={{
|
||||||
|
height: 34,
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textTransform: 'none',
|
||||||
|
borderWidth: 1.5,
|
||||||
|
'&:hover': { borderWidth: 1.5 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Wiederherstellen
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isDirty && dirtySummary && dirtySummary.length > 0 && (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, pt: 0.25 }}>
|
||||||
|
<Typography variant="caption" sx={{ color: 'warning.dark', fontWeight: 700, fontSize: '0.72rem' }}>
|
||||||
|
Geänderte Felder:
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
|
{dirtySummary.map((item) => (
|
||||||
|
<Chip
|
||||||
|
key={item}
|
||||||
|
size="small"
|
||||||
|
color="warning"
|
||||||
|
variant="filled"
|
||||||
|
label={item}
|
||||||
|
sx={{
|
||||||
|
height: 20,
|
||||||
|
fontSize: '0.68rem',
|
||||||
|
fontWeight: 700,
|
||||||
|
bgcolor: 'rgba(237, 108, 2, 0.15)',
|
||||||
|
color: 'warning.dark',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'warning.main',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
{docId != null && kind === 'delivery' && onOpenBookDialog && (
|
{docId != null && kind === 'delivery' && onOpenBookDialog && (
|
||||||
<Button
|
<Button
|
||||||
variant={ledgerSummary?.status === 'booked' ? 'outlined' : 'contained'}
|
variant={ledgerSummary?.status === 'booked' ? 'outlined' : 'contained'}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import TableContainer from '@mui/material/TableContainer';
|
|||||||
import TableHead from '@mui/material/TableHead';
|
import TableHead from '@mui/material/TableHead';
|
||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
|
import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
|
||||||
@@ -24,6 +25,8 @@ class ManifestLines extends PureComponent {
|
|||||||
lines,
|
lines,
|
||||||
saving,
|
saving,
|
||||||
linesColor,
|
linesColor,
|
||||||
|
isLinesDirty = false,
|
||||||
|
dirtyLineKeys = [],
|
||||||
onLineChange,
|
onLineChange,
|
||||||
onAddLine,
|
onAddLine,
|
||||||
onRemoveLine,
|
onRemoveLine,
|
||||||
@@ -65,7 +68,28 @@ class ManifestLines extends PureComponent {
|
|||||||
wide={wide}
|
wide={wide}
|
||||||
minHeight={280}
|
minHeight={280}
|
||||||
color={linesColor}
|
color={linesColor}
|
||||||
title="Artikel"
|
title={
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<span>Artikel</span>
|
||||||
|
{isLinesDirty && (
|
||||||
|
<Chip
|
||||||
|
size="small"
|
||||||
|
color="warning"
|
||||||
|
variant="filled"
|
||||||
|
label="Geändert"
|
||||||
|
sx={{
|
||||||
|
height: 20,
|
||||||
|
fontSize: '0.68rem',
|
||||||
|
fontWeight: 700,
|
||||||
|
bgcolor: 'rgba(237, 108, 2, 0.15)',
|
||||||
|
color: 'warning.dark',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'warning.main',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
sx={{
|
sx={{
|
||||||
...(wide >= 4 ? { gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' } } : {}),
|
...(wide >= 4 ? { gridColumn: { xs: '1 / -1', sm: '1 / -1', md: 'span 4' } } : {}),
|
||||||
...(!hasArticles
|
...(!hasArticles
|
||||||
@@ -115,6 +139,11 @@ class ManifestLines extends PureComponent {
|
|||||||
(supplierArticlesLoading && !matched)
|
(supplierArticlesLoading && !matched)
|
||||||
: false;
|
: false;
|
||||||
const ledgerLine = line.id != null ? ledgerLinesById[line.id] : null;
|
const ledgerLine = line.id != null ? ledgerLinesById[line.id] : null;
|
||||||
|
const isDirty =
|
||||||
|
dirtyLineKeys.includes(lineKey) ||
|
||||||
|
(line.key != null && dirtyLineKeys.includes(line.key)) ||
|
||||||
|
(line.id != null && dirtyLineKeys.includes(`id-${line.id}`)) ||
|
||||||
|
dirtyLineKeys.includes(`idx-${index}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManifestFormLine
|
<ManifestFormLine
|
||||||
@@ -125,6 +154,7 @@ class ManifestLines extends PureComponent {
|
|||||||
line={line.key != null ? line : { ...line, key: index }}
|
line={line.key != null ? line : { ...line, key: index }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
|
isDirty={Boolean(isDirty)}
|
||||||
supplierArticle={matched}
|
supplierArticle={matched}
|
||||||
loading={Boolean(isLineLoading)}
|
loading={Boolean(isLineLoading)}
|
||||||
supplierArticleBusy={Boolean(busySupplierArticleLineKeys[lineKey])}
|
supplierArticleBusy={Boolean(busySupplierArticleLineKeys[lineKey])}
|
||||||
|
|||||||
Reference in New Issue
Block a user