u
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { PureComponent, createRef } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
@@ -7,16 +7,18 @@ import { PanelTileGrid } from 'dashgui/panels';
|
||||
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';
|
||||
import { notifyError, notifyErrorMessage } from '../../utils/notify';
|
||||
import withNavigate from '../withNavigate';
|
||||
import ManifestHeader from './ManifestHeader';
|
||||
import ManifestLines from './ManifestLines';
|
||||
import ManifestPdfDialog from './ManifestPdfDialog';
|
||||
import ArticleSearchDialog from './ArticleSearchDialog';
|
||||
import OrderDocumentsTiles from './OrderDocumentsTiles';
|
||||
import OrderShipmentDialog from './OrderShipmentDialog';
|
||||
import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
|
||||
import entityAutomat from '../../automats/entityAutomat';
|
||||
import supplierAutomat from '../../automats/supplierAutomat';
|
||||
import orderAutomat from '../../automats/orderAutomat';
|
||||
import companyAutomat, { selectCompanyTaxrate } from '../../automats/companyAutomat';
|
||||
import { netFromGross, grossFromNet, getArticleGross } from '../../utils/price';
|
||||
import {
|
||||
@@ -95,14 +97,18 @@ class ManifestForm extends PureComponent {
|
||||
dirtyLineFields: initialEdit?.dirtyLineFields || {},
|
||||
dirtySummary: initialEdit?.dirtySummary || [],
|
||||
saving: false,
|
||||
orderCreating: false,
|
||||
partyError: false,
|
||||
pdfUploading: false,
|
||||
pdfCreating: false,
|
||||
pdfDialogOpen: false,
|
||||
shipmentDialogOpen: false,
|
||||
pdfDialogData: null,
|
||||
pdfFileName: '',
|
||||
articleSearchDialogOpen: false,
|
||||
articleSearchLine: null,
|
||||
};
|
||||
this.headerRef = createRef();
|
||||
this._cancelled = false;
|
||||
this.handleRetry = this.handleRetry.bind(this);
|
||||
this.handleRestore = this.handleRestore.bind(this);
|
||||
@@ -122,11 +128,16 @@ class ManifestForm extends PureComponent {
|
||||
this.handleMoveLine = this.handleMoveLine.bind(this);
|
||||
this.handleRemoveAllLines = this.handleRemoveAllLines.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.handleCreateOrder = this.handleCreateOrder.bind(this);
|
||||
this.handleOpenCreateShipment = this.handleOpenCreateShipment.bind(this);
|
||||
this.handleCloseShipmentDialog = this.handleCloseShipmentDialog.bind(this);
|
||||
this.handleShipmentCreated = this.handleShipmentCreated.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.handleAbortParse = this.handleAbortParse.bind(this);
|
||||
this.isFullyDelivered = this.isFullyDelivered.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -457,10 +468,10 @@ class ManifestForm extends PureComponent {
|
||||
const { mode } = this.props;
|
||||
const { requirePartyBeforeLines } = this.cfg;
|
||||
if (mode === 'create' && requirePartyBeforeLines && partyId == null) {
|
||||
this.setState({ partyId: null, lines: [emptyLine()] });
|
||||
this.setState({ partyId: null, lines: [emptyLine()], partyError: false });
|
||||
return;
|
||||
}
|
||||
this.setState({ partyId });
|
||||
this.setState({ partyId, partyError: false });
|
||||
}
|
||||
|
||||
handleDateChange(date) {
|
||||
@@ -755,10 +766,46 @@ class ManifestForm extends PureComponent {
|
||||
}));
|
||||
}
|
||||
|
||||
isFullyDelivered() {
|
||||
if (this.state.delivered) return true;
|
||||
const lines = this.state.lines || [];
|
||||
const validLines = lines.filter((l) => l.sku && Number(l.amount) > 0);
|
||||
if (validLines.length === 0) return false;
|
||||
|
||||
const shippedBySku = {};
|
||||
for (const sh of this.state.shipments || []) {
|
||||
for (const sl of sh.lines || []) {
|
||||
if (sl.sku) {
|
||||
const k = String(sl.sku).trim().toLowerCase();
|
||||
shippedBySku[k] = (shippedBySku[k] || 0) + (Number(sl.amount) || 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return validLines.every((l) => {
|
||||
const k = String(l.sku).trim().toLowerCase();
|
||||
return (shippedBySku[k] || 0) >= Number(l.amount);
|
||||
});
|
||||
}
|
||||
|
||||
handleRemoveLine(key) {
|
||||
this.setState((prev) => {
|
||||
const lineToRemove = prev.lines.find((line) => line.key === key);
|
||||
if (lineToRemove && this.props.kind === 'order') {
|
||||
const rawSku = lineToRemove.sku != null ? String(lineToRemove.sku).trim().toLowerCase() : '';
|
||||
const isShipped = Boolean(
|
||||
rawSku &&
|
||||
(prev.shipments || []).some((sh) =>
|
||||
(sh.lines || []).some((sl) => sl.sku && String(sl.sku).trim().toLowerCase() === rawSku)
|
||||
)
|
||||
);
|
||||
if (isShipped) {
|
||||
enqueueSnackbar(`Artikel „${lineToRemove.sku || lineToRemove.name}“ ist bereits auf einem Lieferschein und kann nicht entfernt werden.`, { variant: 'warning' });
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const next = prev.lines.filter((line) => line.key !== key);
|
||||
return { lines: next.length > 0 ? next : [emptyLine()] };
|
||||
return { lines: next.length > 0 ? next : [emptyLine()], isDirty: true };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -776,7 +823,29 @@ class ManifestForm extends PureComponent {
|
||||
}
|
||||
|
||||
handleRemoveAllLines() {
|
||||
this.setState({ lines: [] });
|
||||
this.setState((prev) => {
|
||||
if (this.props.kind === 'order') {
|
||||
const shippedSkus = new Set();
|
||||
for (const sh of prev.shipments || []) {
|
||||
for (const sl of sh.lines || []) {
|
||||
if (sl.sku) shippedSkus.add(String(sl.sku).trim().toLowerCase());
|
||||
}
|
||||
}
|
||||
if (shippedSkus.size > 0) {
|
||||
const remaining = prev.lines.filter((l) => {
|
||||
const raw = l.sku != null ? String(l.sku).trim().toLowerCase() : '';
|
||||
return shippedSkus.has(raw);
|
||||
});
|
||||
if (remaining.length === prev.lines.length) {
|
||||
enqueueSnackbar('Alle Positionen sind bereits auf Lieferscheinen und können nicht gelöscht werden.', { variant: 'warning' });
|
||||
return null;
|
||||
}
|
||||
enqueueSnackbar('Nicht ausgelieferte Positionen wurden entfernt.', { variant: 'info' });
|
||||
return { lines: remaining.length > 0 ? remaining : [emptyLine()], isDirty: true };
|
||||
}
|
||||
}
|
||||
return { lines: [emptyLine()], isDirty: true };
|
||||
});
|
||||
}
|
||||
|
||||
async handlePdfUpload(file) {
|
||||
@@ -945,6 +1014,74 @@ class ManifestForm extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: calls quoteApi.createOrder from ./api/index.js
|
||||
// #region POST /api/quote/:id/order — createOrder
|
||||
async handleCreateOrder(e) {
|
||||
e?.preventDefault?.();
|
||||
e?.stopPropagation?.();
|
||||
const { docId, kind } = this.props;
|
||||
if (this.state.orderCreating || this.state.saving) return;
|
||||
if (docId == null || kind !== 'quote') return;
|
||||
|
||||
if (!this.state.partyId) {
|
||||
this.setState({ partyError: true });
|
||||
notifyErrorMessage(
|
||||
'Für eine Bestellung muss ein Kunde ausgewählt sein. Bitte wählen Sie zuerst einen Kunden aus.',
|
||||
);
|
||||
this.headerRef.current?.focusPartyInput?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.isDirty) {
|
||||
const saved = await this.handleSave();
|
||||
if (!saved) return;
|
||||
}
|
||||
|
||||
this.setState({ orderCreating: true });
|
||||
try {
|
||||
const res = await quoteApi.createOrder(docId, {
|
||||
entityId: this.state.partyId,
|
||||
});
|
||||
requireObject(res, 'POST /api/quote/:id/order');
|
||||
const order = requireObject(res.order, 'order');
|
||||
requireDefined(order.id, 'order.id');
|
||||
|
||||
void orderAutomat.actions.reload();
|
||||
|
||||
enqueueSnackbar(
|
||||
order.number
|
||||
? `Bestellung ${order.number} erfolgreich erstellt.`
|
||||
: 'Bestellung erfolgreich erstellt.',
|
||||
{ variant: 'success' },
|
||||
);
|
||||
|
||||
this.props.navigate(`/auftrag/bestellung/${order.id}`);
|
||||
} catch (err) {
|
||||
if (!this._cancelled) {
|
||||
notifyError(err);
|
||||
this.setState({ orderCreating: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
|
||||
async handleOpenCreateShipment() {
|
||||
if (this.state.isDirty) {
|
||||
const saved = await this.handleSave();
|
||||
if (!saved) return;
|
||||
}
|
||||
this.setState({ shipmentDialogOpen: true });
|
||||
}
|
||||
|
||||
handleCloseShipmentDialog() {
|
||||
this.setState({ shipmentDialogOpen: false });
|
||||
}
|
||||
|
||||
async handleShipmentCreated() {
|
||||
await this.loadEdit();
|
||||
void orderAutomat.actions.reload();
|
||||
}
|
||||
|
||||
async handleAttachmentDelete(attachment) {
|
||||
if (!attachment) return;
|
||||
const fileId = attachment.fileId ?? attachment.id;
|
||||
@@ -975,8 +1112,8 @@ class ManifestForm extends PureComponent {
|
||||
async handleSave() {
|
||||
const { mode, docId } = this.props;
|
||||
const cfg = this.cfg;
|
||||
if (this.state.saving) return;
|
||||
if (mode === 'edit' && docId == null) return;
|
||||
if (this.state.saving) return false;
|
||||
if (mode === 'edit' && docId == null) return false;
|
||||
|
||||
const requireAtLeastOne = mode === 'create';
|
||||
let body;
|
||||
@@ -999,7 +1136,7 @@ class ManifestForm extends PureComponent {
|
||||
);
|
||||
} catch (err) {
|
||||
notifyError(err);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setState({ saving: true });
|
||||
@@ -1012,10 +1149,10 @@ class ManifestForm extends PureComponent {
|
||||
requireDefined(data[cfg.docField].id, `${cfg.docField}.id`);
|
||||
this.draftAutomat?.actions?.reset();
|
||||
await cfg.listAutomat.actions.reload();
|
||||
if (this._cancelled) return;
|
||||
if (this._cancelled) return false;
|
||||
enqueueSnackbar(cfg.createdMsg, { variant: 'success' });
|
||||
this.props.navigate(cfg.editPath(data[cfg.docField].id));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const data = await api.update(docId, body);
|
||||
@@ -1023,7 +1160,7 @@ class ManifestForm extends PureComponent {
|
||||
requireObject(data[cfg.docField], cfg.docField);
|
||||
requireDefined(data[cfg.docField].id, `${cfg.docField}.id`);
|
||||
await cfg.listAutomat.actions.reload();
|
||||
if (this._cancelled) return;
|
||||
if (this._cancelled) return false;
|
||||
const doc = data[cfg.docField];
|
||||
const apiLines = requireArray(doc.lines, `${cfg.docField}.lines`);
|
||||
const savedDoc = {
|
||||
@@ -1051,16 +1188,18 @@ class ManifestForm extends PureComponent {
|
||||
saving: false,
|
||||
});
|
||||
enqueueSnackbar(cfg.savedMsg, { variant: 'success' });
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (this._cancelled) return;
|
||||
if (this._cancelled) return false;
|
||||
notifyError(err);
|
||||
this.setState({ saving: false });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
renderArticles() {
|
||||
const cfg = this.cfg;
|
||||
const { lines, saving, dirtyLineFields } = this.state;
|
||||
const { lines, saving, dirtyLineFields, shipments } = this.state;
|
||||
|
||||
return (
|
||||
<ManifestLines
|
||||
@@ -1071,6 +1210,7 @@ class ManifestForm extends PureComponent {
|
||||
wide={4}
|
||||
linesColor={cfg.linesColor}
|
||||
dirtyLineFields={dirtyLineFields || {}}
|
||||
shipments={shipments}
|
||||
onLineChange={this.handleLineChange}
|
||||
onSkuBlur={this.handleSkuBlur}
|
||||
onAddLine={this.handleAddLine}
|
||||
@@ -1120,6 +1260,7 @@ class ManifestForm extends PureComponent {
|
||||
<>
|
||||
<PanelTileGrid>
|
||||
<ManifestHeader
|
||||
ref={this.headerRef}
|
||||
kind={kind}
|
||||
headerColor={cfg.headerColor}
|
||||
dateLabel={cfg.dateLabel}
|
||||
@@ -1127,10 +1268,12 @@ class ManifestForm extends PureComponent {
|
||||
parties={parties}
|
||||
partiesLoading={partiesLoading || status === 'loadingParties'}
|
||||
partyId={partyId}
|
||||
partyError={this.state.partyError}
|
||||
date={date}
|
||||
number={number}
|
||||
externalNumber={externalNumber}
|
||||
saving={saving}
|
||||
orderCreating={this.state.orderCreating}
|
||||
pdfUploading={pdfUploading}
|
||||
pdfCreating={this.state.pdfCreating}
|
||||
lines={lines}
|
||||
@@ -1143,6 +1286,9 @@ class ManifestForm extends PureComponent {
|
||||
onPdfUpload={kind === 'delivery' ? this.handlePdfUpload : undefined}
|
||||
onPdfDelete={this.handleAttachmentDelete}
|
||||
onPdfCreate={kind === 'quote' || kind === 'order' ? this.handleCreatePdf : undefined}
|
||||
onCreateOrder={kind === 'quote' && this.props.mode === 'edit' ? this.handleCreateOrder : undefined}
|
||||
onOpenCreateShipment={kind === 'order' && this.props.mode === 'edit' ? this.handleOpenCreateShipment : undefined}
|
||||
deliverDisabled={kind === 'order' && this.isFullyDelivered()}
|
||||
onSave={this.handleSave}
|
||||
onPartyChange={this.handlePartyChange}
|
||||
onDateChange={this.handleDateChange}
|
||||
@@ -1163,9 +1309,12 @@ class ManifestForm extends PureComponent {
|
||||
shipments={this.state.shipments}
|
||||
payments={this.state.payments}
|
||||
attachments={this.state.attachments}
|
||||
deliverDisabled={this.isFullyDelivered()}
|
||||
onPdfCreate={this.handleCreatePdf}
|
||||
pdfCreating={this.state.pdfCreating}
|
||||
onAttachmentDelete={this.handleAttachmentDelete}
|
||||
onOpenCreateShipment={this.handleOpenCreateShipment}
|
||||
onShipmentChanged={this.handleShipmentCreated}
|
||||
navigate={this.props.navigate}
|
||||
/>
|
||||
) : null}
|
||||
@@ -1184,6 +1333,16 @@ class ManifestForm extends PureComponent {
|
||||
onClose={this.handleCloseArticleSearch}
|
||||
onAssign={this.handleAssignArticleForLine}
|
||||
/>
|
||||
<OrderShipmentDialog
|
||||
open={this.state.shipmentDialogOpen}
|
||||
onClose={this.handleCloseShipmentDialog}
|
||||
orderId={this.props.docId}
|
||||
orderNumber={number}
|
||||
externalNumber={externalNumber}
|
||||
lines={lines}
|
||||
shipments={this.state.shipments}
|
||||
onSuccess={this.handleShipmentCreated}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user