From a90776bb8b6e0c3cac14d98b61dcf15e1bda5942 Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 15 Oct 2026 11:12:02 +0200 Subject: [PATCH] u --- src/api/order.js | 12 + src/automats/manifestDraftAutomat.js | 8 + src/components/manifest/ManifestForm.jsx | 88 +++++- src/components/manifest/ManifestHeader.jsx | 2 +- .../manifest/OrderDocumentsTiles.jsx | 277 ++++++++++++++++++ src/components/manifest/index.js | 1 + 6 files changed, 378 insertions(+), 10 deletions(-) create mode 100644 src/components/manifest/OrderDocumentsTiles.jsx diff --git a/src/api/order.js b/src/api/order.js index 419aaa9..489985b 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -75,4 +75,16 @@ export const order = { return http(`/api/order/${id}/file/${fileId}`, { method: 'DELETE' }); }, // #endregion + + /** + * POST /api/order/:id/pdf + * Generates and attaches order confirmation PDF. + * @param {number|string} id Order ID + * → 201 { attachment: Attachment, fileUrl: string } + */ + // #region POST /api/order/:id/pdf — createPdf + createPdf(id) { + return http(`/api/order/${id}/pdf`, { method: 'POST' }); + }, + // #endregion }; diff --git a/src/automats/manifestDraftAutomat.js b/src/automats/manifestDraftAutomat.js index 85b33e5..09c9d76 100644 --- a/src/automats/manifestDraftAutomat.js +++ b/src/automats/manifestDraftAutomat.js @@ -445,6 +445,14 @@ export function createManifestEditAutomat({ kind, name }) { externalNumber, lines, attachments, + shipments: doc.shipments || [], + payments: doc.payments || [], + shippings: doc.shippings || [], + orderTotal: doc.orderTotal ?? null, + paidTotal: doc.paidTotal ?? null, + paid: Boolean(doc.paid), + shipped: Boolean(doc.shipped), + delivered: Boolean(doc.delivered), baseline, isDirty: false, dirtyFields: { ...emptyFields }, diff --git a/src/components/manifest/ManifestForm.jsx b/src/components/manifest/ManifestForm.jsx index 594bfdf..85b9a99 100644 --- a/src/components/manifest/ManifestForm.jsx +++ b/src/components/manifest/ManifestForm.jsx @@ -4,7 +4,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import { enqueueSnackbar } from 'notistack'; import BackendError from '../BackendError'; import { PanelTileGrid } from 'dashgui/panels'; -import { delivery as deliveryApi, quote as quoteApi } from '../../api'; +import { delivery as deliveryApi, quote as quoteApi, order as orderApi, shipment as shipmentApi, payment as paymentApi } from '../../api'; import { requireArray, requireDefined, requireObject } from '../../utils/apiShape'; import { backendErrorFrom } from '../../utils/backendError'; import { notifyError } from '../../utils/notify'; @@ -13,6 +13,7 @@ import ManifestHeader from './ManifestHeader'; import ManifestLines from './ManifestLines'; import ManifestPdfDialog from './ManifestPdfDialog'; import ArticleSearchDialog from './ArticleSearchDialog'; +import OrderDocumentsTiles from './OrderDocumentsTiles'; import artikelAutomat, { getArticle } from '../../automats/artikelAutomat'; import entityAutomat from '../../automats/entityAutomat'; import supplierAutomat from '../../automats/supplierAutomat'; @@ -80,6 +81,14 @@ class ManifestForm extends PureComponent { externalNumber: initialDoc?.externalNumber || '', lines: initialDoc?.lines?.length > 0 ? initialDoc.lines : [emptyLine()], attachments: initialDoc?.attachments || [], + shipments: initialEdit?.shipments || [], + payments: initialEdit?.payments || [], + shippings: initialEdit?.shippings || [], + orderTotal: initialEdit?.orderTotal ?? null, + paidTotal: initialEdit?.paidTotal ?? null, + paid: Boolean(initialEdit?.paid), + shipped: Boolean(initialEdit?.shipped), + delivered: Boolean(initialEdit?.delivered), isDirty: Boolean(initialEdit?.isDirty), dirtyFields: initialEdit?.dirtyFields || null, dirtyLineKeys: initialEdit?.dirtyLineKeys || [], @@ -188,6 +197,14 @@ class ManifestForm extends PureComponent { externalNumber: state.externalNumber || '', lines: state.lines?.length > 0 ? state.lines : [emptyLine()], attachments: state.attachments || [], + shipments: state.shipments || [], + payments: state.payments || [], + shippings: state.shippings || [], + orderTotal: state.orderTotal ?? null, + paidTotal: state.paidTotal ?? null, + paid: Boolean(state.paid), + shipped: Boolean(state.shipped), + delivered: Boolean(state.delivered), isDirty: Boolean(state.isDirty), dirtyFields: state.dirtyFields, dirtyLineKeys: state.dirtyLineKeys, @@ -347,6 +364,27 @@ class ManifestForm extends PureComponent { const apiFiles = doc.files ?? []; const attachments = await restoreAttachmentsFromApi(apiFiles); + let shipments = doc.shipments; + let payments = doc.payments; + if (kind === 'order') { + if (!shipments) { + try { + const shRes = await shipmentApi.listByOrder(docId); + shipments = shRes?.shipments ?? []; + } catch { + shipments = []; + } + } + if (!payments) { + try { + const payRes = await paymentApi.listByOrder(docId); + payments = payRes?.payments ?? []; + } catch { + payments = []; + } + } + } + const loadedDoc = { date: dateInputValue(doc.date), partyId: doc[cfg.partyField], @@ -355,6 +393,14 @@ class ManifestForm extends PureComponent { doc.externalNumber != null ? String(doc.externalNumber) : '', lines, attachments, + shipments: shipments || [], + payments: payments || [], + shippings: doc.shippings || [], + orderTotal: doc.orderTotal ?? null, + paidTotal: doc.paidTotal ?? null, + paid: Boolean(doc.paid), + shipped: Boolean(doc.shipped), + delivered: Boolean(doc.delivered), }; if (this.editAutomat) { @@ -861,19 +907,21 @@ class ManifestForm extends PureComponent { async handleCreatePdf(e) { e?.preventDefault?.(); e?.stopPropagation?.(); - const { docId } = this.props; + const { docId, kind } = this.props; if (this.state.pdfCreating || this.state.saving) return; if (docId == null) return; this.setState({ pdfCreating: true }); try { - const res = await quoteApi.createPdf(docId); - requireObject(res, 'POST /api/quote/:id/pdf'); + const api = kind === 'order' ? orderApi : quoteApi; + const res = await api.createPdf(docId); + requireObject(res, `POST /api/${kind}/:id/pdf`); - const quoteRes = await quoteApi.get(docId); - if (quoteRes?.quote?.files && !this._cancelled) { + const getRes = await api.get(docId); + const doc = getRes?.[kind]; + if (doc?.files && !this._cancelled) { const attachments = await restoreAttachmentsFromApi( - quoteRes.quote.files, + doc.files, this.state.attachments, ); this.setState({ attachments }); @@ -882,7 +930,10 @@ class ManifestForm extends PureComponent { } } - enqueueSnackbar('Angebot-PDF erfolgreich erstellt.', { variant: 'success' }); + enqueueSnackbar( + kind === 'order' ? 'Auftrag-PDF erfolgreich erstellt.' : 'Angebot-PDF erfolgreich erstellt.', + { variant: 'success' }, + ); } catch (err) { if (!this._cancelled) { notifyError(err); @@ -1091,7 +1142,7 @@ class ManifestForm extends PureComponent { onRestore={this.props.mode === 'edit' && this.state.isDirty ? this.handleRestore : undefined} onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined} onPdfDelete={this.handleAttachmentDelete} - onPdfCreate={kind === 'quote' ? this.handleCreatePdf : undefined} + onPdfCreate={kind === 'quote' || kind === 'order' ? this.handleCreatePdf : undefined} onSave={this.handleSave} onPartyChange={this.handlePartyChange} onDateChange={this.handleDateChange} @@ -1099,6 +1150,25 @@ class ManifestForm extends PureComponent { onExternalNumberChange={this.handleExternalNumberChange} /> {status === 'ready' ? this.renderArticles() : null} + {status === 'ready' && kind === 'order' && this.props.mode === 'edit' ? ( + + ) : null} )} - {kind === 'quote' && onPdfCreate && docId != null && Boolean(number) && ( + {(kind === 'quote' || kind === 'order') && onPdfCreate && docId != null && Boolean(number) && ( + + + {shipments.length > 0 ? ( + + {shipments.map((sh) => { + const carrier = sh.shippings?.[0]?.carrier; + const tracking = sh.shippings?.[0]?.trackingCode; + const posCount = sh.lineCount ?? sh.lines?.length ?? 0; + + return ( + + + + {sh.number} + + + {sh.date ? formatDate(sh.date) : '–'} + + + + {sh.externalNumber ? ( + + Ref: {sh.externalNumber} + + ) : null} + + + + {posCount} Pos. + + {carrier || tracking ? ( + + ) : null} + + + ); + })} + + ) : ( + + + Noch keine Lieferscheine erstellt. + + + + )} + + ); + } + + renderPaymentsTile() { + const { payments = [], paid, paidTotal, orderTotal } = this.props; + + const tot = Number(orderTotal) || 0; + const pd = Number(paidTotal) || 0; + const openAmount = Math.max(0, tot - pd); + + const statusLabel = paid ? 'Bezahlt' : pd > 0 ? 'Teilzahlung' : 'Offen'; + const statusColor = paid ? 'success' : pd > 0 ? 'warning' : 'error'; + + return ( + + + + + + + + + + {/* Financial KPI Card */} + + + + Summe: + + + {formatPrice(tot)} + + + + + Bezahlt: + + + {formatPrice(pd)} + + + + + + Offen: + + 0 ? 'error.main' : 'text.secondary' }}> + {formatPrice(openAmount)} + + + + + {payments.length > 0 ? ( + + {payments.map((pay) => ( + + + + + #{pay.id} + + + + +{formatPrice(pay.amount)} + + + ))} + + ) : ( + + + Noch keine Zahlungen erfasst. + + + )} + + ); + } + + render() { + return ( + <> + {this.renderShipmentsTile()} + {this.renderPaymentsTile()} + + ); + } +} + +export default OrderDocumentsTiles; diff --git a/src/components/manifest/index.js b/src/components/manifest/index.js index 8e91876..2cb9ca1 100644 --- a/src/components/manifest/index.js +++ b/src/components/manifest/index.js @@ -15,6 +15,7 @@ export { default as TextEditorPanel } from './TextEditorPanel'; export { default as PdfViewerPanel } from './PdfViewerPanel'; export { default as ArticleSearchDialog } from './ArticleSearchDialog'; export { default as BookDeliveryDialog } from './BookDeliveryDialog'; +export { default as OrderDocumentsTiles } from './OrderDocumentsTiles'; export { withManifestId } from './withManifestId'; export { manifestConfig,