u
This commit is contained in:
@@ -7,7 +7,7 @@ description: >-
|
|||||||
|
|
||||||
# Dash API Routes Overview (All)
|
# Dash API Routes Overview (All)
|
||||||
|
|
||||||
Overview of all 242 API endpoints across all modules.
|
Overview of all 246 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -426,6 +426,9 @@ _none_
|
|||||||
#### Update delivery — `PATCH /delivery/{id}`
|
#### 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 } }[] }`
|
- 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`
|
- 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}`
|
#### Get delivery attachment file content — `GET /delivery/{id}/file/{fileId}`
|
||||||
- Params: `id: int, fileId: int, query: download?: bool`
|
- Params: `id: int, fileId: int, query: download?: bool`
|
||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
@@ -639,6 +642,9 @@ _none_
|
|||||||
#### Update order — `PATCH /order/{id}`
|
#### Update order — `PATCH /order/{id}`
|
||||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }`
|
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }`
|
||||||
- Returns: `200 T1`
|
- 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}`
|
#### Get order attachment file content — `GET /order/{id}/file/{fileId}`
|
||||||
- Params: `id: int, fileId: int, query: download?: bool`
|
- Params: `id: int, fileId: int, query: download?: bool`
|
||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
@@ -777,6 +783,9 @@ _none_
|
|||||||
#### Update quote — `PATCH /quote/{id}`
|
#### Update quote — `PATCH /quote/{id}`
|
||||||
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }`
|
- Params: `id: int, body: { number?: str, date?: str, entityId?: int|null, lines?: T34[] }`
|
||||||
- Returns: `200 T8`
|
- 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}`
|
#### Get quote attachment file content — `GET /quote/{id}/file/{fileId}`
|
||||||
- Params: `id: int, fileId: int, query: download?: bool`
|
- Params: `id: int, fileId: int, query: download?: bool`
|
||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
@@ -807,6 +816,9 @@ _none_
|
|||||||
#### Get shipment by ID — `GET /shipment/{id}`
|
#### Get shipment by ID — `GET /shipment/{id}`
|
||||||
- Params: `id: int`
|
- Params: `id: int`
|
||||||
- Returns: `200 { shipment: T5 }`
|
- 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}`
|
#### Get shipment attachment file content — `GET /shipment/{id}/file/{fileId}`
|
||||||
- Params: `id: int, fileId: int, query: download?: bool`
|
- Params: `id: int, fileId: int, query: download?: bool`
|
||||||
- Returns: `200 str`
|
- Returns: `200 str`
|
||||||
|
|||||||
@@ -61,4 +61,30 @@ export const quote = {
|
|||||||
return http(`/api/quote/${id}`, { method: 'DELETE' });
|
return http(`/api/quote/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
// #endregion
|
// #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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from '../colors';
|
} from '../colors';
|
||||||
import { ChatFab, ChatDrawer } from './chat';
|
import { ChatFab, ChatDrawer } from './chat';
|
||||||
|
|
||||||
const BreakpointOverlay = 0 && import.meta.env.DEV
|
const BreakpointOverlay = 1 && import.meta.env.DEV
|
||||||
? lazy(() => import('./BreakpointOverlay'))
|
? lazy(() => import('./BreakpointOverlay'))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
|||||||
@@ -89,11 +89,13 @@ export class Attachment extends PureComponent {
|
|||||||
const url = URL.createObjectURL(attachment.file);
|
const url = URL.createObjectURL(attachment.file);
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
} else {
|
} else {
|
||||||
const docId = attachment.manifestId;
|
const docId = this.props.docId ?? attachment.manifestId;
|
||||||
const fileId = attachment.fileId ?? attachment.id;
|
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) {
|
if (docId != null && fileId != null) {
|
||||||
window.open(
|
window.open(
|
||||||
`/api/delivery/${encodeURIComponent(docId)}/file/${encodeURIComponent(fileId)}`,
|
`/api/${prefix}/${encodeURIComponent(docId)}/file/${encodeURIComponent(fileId)}`,
|
||||||
'_blank',
|
'_blank',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -109,7 +111,7 @@ export class Attachment extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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 thumbnail = attachment?.thumbnail || this.state.generatedThumbnail;
|
||||||
const name = attachment?.name || 'Dokument';
|
const name = attachment?.name || 'Dokument';
|
||||||
const size = attachment?.size;
|
const size = attachment?.size;
|
||||||
@@ -229,7 +231,7 @@ export class Attachment extends PureComponent {
|
|||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{isPdf || isText ? (
|
{kind === 'delivery' && Boolean(onRescan) && (isPdf || isText) ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
hasArticles
|
hasArticles
|
||||||
@@ -270,24 +272,20 @@ export class Attachment extends PureComponent {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
<Tooltip title="Löschen">
|
<IconButton
|
||||||
<span>
|
size="small"
|
||||||
<IconButton
|
disabled={disabled}
|
||||||
size="small"
|
onClick={this.handleDelete}
|
||||||
disabled={disabled}
|
sx={{
|
||||||
onClick={this.handleDelete}
|
bgcolor: SCRIM_ACTION,
|
||||||
sx={{
|
color: WHITE,
|
||||||
bgcolor: SCRIM_ACTION,
|
p: 0.5,
|
||||||
color: WHITE,
|
backdropFilter: 'blur(2px)',
|
||||||
p: 0.5,
|
'&:hover': { bgcolor: 'error.main' },
|
||||||
backdropFilter: 'blur(2px)',
|
}}
|
||||||
'&:hover': { bgcolor: 'error.main' },
|
>
|
||||||
}}
|
<DeleteOutlineOutlinedIcon sx={{ fontSize: 16 }} />
|
||||||
>
|
</IconButton>
|
||||||
<DeleteOutlineOutlinedIcon sx={{ fontSize: 16 }} />
|
|
||||||
</IconButton>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Bottom file metadata overlay */}
|
{/* Bottom file metadata overlay */}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Attachment from './Attachment';
|
|||||||
|
|
||||||
export class AttachmentList extends PureComponent {
|
export class AttachmentList extends PureComponent {
|
||||||
render() {
|
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) {
|
if (!Array.isArray(attachments) || attachments.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,8 @@ export class AttachmentList extends PureComponent {
|
|||||||
<Attachment
|
<Attachment
|
||||||
key={attachment.id ?? index}
|
key={attachment.id ?? index}
|
||||||
attachment={attachment}
|
attachment={attachment}
|
||||||
|
kind={kind}
|
||||||
|
docId={docId}
|
||||||
onRescan={onRescan}
|
onRescan={onRescan}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import CircularProgress from '@mui/material/CircularProgress';
|
|||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
import BackendError from '../BackendError';
|
import BackendError from '../BackendError';
|
||||||
import { PanelTileGrid } from 'dashgui/panels';
|
import { PanelTileGrid } from 'dashgui/panels';
|
||||||
import { delivery as deliveryApi } from '../../api';
|
import { delivery as deliveryApi, quote as quoteApi } from '../../api';
|
||||||
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
|
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
|
||||||
import { backendErrorFrom } from '../../utils/backendError';
|
import { backendErrorFrom } from '../../utils/backendError';
|
||||||
import { notifyError } from '../../utils/notify';
|
import { notifyError } from '../../utils/notify';
|
||||||
@@ -73,6 +73,7 @@ class ManifestForm extends PureComponent {
|
|||||||
dirtySummary: initialEdit?.dirtySummary || [],
|
dirtySummary: initialEdit?.dirtySummary || [],
|
||||||
saving: false,
|
saving: false,
|
||||||
pdfUploading: false,
|
pdfUploading: false,
|
||||||
|
pdfCreating: false,
|
||||||
pdfDialogOpen: false,
|
pdfDialogOpen: false,
|
||||||
pdfDialogData: null,
|
pdfDialogData: null,
|
||||||
pdfFileName: '',
|
pdfFileName: '',
|
||||||
@@ -98,6 +99,8 @@ class ManifestForm extends PureComponent {
|
|||||||
this.handleRemoveAllLines = this.handleRemoveAllLines.bind(this);
|
this.handleRemoveAllLines = this.handleRemoveAllLines.bind(this);
|
||||||
this.handleSave = this.handleSave.bind(this);
|
this.handleSave = this.handleSave.bind(this);
|
||||||
this.handlePdfUpload = this.handlePdfUpload.bind(this);
|
this.handlePdfUpload = this.handlePdfUpload.bind(this);
|
||||||
|
this.handleCreatePdf = this.handleCreatePdf.bind(this);
|
||||||
|
this.handleAttachmentDelete = this.handleAttachmentDelete.bind(this);
|
||||||
this.handleClosePdfDialog = this.handleClosePdfDialog.bind(this);
|
this.handleClosePdfDialog = this.handleClosePdfDialog.bind(this);
|
||||||
this.handleAbortParse = this.handleAbortParse.bind(this);
|
this.handleAbortParse = this.handleAbortParse.bind(this);
|
||||||
}
|
}
|
||||||
@@ -806,6 +809,83 @@ class ManifestForm extends PureComponent {
|
|||||||
this.setState({ pdfDialogOpen: false, pdfUploading: false });
|
this.setState({ pdfDialogOpen: false, pdfUploading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleCreatePdf() {
|
||||||
|
const { docId } = this.props;
|
||||||
|
if (this.state.pdfCreating || this.state.saving) return;
|
||||||
|
if (docId == null) return;
|
||||||
|
|
||||||
|
if (this.state.isDirty) {
|
||||||
|
await this.handleSave();
|
||||||
|
if (this.state.isDirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ pdfCreating: true });
|
||||||
|
try {
|
||||||
|
const res = await quoteApi.createPdf(docId);
|
||||||
|
requireObject(res, 'POST /api/quote/:id/pdf');
|
||||||
|
|
||||||
|
const quoteRes = await quoteApi.get(docId);
|
||||||
|
if (quoteRes?.quote?.files && !this._cancelled) {
|
||||||
|
const attachments = await restoreAttachmentsFromApi(
|
||||||
|
quoteRes.quote.files,
|
||||||
|
this.state.attachments,
|
||||||
|
);
|
||||||
|
this.setState({ attachments });
|
||||||
|
if (this.editAutomat) {
|
||||||
|
this.editAutomat.actions.updateDraft({ attachments });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueSnackbar('Angebot-PDF erfolgreich erstellt.', { variant: 'success' });
|
||||||
|
|
||||||
|
const fileUrl =
|
||||||
|
res.fileUrl ||
|
||||||
|
(res.attachment?.fileId
|
||||||
|
? `/api/quote/${docId}/file/${res.attachment.fileId}`
|
||||||
|
: null);
|
||||||
|
if (fileUrl) {
|
||||||
|
window.open(fileUrl, '_blank');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!this._cancelled) {
|
||||||
|
notifyError(err);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!this._cancelled) {
|
||||||
|
this.setState({ pdfCreating: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAttachmentDelete(attachment) {
|
||||||
|
if (!attachment) return;
|
||||||
|
const targetId = attachment.id ?? attachment.fileId;
|
||||||
|
const { docId } = this.props;
|
||||||
|
|
||||||
|
const nextAttachments = (this.state.attachments || []).filter(
|
||||||
|
(a) => (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() {
|
async handleSave() {
|
||||||
const { mode, docId } = this.props;
|
const { mode, docId } = this.props;
|
||||||
const cfg = this.cfg;
|
const cfg = this.cfg;
|
||||||
@@ -965,12 +1045,17 @@ class ManifestForm extends PureComponent {
|
|||||||
externalNumber={externalNumber}
|
externalNumber={externalNumber}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
pdfUploading={pdfUploading}
|
pdfUploading={pdfUploading}
|
||||||
|
pdfCreating={this.state.pdfCreating}
|
||||||
lines={lines}
|
lines={lines}
|
||||||
|
attachments={this.state.attachments}
|
||||||
|
docId={this.props.docId}
|
||||||
isDirty={this.state.isDirty}
|
isDirty={this.state.isDirty}
|
||||||
dirtyFields={this.state.dirtyFields}
|
dirtyFields={this.state.dirtyFields}
|
||||||
dirtySummary={this.state.dirtySummary}
|
dirtySummary={this.state.dirtySummary}
|
||||||
onRestore={this.props.mode === 'edit' && this.state.isDirty ? this.handleRestore : undefined}
|
onRestore={this.props.mode === 'edit' && this.state.isDirty ? this.handleRestore : undefined}
|
||||||
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
||||||
|
onPdfDelete={this.handleAttachmentDelete}
|
||||||
|
onPdfCreate={kind === 'quote' ? this.handleCreatePdf : undefined}
|
||||||
onSave={this.handleSave}
|
onSave={this.handleSave}
|
||||||
onPartyChange={this.handlePartyChange}
|
onPartyChange={this.handlePartyChange}
|
||||||
onDateChange={this.handleDateChange}
|
onDateChange={this.handleDateChange}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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 UploadFileIcon from '@mui/icons-material/UploadFile';
|
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||||
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||||
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';
|
||||||
@@ -225,6 +226,8 @@ class ManifestHeader extends PureComponent {
|
|||||||
pdfUploading,
|
pdfUploading,
|
||||||
attachments,
|
attachments,
|
||||||
onPdfUpload,
|
onPdfUpload,
|
||||||
|
onPdfCreate,
|
||||||
|
pdfCreating,
|
||||||
onPdfRescan,
|
onPdfRescan,
|
||||||
onPdfDelete,
|
onPdfDelete,
|
||||||
onPdfClick,
|
onPdfClick,
|
||||||
@@ -428,6 +431,8 @@ class ManifestHeader extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<AttachmentList
|
<AttachmentList
|
||||||
|
kind={kind}
|
||||||
|
docId={docId}
|
||||||
attachments={attachments}
|
attachments={attachments}
|
||||||
onRescan={onPdfRescan}
|
onRescan={onPdfRescan}
|
||||||
onDelete={onPdfDelete}
|
onDelete={onPdfDelete}
|
||||||
@@ -518,6 +523,26 @@ class ManifestHeader extends PureComponent {
|
|||||||
: 'Ins Lager buchen'}
|
: 'Ins Lager buchen'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{kind === 'quote' && onPdfCreate && docId != null && Boolean(number) && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={pdfCreating ? <CircularProgress size={16} color="inherit" /> : <PictureAsPdfIcon />}
|
||||||
|
disabled={saving || pdfCreating}
|
||||||
|
onClick={onPdfCreate}
|
||||||
|
title="PDF erstellen"
|
||||||
|
sx={{
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
height: 40,
|
||||||
|
width: '100%',
|
||||||
|
px: 1.5,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pdfCreating ? 'Wird erstellt…' : 'PDF erstellen'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{onPdfUpload ? (
|
{onPdfUpload ? (
|
||||||
<FileDropzone
|
<FileDropzone
|
||||||
accept="application/pdf,.pdf,text/plain,.txt,text/csv,.csv"
|
accept="application/pdf,.pdf,text/plain,.txt,text/csv,.csv"
|
||||||
|
|||||||
Reference in New Issue
Block a user