u
This commit is contained in:
@@ -62,6 +62,14 @@ import { article as articleApi, supplier as supplierApi } from '../api';
|
|||||||
* @property {string|null} unmatchedSupplierName Parsed supplier name without a matching entity.
|
* @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.
|
* 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.
|
* @returns {DeliveryLine} Normalized line.
|
||||||
*/
|
*/
|
||||||
export function createEmptyLineitem(line = {}) {
|
export function createEmptyLineitem(line = {}) {
|
||||||
const netVal = line.netprice ?? line.net ?? null;
|
const netRaw = line.netprice ?? line.net ?? line.eknetto ?? line.netPrice ?? null;
|
||||||
const taxVal = line.taxrate ?? line.tax ?? 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 {
|
return {
|
||||||
key: line.key ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
key: line.key ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
name: line.name ?? null,
|
name: line.name ?? line.articleName ?? line.title ?? null,
|
||||||
sku: line.sku ?? null,
|
sku: line.sku ?? line.liefArtNr ?? line.articleNumber ?? null,
|
||||||
amount: line.amount ?? null,
|
amount: amountVal,
|
||||||
pu: line.pu ?? null,
|
pu: puVal,
|
||||||
unit: line.unit ?? null,
|
unit: line.unit ?? null,
|
||||||
netprice: netVal,
|
netprice: netVal,
|
||||||
net: 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} */
|
/** Serializes the delivery into a VFS draft manifest format. @returns {object} */
|
||||||
getManifest() {
|
getManifest() {
|
||||||
const state = automat.state;
|
const state = automat.state;
|
||||||
@@ -724,6 +816,7 @@ export const {
|
|||||||
setId,
|
setId,
|
||||||
setDeliveryEntities,
|
setDeliveryEntities,
|
||||||
setDelivery,
|
setDelivery,
|
||||||
|
loadDraftManifest,
|
||||||
setDate,
|
setDate,
|
||||||
setDeliveryEntity,
|
setDeliveryEntity,
|
||||||
setPartyId,
|
setPartyId,
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import LensBlurIcon from '@mui/icons-material/LensBlur';
|
|||||||
import CalculateIcon from '@mui/icons-material/Calculate';
|
import CalculateIcon from '@mui/icons-material/Calculate';
|
||||||
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
|
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
|
||||||
import DirectionsIcon from '@mui/icons-material/Directions';
|
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 HealingMarkdown from './HealingMarkdown';
|
||||||
import {
|
import {
|
||||||
VfsAttachmentLink,
|
VfsAttachmentLink,
|
||||||
@@ -95,6 +99,8 @@ const TOOL_ICON_MAP = {
|
|||||||
ean_checksum: QrCodeScannerIcon,
|
ean_checksum: QrCodeScannerIcon,
|
||||||
'set-user-route': DirectionsIcon,
|
'set-user-route': DirectionsIcon,
|
||||||
set_user_route: DirectionsIcon,
|
set_user_route: DirectionsIcon,
|
||||||
|
draft_delivery_manifest: EditNoteIcon,
|
||||||
|
'draft-delivery-manifest': EditNoteIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatMessageTime(isoString) {
|
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 (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant={applied ? 'contained' : 'outlined'}
|
||||||
|
color={applied ? 'success' : 'primary'}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
sx={{
|
||||||
|
height: 20,
|
||||||
|
minHeight: 20,
|
||||||
|
py: 0,
|
||||||
|
px: 0.9,
|
||||||
|
fontSize: '0.72rem',
|
||||||
|
fontWeight: 700,
|
||||||
|
textTransform: 'none',
|
||||||
|
lineHeight: 1,
|
||||||
|
borderRadius: '4px',
|
||||||
|
borderColor: applied ? undefined : 'rgba(0, 0, 0, 0.23)',
|
||||||
|
bgcolor: applied ? 'success.main' : 'transparent',
|
||||||
|
color: applied ? '#ffffff' : 'inherit',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: applied ? 'success.dark' : 'action.hover',
|
||||||
|
borderColor: applied ? undefined : 'primary.main',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{applied ? '✓ Use' : 'Use'}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function WorkIconView({ tool }) {
|
function WorkIconView({ tool }) {
|
||||||
if (!tool || !tool.name) return null;
|
if (!tool || !tool.name) return null;
|
||||||
const isGreen = tool?.state === 'green';
|
const isGreen = tool?.state === 'green';
|
||||||
const isUrlGet = tool?.name === 'url_get';
|
const isUrlGet = tool?.name === 'url_get';
|
||||||
|
const isDraftDelivery = isDraftDeliveryManifestTool(tool?.name);
|
||||||
const ToolNameIcon = tool?.name ? TOOL_ICON_MAP[tool.name] : null;
|
const ToolNameIcon = tool?.name ? TOOL_ICON_MAP[tool.name] : null;
|
||||||
const prefix = ToolNameIcon ? tool?.name : null;
|
const prefix = ToolNameIcon ? tool?.name : null;
|
||||||
const tooltipText = formatToolTooltip(tool?.content, tool?.result, tool?.parameters || tool?.arguments, prefix);
|
const tooltipText = formatToolTooltip(tool?.content, tool?.result, tool?.parameters || tool?.arguments, prefix);
|
||||||
const targetUrl = isUrlGet ? extractUrlFromTool(tool) : null;
|
const targetUrl = isUrlGet ? extractUrlFromTool(tool) : null;
|
||||||
|
|
||||||
return (
|
const iconElement = (
|
||||||
<ClickTooltip
|
<ClickTooltip
|
||||||
title={
|
title={
|
||||||
<Box
|
<Box
|
||||||
@@ -554,6 +689,17 @@ function WorkIconView({ tool }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</ClickTooltip>
|
</ClickTooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isDraftDelivery) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
|
{iconElement}
|
||||||
|
<DraftDeliveryManifestUseButton tool={tool} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return iconElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function UsageChipView({ usage }) {
|
function UsageChipView({ usage }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user