diff --git a/src/automats/deliveryEditorAutomat.js b/src/automats/deliveryEditorAutomat.js
index ec6aae2..12fb723 100644
--- a/src/automats/deliveryEditorAutomat.js
+++ b/src/automats/deliveryEditorAutomat.js
@@ -62,6 +62,14 @@ import { article as articleApi, supplier as supplierApi } from '../api';
* @property {string|null} unmatchedSupplierName Parsed supplier name without a matching entity.
*/
+function parseNum(value) {
+ if (value == null || value === '') return null;
+ const cleaned = String(value).trim().replace('%', '').trim().replace(',', '.');
+ if (cleaned === '') return null;
+ const n = Number(cleaned);
+ return Number.isFinite(n) ? n : null;
+}
+
/**
* Normalizes a partial line into an editor line with a stable key.
*
@@ -69,14 +77,22 @@ import { article as articleApi, supplier as supplierApi } from '../api';
* @returns {DeliveryLine} Normalized line.
*/
export function createEmptyLineitem(line = {}) {
- const netVal = line.netprice ?? line.net ?? null;
- const taxVal = line.taxrate ?? line.tax ?? null;
+ const netRaw = line.netprice ?? line.net ?? line.eknetto ?? line.netPrice ?? 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;
+
return {
key: line.key ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
- name: line.name ?? null,
- sku: line.sku ?? null,
- amount: line.amount ?? null,
- pu: line.pu ?? null,
+ name: line.name ?? line.articleName ?? line.title ?? null,
+ sku: line.sku ?? line.liefArtNr ?? line.articleNumber ?? null,
+ amount: amountVal,
+ pu: puVal,
unit: line.unit ?? null,
netprice: netVal,
net: netVal,
@@ -657,6 +673,82 @@ export function createDeliveryEditorAutomat(options = {}) {
});
},
+ /**
+ * Loads a delivery manifest (e.g. from draft_delivery_manifest tool) into the editor.
+ * @param {Object} manifestData
+ * @returns {void}
+ */
+ loadDraftManifest(manifestData) {
+ if (!manifestData || typeof manifestData !== 'object') return;
+ const data = manifestData.manifest ?? manifestData;
+ const basedata = data.basedata ?? data.baseData ?? data;
+ const rawItems =
+ data.items?.items ??
+ data.items ??
+ data.lines ??
+ data.lineitems ??
+ [];
+ const itemsList = Array.isArray(rawItems) ? rawItems : [];
+
+ const partyId =
+ basedata.lieferant_id ??
+ basedata.lieferantId ??
+ basedata.partyId ??
+ basedata.deliveryEntity ??
+ basedata.supplier_id ??
+ basedata.supplierId ??
+ null;
+ const supplierName =
+ basedata.lieferantName ??
+ basedata.supplierName ??
+ basedata.unmatchedSupplierName ??
+ null;
+ const extNum =
+ basedata.manifestNr ??
+ basedata.manifestNumber ??
+ basedata.externalNumber ??
+ basedata.external_number ??
+ basedata.invoiceNumber ??
+ basedata.invoice_number ??
+ null;
+ const date = basedata.date ?? null;
+ const type = basedata.manifestType ?? basedata.type ?? 'rechnung';
+
+ let matchedPartyId = partyId != null && partyId !== '' ? partyId : null;
+ if (!matchedPartyId && supplierName && automat.state.deliveryEntities?.length > 0) {
+ const normName = String(supplierName).trim().toLowerCase();
+ const found = automat.state.deliveryEntities.find(
+ (e) =>
+ (e.name && String(e.name).trim().toLowerCase() === normName) ||
+ (e.company && String(e.company).trim().toLowerCase() === normName)
+ );
+ if (found?.id != null) {
+ matchedPartyId = found.id;
+ }
+ }
+
+ const lines = itemsList.map((item, idx) =>
+ createEmptyLineitem({
+ ...item,
+ key: item.key ?? `manifest-${Date.now()}-${idx}-${Math.random().toString(36).slice(2, 6)}`,
+ })
+ );
+
+ automat.actions.setDelivery({
+ id: null,
+ deliveryId: null,
+ date,
+ partyId: matchedPartyId,
+ deliveryEntity: matchedPartyId,
+ unmatchedSupplierName: matchedPartyId != null ? null : supplierName,
+ externalNumber: extNum,
+ external_number: extNum,
+ type,
+ lines,
+ lineitems: lines,
+ });
+ },
+
/** Serializes the delivery into a VFS draft manifest format. @returns {object} */
getManifest() {
const state = automat.state;
@@ -724,6 +816,7 @@ export const {
setId,
setDeliveryEntities,
setDelivery,
+ loadDraftManifest,
setDate,
setDeliveryEntity,
setPartyId,
diff --git a/src/components/chat/ChatFlowList.jsx b/src/components/chat/ChatFlowList.jsx
index 9d18c12..a4d4a3e 100644
--- a/src/components/chat/ChatFlowList.jsx
+++ b/src/components/chat/ChatFlowList.jsx
@@ -26,6 +26,10 @@ import LensBlurIcon from '@mui/icons-material/LensBlur';
import CalculateIcon from '@mui/icons-material/Calculate';
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
import DirectionsIcon from '@mui/icons-material/Directions';
+import EditNoteIcon from '@mui/icons-material/EditNote';
+import Button from '@mui/material/Button';
+import { enqueueSnackbar } from 'notistack';
+import { deliveryEditorAutomat, setLastDeliveryPath } from '../../automats';
import HealingMarkdown from './HealingMarkdown';
import {
VfsAttachmentLink,
@@ -95,6 +99,8 @@ const TOOL_ICON_MAP = {
ean_checksum: QrCodeScannerIcon,
'set-user-route': DirectionsIcon,
set_user_route: DirectionsIcon,
+ draft_delivery_manifest: EditNoteIcon,
+ 'draft-delivery-manifest': EditNoteIcon,
};
function formatMessageTime(isoString) {
@@ -486,16 +492,145 @@ class UrlFaviconIcon extends PureComponent {
}
}
+export function isDraftDeliveryManifestTool(name) {
+ if (!name || typeof name !== 'string') return false;
+ const n = name.trim().toLowerCase();
+ return n === 'draft_delivery_manifest' || n === 'draft-delivery-manifest';
+}
+
+function tryExtractObject(val) {
+ if (!val) return null;
+ if (typeof val === 'object') return val;
+ if (typeof val === 'string') {
+ const trimmed = val.trim();
+ try {
+ return JSON.parse(trimmed);
+ } catch {}
+ const match = trimmed.match(/\{[\s\S]*\}/);
+ if (match) {
+ try {
+ return JSON.parse(match[0]);
+ } catch {}
+ }
+ }
+ return null;
+}
+
+export function parseDraftDeliveryManifest(tool) {
+ if (!tool) return null;
+ const candidates = [tool.parameters, tool.arguments, tool.content, tool.result, tool];
+ for (const c of candidates) {
+ const obj = tryExtractObject(c);
+ if (obj && typeof obj === 'object') {
+ if (obj.manifest && typeof obj.manifest === 'object') {
+ return obj.manifest;
+ }
+ if (obj.basedata || obj.baseData || obj.items || obj.lines || obj.lineitems) {
+ return obj;
+ }
+ }
+ }
+ return null;
+}
+
+class DraftDeliveryManifestUseButton extends PureComponent {
+ state = {
+ applied: false,
+ };
+
+ handleClick = async (e) => {
+ e?.stopPropagation();
+ const { tool } = this.props;
+ const manifest = parseDraftDeliveryManifest(tool);
+ if (!manifest) {
+ enqueueSnackbar('Keine gültigen Lieferscheindaten gefunden', { variant: 'warning' });
+ return;
+ }
+
+ deliveryEditorAutomat.actions.loadDraftManifest(manifest);
+ setLastDeliveryPath('/lieferung/neu');
+
+ this.setState({ applied: true });
+ setTimeout(() => {
+ this.setState({ applied: false });
+ }, 2500);
+
+ const count =
+ manifest.items?.numberOfItemsToAdd ??
+ (Array.isArray(manifest.items?.items)
+ ? manifest.items.items.length
+ : Array.isArray(manifest.items)
+ ? manifest.items.length
+ : Array.isArray(manifest.lines)
+ ? manifest.lines.length
+ : 0);
+
+ enqueueSnackbar(
+ count > 0
+ ? `${count} Positionen in den Lieferschein-Editor geladen`
+ : 'Lieferschein in den Editor geladen',
+ { variant: 'success' }
+ );
+
+ try {
+ const { router } = await import('../../appRouter');
+ if (router?.navigate) {
+ const curPath =
+ router.state?.location?.pathname ||
+ (typeof window !== 'undefined' ? window.location.pathname : '');
+ if (!curPath.startsWith('/lieferung/neu')) {
+ void router.navigate('/lieferung/neu');
+ }
+ }
+ } catch (err) {
+ console.warn('[DraftDeliveryManifestUseButton] Navigation failed:', err);
+ }
+ };
+
+ render() {
+ const { applied } = this.state;
+ return (
+
+ );
+ }
+}
+
function WorkIconView({ tool }) {
if (!tool || !tool.name) return null;
const isGreen = tool?.state === 'green';
const isUrlGet = tool?.name === 'url_get';
+ const isDraftDelivery = isDraftDeliveryManifestTool(tool?.name);
const ToolNameIcon = tool?.name ? TOOL_ICON_MAP[tool.name] : null;
const prefix = ToolNameIcon ? tool?.name : null;
const tooltipText = formatToolTooltip(tool?.content, tool?.result, tool?.parameters || tool?.arguments, prefix);
const targetUrl = isUrlGet ? extractUrlFromTool(tool) : null;
- return (
+ const iconElement = (
);
+
+ if (isDraftDelivery) {
+ return (
+
+ {iconElement}
+
+
+ );
+ }
+
+ return iconElement;
}
function UsageChipView({ usage }) {