From eb4654dfbf365ff4b33a5d12c6703b58d2579ddd Mon Sep 17 00:00:00 2001 From: seb Date: Wed, 14 Oct 2026 23:13:46 +0200 Subject: [PATCH] u --- .agents/skills/backend/SKILL.md | 14 +++- src/api/quote.js | 26 +++++++ src/components/AppShell.jsx | 2 +- src/components/manifest/Attachment.jsx | 42 +++++------ src/components/manifest/AttachmentList.jsx | 4 +- src/components/manifest/ManifestForm.jsx | 87 +++++++++++++++++++++- src/components/manifest/ManifestHeader.jsx | 25 +++++++ 7 files changed, 174 insertions(+), 26 deletions(-) diff --git a/.agents/skills/backend/SKILL.md b/.agents/skills/backend/SKILL.md index dbe9104..fc68e35 100644 --- a/.agents/skills/backend/SKILL.md +++ b/.agents/skills/backend/SKILL.md @@ -7,7 +7,7 @@ description: >- # Dash API Routes Overview (All) -Overview of all 242 API endpoints across all modules. +Overview of all 246 API endpoints across all modules. ### Notation @@ -426,6 +426,9 @@ _none_ #### Update delivery — `PATCH /delivery/{id}` - Params: `id: int, body: { externalNumber?: str|null, date?: str, supplierId?: int|null, lines?: T34[], files?: { mimetype: str, name: str, content: str, sort?: int, size?: int, hash?: str, created?: str, modified?: str, meta?: { [k]: str } }[] }` - Returns: `200 T6` +#### Delete delivery file attachment — `DELETE /delivery/{id}/file/{fileId}` +- Params: `id: int, fileId: int` +- Returns: `204 void` #### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}` - Params: `id: int, fileId: int, query: download?: bool` - Returns: `200 str` @@ -639,6 +642,9 @@ _none_ #### Update order — `PATCH /order/{id}` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }` - Returns: `200 T1` +#### Delete order file attachment — `DELETE /order/{id}/file/{fileId}` +- Params: `id: int, fileId: int` +- Returns: `204 void` #### Get order attachment file content — `GET /order/{id}/file/{fileId}` - Params: `id: int, fileId: int, query: download?: bool` - Returns: `200 str` @@ -777,6 +783,9 @@ _none_ #### Update quote — `PATCH /quote/{id}` - Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }` - Returns: `200 T8` +#### Delete quote file attachment — `DELETE /quote/{id}/file/{fileId}` +- Params: `id: int, fileId: int` +- Returns: `204 void` #### Get quote attachment file content — `GET /quote/{id}/file/{fileId}` - Params: `id: int, fileId: int, query: download?: bool` - Returns: `200 str` @@ -807,6 +816,9 @@ _none_ #### Get shipment by ID — `GET /shipment/{id}` - Params: `id: int` - Returns: `200 { shipment: T5 }` +#### Delete shipment file attachment — `DELETE /shipment/{id}/file/{fileId}` +- Params: `id: int, fileId: int` +- Returns: `204 void` #### Get shipment attachment file content — `GET /shipment/{id}/file/{fileId}` - Params: `id: int, fileId: int, query: download?: bool` - Returns: `200 str` diff --git a/src/api/quote.js b/src/api/quote.js index 50ff05e..a3a6898 100644 --- a/src/api/quote.js +++ b/src/api/quote.js @@ -61,4 +61,30 @@ export const quote = { return http(`/api/quote/${id}`, { method: 'DELETE' }); }, // #endregion + + /** + * POST /api/quote/:id/pdf + * Generates and attaches quote PDF. + * @param {number|string} id Quote ID + * → 201 { attachment: object, fileUrl: string } + */ + // #region POST /api/quote/:id/pdf — createPdf + createPdf(id) { + return http(`/api/quote/${id}/pdf`, { method: 'POST' }); + }, + // #endregion + + /** + * DELETE /api/quote/:id/file/:fileId + * Deletes quote attachment file. + * @param {number|string} id Quote ID + * @param {number|string} fileId File ID + * → 204 void + */ + // #region DELETE /api/quote/:id/file/:fileId — deleteFile + deleteFile(id, fileId) { + return http(`/api/quote/${id}/file/${fileId}`, { method: 'DELETE' }); + }, + // #endregion }; + diff --git a/src/components/AppShell.jsx b/src/components/AppShell.jsx index 8dbb259..b3a5610 100644 --- a/src/components/AppShell.jsx +++ b/src/components/AppShell.jsx @@ -21,7 +21,7 @@ import { } from '../colors'; import { ChatFab, ChatDrawer } from './chat'; -const BreakpointOverlay = 0 && import.meta.env.DEV +const BreakpointOverlay = 1 && import.meta.env.DEV ? lazy(() => import('./BreakpointOverlay')) : null; diff --git a/src/components/manifest/Attachment.jsx b/src/components/manifest/Attachment.jsx index 2706eb2..38ea0f8 100644 --- a/src/components/manifest/Attachment.jsx +++ b/src/components/manifest/Attachment.jsx @@ -89,11 +89,13 @@ export class Attachment extends PureComponent { const url = URL.createObjectURL(attachment.file); window.open(url, '_blank'); } else { - const docId = attachment.manifestId; + const docId = this.props.docId ?? attachment.manifestId; const fileId = attachment.fileId ?? attachment.id; + const kind = this.props.kind || 'delivery'; + const prefix = kind === 'delivery' ? 'delivery' : kind === 'quote' ? 'quote' : 'order'; if (docId != null && fileId != null) { window.open( - `/api/delivery/${encodeURIComponent(docId)}/file/${encodeURIComponent(fileId)}`, + `/api/${prefix}/${encodeURIComponent(docId)}/file/${encodeURIComponent(fileId)}`, '_blank', ); } @@ -109,7 +111,7 @@ export class Attachment extends PureComponent { } render() { - const { attachment, disabled, sx, hasArticles } = this.props; + const { attachment, disabled, sx, hasArticles, kind, onRescan } = this.props; const thumbnail = attachment?.thumbnail || this.state.generatedThumbnail; const name = attachment?.name || 'Dokument'; const size = attachment?.size; @@ -229,7 +231,7 @@ export class Attachment extends PureComponent { }} onClick={(e) => e.stopPropagation()} > - {isPdf || isText ? ( + {kind === 'delivery' && Boolean(onRescan) && (isPdf || isText) ? ( ) : null} - - - - - - - + + + {/* Bottom file metadata overlay */} diff --git a/src/components/manifest/AttachmentList.jsx b/src/components/manifest/AttachmentList.jsx index b495f21..5cd5378 100644 --- a/src/components/manifest/AttachmentList.jsx +++ b/src/components/manifest/AttachmentList.jsx @@ -6,7 +6,7 @@ import Attachment from './Attachment'; export class AttachmentList extends PureComponent { render() { - const { attachments, onRescan, onDelete, onClick, disabled, hasArticles } = this.props; + const { attachments, onRescan, onDelete, onClick, disabled, hasArticles, kind, docId } = this.props; if (!Array.isArray(attachments) || attachments.length === 0) { return null; } @@ -26,6 +26,8 @@ export class AttachmentList extends PureComponent { (a.id ?? a.fileId) !== targetId && a !== attachment, + ); + this.setState({ attachments: nextAttachments }); + + if (this.editAutomat) { + this.editAutomat.actions.updateDraft({ attachments: nextAttachments }); + } + + if (docId != null && targetId != null && attachment.isPersisted) { + try { + const api = apiFor(this.cfg); + if (typeof api.deleteFile === 'function') { + await api.deleteFile(docId, targetId); + enqueueSnackbar('Anhang gelöscht.', { variant: 'success' }); + } + } catch (err) { + notifyError(err); + } + } + } + async handleSave() { const { mode, docId } = this.props; const cfg = this.cfg; @@ -965,12 +1045,17 @@ class ManifestForm extends PureComponent { externalNumber={externalNumber} saving={saving} pdfUploading={pdfUploading} + pdfCreating={this.state.pdfCreating} lines={lines} + attachments={this.state.attachments} + docId={this.props.docId} 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} + onPdfDelete={this.handleAttachmentDelete} + onPdfCreate={kind === 'quote' ? this.handleCreatePdf : undefined} onSave={this.handleSave} onPartyChange={this.handlePartyChange} onDateChange={this.handleDateChange} diff --git a/src/components/manifest/ManifestHeader.jsx b/src/components/manifest/ManifestHeader.jsx index 2717a39..c31acfb 100644 --- a/src/components/manifest/ManifestHeader.jsx +++ b/src/components/manifest/ManifestHeader.jsx @@ -7,6 +7,7 @@ import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Chip from '@mui/material/Chip'; import UploadFileIcon from '@mui/icons-material/UploadFile'; +import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined'; import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; @@ -225,6 +226,8 @@ class ManifestHeader extends PureComponent { pdfUploading, attachments, onPdfUpload, + onPdfCreate, + pdfCreating, onPdfRescan, onPdfDelete, onPdfClick, @@ -428,6 +431,8 @@ class ManifestHeader extends PureComponent { )} )} + {kind === 'quote' && onPdfCreate && docId != null && Boolean(number) && ( + + )} {onPdfUpload ? (