This commit is contained in:
seb
2026-09-25 07:27:37 +02:00
parent b331f8f535
commit a3db1742e7
17 changed files with 2273 additions and 371 deletions

View File

@@ -80,44 +80,101 @@ export function emptyLine() {
}
export function lineFromApi(row) {
const netVal = row.netprice != null ? String(row.netprice) : row.net != null ? String(row.net) : '';
const taxVal = row.taxrate != null ? String(row.taxrate) : row.tax != null ? String(row.tax) : '';
return {
id: 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),
tax: taxVal,
rebate: row.rebate == null ? '' : String(row.rebate),
net: row.net == null ? '' : String(row.net),
net: netVal,
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) : '',
netprice: netVal,
taxrate: taxVal,
};
}
function parseNum(value) {
export function parseNum(value) {
if (value == null || value === '') return null;
const n = Number(String(value).trim().replace(',', '.'));
const cleaned = String(value).trim().replace('%', '').trim().replace(',', '.');
if (cleaned === '') return null;
const n = Number(cleaned);
return Number.isFinite(n) ? n : null;
}
export function getFirstVal(...vals) {
for (const v of vals) {
if (v != null && String(v).trim() !== '') return v;
}
return null;
}
export function hasArticleLines(lines) {
if (!Array.isArray(lines) || lines.length === 0) return false;
return lines.some((l) => {
if (!l) return false;
const sku = String(l.sku ?? '').trim();
const name = String(l.name ?? '').trim();
const amount = String(l.amount ?? '').trim();
const net = String(l.net ?? l.netprice ?? '').trim();
return Boolean(sku || name || amount || net);
});
}
/** Build create/update line array. Throws Error on client validation. */
export function buildLines(lines, { requireAtLeastOne }) {
const outLines = [];
for (const line of lines) {
const errors = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const sku = String(line.sku ?? '').trim();
const name = String(line.name ?? '').trim();
const amount = parseNum(line.amount);
const net = parseNum(line.net ?? line.netprice);
const tax = parseNum(line.tax ?? line.taxrate);
const rebate = parseNum(line.rebate);
const pu = parseNum(line.pu);
const rawAmount = getFirstVal(line.amount, line.count);
const amount = parseNum(rawAmount);
const rawNet = getFirstVal(line.net, line.netprice, line.netPrice, line.eknetto);
const net = parseNum(rawNet);
const rawTax = getFirstVal(line.tax, line.taxrate, line.taxRate, line.mwst);
const tax = parseNum(rawTax);
const rebate = parseNum(getFirstVal(line.rebate));
const rawPu = getFirstVal(line.pu, line.vpe);
const pu = parseNum(rawPu);
const blank =
!sku && !name && amount == null && net == null && tax == null && rebate == null;
if (blank) continue;
if ((!sku && !name) || amount == null || net == null || tax == null) {
throw new Error('Jede Zeile braucht SKU oder Name, Amount, Net und Taxrate.');
const missing = [];
if (!sku && !name) {
missing.push('Artikel (SKU oder Name)');
}
if (amount == null) {
missing.push(rawAmount != null ? 'gültige Menge' : 'Menge');
}
if ('pu' in line && pu == null) {
missing.push(rawPu != null ? 'gültige VPE (PU)' : 'VPE (PU)');
}
if (net == null) {
missing.push(rawNet != null ? 'gültiger Nettopreis' : 'Nettopreis');
}
if (tax == null) {
missing.push(rawTax != null ? 'gültiger Steuersatz' : 'Steuersatz');
}
if (missing.length > 0) {
const identifier = name || sku;
const rowLabel = `Zeile ${i + 1}${identifier ? ` („${identifier}“)` : ''}`;
const fieldsText =
missing.length === 1
? `${missing[0]} fehlt`
: `${missing.slice(0, -1).join(', ')} und ${missing[missing.length - 1]} fehlen`;
errors.push(`${rowLabel}: ${fieldsText}.`);
continue;
}
const row = { amount, net, taxrate: tax };
if (sku) row.sku = sku;
if (name) row.name = name;
@@ -125,6 +182,18 @@ export function buildLines(lines, { requireAtLeastOne }) {
if (pu != null) row.pu = pu;
outLines.push(row);
}
if (errors.length > 0) {
if (errors.length <= 3) {
throw new Error(errors.join(' '));
}
const visibleErrors = errors.slice(0, 3).join(' ');
const remainingCount = errors.length - 3;
throw new Error(
`${visibleErrors} (und ${remainingCount} weitere fehlerhafte Zeile${remainingCount === 1 ? '' : 'n'})`,
);
}
if (requireAtLeastOne && outLines.length === 0) {
throw new Error('Mindestens eine Artikelzeile nötig.');
}
@@ -480,14 +549,14 @@ export function manifestConfig(kind) {
deletedMsg: 'Lieferung gelöscht.',
confirmDelete: (number) => `Lieferung „${number}“ wirklich löschen?`,
invalidId: 'Ungültige Lieferungs-ID.',
partyGateMessage: 'Lieferant wählen, um Artikel zu erfassen.',
partyGateMessage: null,
getPathLabel: 'GET /api/delivery/:id',
postPathLabel: 'POST /api/delivery',
patchPathLabel: 'PATCH /api/delivery/:id',
listColor: '#fed7aa',
headerColor: '#fed7aa',
linesColor: '#bbf7d0',
requirePartyBeforeLines: true,
requirePartyBeforeLines: false,
updateCache: 'updateDelivery',
listPromise: (ctx) => ctx.deliveries ?? ctx.deliveryPromise,
partiesByIdFromList: (parties) =>