feat(manifest): improve PDF extraction workflow and fix delivery lines rendering

- Fix SSE 'result' event handling in delivery creation and stop circular progress
- Directly populate deliveryEditorSlice with parsed header, line items, and notes
- Simplify ManifestPdfDialog to display LLM protocol log and status only
- Render PDF analysis notes at the bottom of the delivery page
- Keep article lines visible when imported items exist even if partyId is not yet selected
- Bind handleEdit and handleDelete in ManifestList constructor to fix row click TypeError
- Support delivery columns (name, pu, netprice, taxrate) in ManifestForm and lineFromApi
- Remove obsolete Unit column from delivery lines
This commit is contained in:
seb
2026-09-16 10:06:19 +02:00
parent 7135cdb5fe
commit 0130ee6516
10 changed files with 665 additions and 128 deletions

View File

@@ -69,17 +69,27 @@ export function emptyLine() {
tax: '',
rebate: '',
net: '',
name: '',
pu: '',
unit: '',
netprice: '',
taxrate: '',
};
}
export function lineFromApi(row) {
return {
key: `line-${row.id}`,
key: `line-${row.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`}`,
amount: row.amount == null ? '' : String(row.amount),
sku: row.sku ?? '',
tax: row.tax == null ? '' : String(row.tax),
rebate: row.rebate == null ? '' : String(row.rebate),
net: row.net == null ? '' : String(row.net),
name: row.name ?? '',
pu: row.pu == null ? '' : String(row.pu),
unit: row.unit ?? '',
netprice: row.netprice != null ? String(row.netprice) : row.net != null ? String(row.net) : '',
taxrate: row.taxrate != null ? String(row.taxrate) : row.tax != null ? String(row.tax) : '',
};
}
@@ -95,8 +105,8 @@ export function buildLines(lines, { requireAtLeastOne }) {
for (const line of lines) {
const sku = String(line.sku ?? '').trim();
const amount = parseNum(line.amount);
const net = parseNum(line.net);
const tax = parseNum(line.tax);
const net = parseNum(line.net ?? line.netprice);
const tax = parseNum(line.tax ?? line.taxrate);
const rebate = parseNum(line.rebate);
const blank =
!sku && amount == null && net == null && tax == null && rebate == null;
@@ -106,6 +116,9 @@ export function buildLines(lines, { requireAtLeastOne }) {
}
const row = { sku, amount, net, tax };
if (rebate != null) row.rebate = rebate;
if (line.name) row.name = line.name;
if (line.pu != null) row.pu = line.pu;
if (line.unit) row.unit = line.unit;
outLines.push(row);
}
if (requireAtLeastOne && outLines.length === 0) {
@@ -157,6 +170,15 @@ export const LINE_COLUMNS = [
{ id: 'net', label: 'Net', width: 104, align: 'right', type: 'number' },
];
export const DELIVERY_LINE_COLUMNS = [
{ id: 'sku', label: 'SKU', width: 130, align: 'left', type: 'text' },
{ id: 'name', label: 'Name', width: 220, align: 'left', type: 'text' },
{ id: 'amount', label: 'Amount', width: 88, align: 'right', type: 'number' },
{ id: 'pu', label: 'PU', width: 80, align: 'right', type: 'number' },
{ id: 'taxrate', label: 'Tax', width: 80, align: 'right', type: 'number' },
{ id: 'netprice', label: 'Net', width: 104, align: 'right', type: 'number' },
];
/** @typedef {'delivery'|'quote'|'order'} ManifestKind */
/**
@@ -220,6 +242,7 @@ export function manifestConfig(kind) {
// Delivery uses a server-allocated read-only number plus an optional
// caller-supplied external reference.
externalNumber: true,
columns: DELIVERY_LINE_COLUMNS,
};
}
if (kind === 'quote') {
@@ -253,6 +276,7 @@ export function manifestConfig(kind) {
listPromise: (ctx) => ctx.quotes ?? ctx.quotePromise,
partiesByIdFromList: (parties) =>
Object.fromEntries(parties.map((row) => [row.id, row])),
columns: LINE_COLUMNS,
};
}
if (kind === 'order') {
@@ -286,6 +310,7 @@ export function manifestConfig(kind) {
listPromise: (ctx) => ctx.orders ?? ctx.orderPromise,
partiesByIdFromList: (parties) =>
Object.fromEntries(parties.map((row) => [row.id, row])),
columns: LINE_COLUMNS,
};
}
throw new Error(`Unknown manifest kind: ${kind}`);