diff --git a/src/automats/deliveryEditorAutomat.js b/src/automats/deliveryEditorAutomat.js index 12fb723..c47d8d0 100644 --- a/src/automats/deliveryEditorAutomat.js +++ b/src/automats/deliveryEditorAutomat.js @@ -77,12 +77,19 @@ function parseNum(value) { * @returns {DeliveryLine} Normalized line. */ export function createEmptyLineitem(line = {}) { - const netRaw = line.netprice ?? line.net ?? line.eknetto ?? line.netPrice ?? null; + let netVal = null; + if (line.netprice != null || line.net != null || line.netPrice != null) { + const netRaw = line.netprice ?? line.net ?? line.netPrice; + netVal = netRaw != null ? (parseNum(netRaw) ?? netRaw) : null; + } else if (line.eknetto != null && line.eknetto !== '') { + const parsedCent = parseNum(line.eknetto); + netVal = parsedCent != null ? Math.round((parsedCent / 100) * 10000) / 10000 : null; + } + const taxRaw = line.taxrate ?? line.tax ?? line.mwst ?? line.taxRate ?? null; const amountRaw = line.amount ?? line.count ?? line.qty ?? line.quantity ?? null; const puRaw = line.pu ?? line.vpe ?? null; - const netVal = netRaw != null ? (parseNum(netRaw) ?? netRaw) : null; const taxVal = taxRaw != null ? (parseNum(taxRaw) ?? taxRaw) : null; const amountVal = amountRaw != null ? (parseNum(amountRaw) ?? amountRaw) : null; const puVal = puRaw != null ? (parseNum(puRaw) ?? puRaw) : null; diff --git a/src/components/chat/ChatFlowList.jsx b/src/components/chat/ChatFlowList.jsx index a4d4a3e..ee3dfb6 100644 --- a/src/components/chat/ChatFlowList.jsx +++ b/src/components/chat/ChatFlowList.jsx @@ -19,7 +19,7 @@ import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import DeleteIcon from '@mui/icons-material/Delete'; import GoogleIcon from '@mui/icons-material/Google'; import WebIcon from '@mui/icons-material/Web'; -import EscalatorWarningIcon from '@mui/icons-material/EscalatorWarning'; +import EngineeringIcon from '@mui/icons-material/Engineering'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import ManageSearchIcon from '@mui/icons-material/ManageSearch'; import LensBlurIcon from '@mui/icons-material/LensBlur'; @@ -91,7 +91,9 @@ const TOOL_ICON_MAP = { rm: DeleteIcon, web_search: GoogleFaviconIcon, url_get: WebIcon, - sub_agent: EscalatorWarningIcon, + subagent: EngineeringIcon, + sub_agent: EngineeringIcon, + 'sub-agent': EngineeringIcon, pdf_read: PictureAsPdfIcon, search_articles: ManageSearchIcon, edit_image: LensBlurIcon, diff --git a/src/components/manifest/manifestConfig.js b/src/components/manifest/manifestConfig.js index cc825d1..8de78b2 100644 --- a/src/components/manifest/manifestConfig.js +++ b/src/components/manifest/manifestConfig.js @@ -454,7 +454,7 @@ export function collectFilesForUpload(attachments = [], { onlyNew = false } = {} */ export async function buildManifestBody( form, - { partyField, requireAtLeastOne, externalNumber: useExternalNumber, kind, isUpdate, mode }, + { partyField, requireAtLeastOne, externalNumber: useExternalNumber, kind, isUpdate, mode, omitLines }, ) { const date = form.date; if (!date) throw new Error('Datum fehlt.'); @@ -462,6 +462,7 @@ export async function buildManifestBody( const externalNumber = String(form.externalNumber ?? '').trim(); const isEdit = isUpdate != null ? Boolean(isUpdate) : (mode === 'edit' || !requireAtLeastOne); const shouldRequireLine = requireAtLeastOne !== false; + const shouldIncludeLines = !omitLines && form.lines !== undefined && form.lines !== null; let body; if (kind === 'delivery') { @@ -470,8 +471,10 @@ export async function buildManifestBody( body = { externalNumber: externalNumber || null, date, - lines: buildLines(form.lines, { requireAtLeastOne: shouldRequireLine }), }; + if (shouldIncludeLines) { + body.lines = buildLines(form.lines, { requireAtLeastOne: shouldRequireLine }); + } } else { let number = String(form.number ?? '').trim(); if (!number) { @@ -481,8 +484,10 @@ export async function buildManifestBody( body = { number, date, - lines: buildLines(form.lines, { requireAtLeastOne: shouldRequireLine }), }; + if (shouldIncludeLines) { + body.lines = buildLines(form.lines, { requireAtLeastOne: shouldRequireLine }); + } if (useExternalNumber || form.externalNumber != null) { body.externalNumber = externalNumber || null; } diff --git a/src/routes/lieferung/neu.jsx b/src/routes/lieferung/neu.jsx index 7c542b4..0992762 100644 --- a/src/routes/lieferung/neu.jsx +++ b/src/routes/lieferung/neu.jsx @@ -57,6 +57,51 @@ import { YELLOW_BG, } from '../../colors'; +function areLinesEqual(lines1, lines2) { + if (!Array.isArray(lines1) || !Array.isArray(lines2)) return false; + if (lines1.length !== lines2.length) return false; + for (let i = 0; i < lines1.length; i++) { + const a = lines1[i] || {}; + const b = lines2[i] || {}; + const aSku = String(a.sku ?? '').trim(); + const bSku = String(b.sku ?? '').trim(); + if (aSku !== bSku) return false; + + const aName = String(a.name ?? '').trim(); + const bName = String(b.name ?? '').trim(); + if (aName !== bName) return false; + + const aAmount = a.amount != null && a.amount !== '' ? Number(a.amount) : null; + const bAmount = b.amount != null && b.amount !== '' ? Number(b.amount) : null; + if (aAmount !== bAmount) return false; + + const aNet = + (a.netprice ?? a.net) != null && (a.netprice ?? a.net) !== '' + ? Number(a.netprice ?? a.net) + : null; + const bNet = + (b.netprice ?? b.net) != null && (b.netprice ?? b.net) !== '' + ? Number(b.netprice ?? b.net) + : null; + if (aNet !== bNet) return false; + + const aTax = + (a.taxrate ?? a.tax) != null && (a.taxrate ?? a.tax) !== '' + ? Number(a.taxrate ?? a.tax) + : null; + const bTax = + (b.taxrate ?? b.tax) != null && (b.taxrate ?? b.tax) !== '' + ? Number(b.taxrate ?? b.tax) + : null; + if (aTax !== bTax) return false; + + const aPu = a.pu != null && a.pu !== '' ? Number(a.pu) : 1; + const bPu = b.pu != null && b.pu !== '' ? Number(b.pu) : 1; + if (aPu !== bPu) return false; + } + return true; +} + class LieferungNeuPage extends PureComponent { constructor(props) { super(props); @@ -150,6 +195,7 @@ class LieferungNeuPage extends PureComponent { this.automat = getDeliveryEditorAutomat(this.props.docId); this.unsubscribe = this.automat.subscribe(this); this.setState({ ...this.automat.state }); + this._initialLines = null; if (this.props.docId != null) { void this.loadEdit(); } else { @@ -227,6 +273,7 @@ class LieferungNeuPage extends PureComponent { if (doc.number != null) this.automat.actions.setNumber(doc.number); if (doc.externalNumber != null) this.automat.actions.setExternalNumber(String(doc.externalNumber)); this.automat.actions.setLineitems(lines); + this._initialLines = lines; // Restore attachments with thumbnails const apiFiles = doc.files ?? []; @@ -786,9 +833,24 @@ class LieferungNeuPage extends PureComponent { }); } - handlePdfDelete(attachment) { - if (!attachment?.id) return; - this.automat.actions.removeAttachment(attachment.id); + async handlePdfDelete(attachment) { + if (!attachment) return; + const fileId = attachment.fileId ?? attachment.id; + const docId = this.props.docId ?? attachment.manifestId ?? this.state.id; + + this.automat.actions.removeAttachment(attachment.id ?? fileId); + if (fileId != null && fileId !== attachment.id) { + this.automat.actions.removeAttachment(fileId); + } + + if (docId != null && fileId != null && attachment.isPersisted) { + try { + await deliveryApi.deleteFile(docId, fileId); + enqueueSnackbar('Anhang gelöscht.', { variant: 'success' }); + } catch (err) { + notifyError(err); + } + } } async ensureAttachmentFile(attachment) { @@ -1031,6 +1093,9 @@ class LieferungNeuPage extends PureComponent { if (this.state.saving) return; const isCreate = docId == null; + const linesModified = + isCreate || !this._initialLines || !areLinesEqual(this.state.lines, this._initialLines); + let body; try { body = await buildManifestBody( @@ -1039,13 +1104,17 @@ class LieferungNeuPage extends PureComponent { externalNumber: this.state.externalNumber ?? '', date: this.state.date ?? '', partyId: this.state.partyId, - lines: this.state.lines, + lines: linesModified ? this.state.lines : undefined, attachments: this.state.attachments, }, { + kind: cfg.kind, partyField: cfg.partyField, requireAtLeastOne: isCreate, externalNumber: cfg.externalNumber, + isUpdate: !isCreate, + mode: isCreate ? 'create' : 'edit', + omitLines: !linesModified, }, ); } catch (err) { @@ -1083,6 +1152,7 @@ class LieferungNeuPage extends PureComponent { if (doc.number != null) this.automat.actions.setNumber(doc.number); if (doc.externalNumber != null) this.automat.actions.setExternalNumber(String(doc.externalNumber)); this.automat.actions.setLineitems(lines); + this._initialLines = lines; void this.reloadLedgerSummary(); // Restore attachments with thumbnails