u
This commit is contained in:
@@ -233,3 +233,307 @@ export function getManifestDraftAutomat(kind) {
|
||||
if (kind === 'order') return orderDraftAutomat;
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user