feat(schema): rename manifestarticle customname to name, add pu, and use taxrate

This commit is contained in:
seb
2026-09-16 10:05:24 +02:00
parent 63f83b6822
commit 7b3d2141fa
7 changed files with 1566 additions and 1550 deletions

View File

@@ -328,18 +328,20 @@ type DeliverySummary = {
type DeliveryLine = {
id: number,
sku: string | null,
customname: string | null,
name: string | null,
pu: number,
amount: number,
net: number,
rebate: number,
tax: number, // tax.rate
tax: number, // manifestarticle.taxrate (also exposed as taxrate)
taxrate: number,
comment: string | null,
}
type Delivery = DeliverySummary & { lines: DeliveryLine[] }
```
`create` inserts header + lines in one transaction. The `number` is auto-allocated via `getnextnumberwithprefixandpadding` (`numbers` type `delivery`); supply `externalNumber` for an external (supplier) reference. `supplierId` null or a Lieferanten `entity` of the company (else `Supplier not found`). Each line requires either `sku` or `customname`. For delivery manifests, `sku` points to `articlesupplier.sku`; for quote and order manifests, `sku` points to `article.sku`. Lines are stored without FKs so manifest articles are preserved even if articles or suppliers are deleted. `amount`, `net`, `tax` (rate → `tax.ensure`); `rebate` defaults `0`. Empty/invalid `date` → `Invalid delivery`; bad line payload → `Invalid lines`.
`create` inserts header + lines in one transaction. The `number` is auto-allocated via `getnextnumberwithprefixandpadding` (`numbers` type `delivery`); supply `externalNumber` for an external (supplier) reference. `supplierId` null or a Lieferanten `entity` of the company (else `Supplier not found`). Each line requires either `sku` or `name`. For delivery manifests, `sku` points to `articlesupplier.sku`; for quote and order manifests, `sku` points to `article.sku`. Lines are stored without FKs so manifest articles are preserved even if articles or suppliers are deleted. `amount`, `net`, `tax` (rate → `tax.ensure`); `rebate` defaults `0`. Empty/invalid `date` → `Invalid delivery`; bad line payload → `Invalid lines`.
`update` patches header fields that are present. If `lines` is present, replace-all lines; if omitted, leave lines unchanged. Returns full `Delivery`.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

@@ -573,14 +573,14 @@ BEGIN
id INT IDENTITY(1,1) PRIMARY KEY,
manifest_id INT NOT NULL,
sku NVARCHAR(100) NULL,
customname NVARCHAR(255) NULL,
name NVARCHAR(255) NULL,
pu FLOAT NOT NULL CONSTRAINT DF_manifestarticle_pu DEFAULT 1,
amount FLOAT NOT NULL,
net FLOAT NOT NULL,
comment NVARCHAR(MAX) NULL,
rebate FLOAT NOT NULL CONSTRAINT DF_manifestarticle_rebate DEFAULT 0,
tax_id INT NOT NULL,
CONSTRAINT FK_manifestarticle_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE,
CONSTRAINT FK_manifestarticle_tax FOREIGN KEY (tax_id) REFERENCES tax(id) ON DELETE CASCADE
taxrate DECIMAL(18,4) NOT NULL CONSTRAINT DF_manifestarticle_taxrate DEFAULT 0,
CONSTRAINT FK_manifestarticle_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE
);
END
`;

View File

@@ -32,14 +32,15 @@ export const GET_DELIVERY_LINES = `
SELECT
ma.id,
ma.sku,
ma.customname,
ma.name,
ma.pu,
ma.amount,
ma.net,
ma.rebate,
t.rate AS tax,
ma.taxrate AS tax,
ma.taxrate,
ma.comment
FROM manifestarticle ma
INNER JOIN tax t ON t.id = ma.tax_id
WHERE ma.manifest_id = @manifestId
ORDER BY ma.id
`;
@@ -78,22 +79,24 @@ export const CREATE_LINE = `
INSERT INTO manifestarticle (
manifest_id,
sku,
customname,
name,
pu,
amount,
net,
comment,
rebate,
tax_id
taxrate
)
OUTPUT INSERTED.id
VALUES (
@manifestId,
@sku,
@customname,
@name,
@pu,
@amount,
@net,
@comment,
@rebate,
@taxId
@taxrate
)
`;

View File

@@ -38,14 +38,15 @@ export const GET_MANIFEST_LINES = `
SELECT
ma.id,
ma.sku,
ma.customname,
ma.name,
ma.pu,
ma.amount,
ma.net,
ma.rebate,
t.rate AS tax,
ma.taxrate AS tax,
ma.taxrate,
ma.comment
FROM manifestarticle ma
INNER JOIN tax t ON t.id = ma.tax_id
WHERE ma.manifest_id = @manifestId
ORDER BY ma.id
`;
@@ -84,22 +85,24 @@ export const CREATE_LINE = `
INSERT INTO manifestarticle (
manifest_id,
sku,
customname,
name,
pu,
amount,
net,
comment,
rebate,
tax_id
taxrate
)
OUTPUT INSERTED.id
VALUES (
@manifestId,
@sku,
@customname,
@name,
@pu,
@amount,
@net,
@comment,
@rebate,
@taxId
@taxrate
)
`;

View File

@@ -5,7 +5,7 @@ import request from './request.js';
* Normalizes and validates an array of manifest line inputs.
*
* @param {Array<object>} lines - The raw line inputs.
* @returns {Array<{sku: string|null, customname: string|null, amount: number, net: number, rebate: number, tax: number, comment: string|null}>} The normalized lines.
* @returns {Array<{sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, tax: number, comment: string|null}>} The normalized lines.
* @throws {Error} If the lines are invalid.
*/
export function normalizeLines(lines) {
@@ -15,25 +15,35 @@ export function normalizeLines(lines) {
return lines.map((entry) => {
const sku = entry?.sku?.trim?.() ? entry.sku.trim() : null;
const customname =
entry?.customname?.trim?.() ? entry.customname.trim() : null;
const name =
entry?.name?.trim?.()
? entry.name.trim()
: entry?.customname?.trim?.()
? entry.customname.trim()
: null;
// Must have either an SKU or a custom name.
if (!sku && !customname) {
// Must have either an SKU or a name.
if (!sku && !name) {
throw new Error('Invalid lines');
}
const pu =
entry?.pu === undefined || entry?.pu === null ? 1 : entry.pu;
const amount = entry?.amount;
const net = entry?.net;
const rebate =
entry?.rebate === undefined || entry?.rebate === null ? 0 : entry.rebate;
const tax = entry?.tax;
const tax =
entry?.taxrate !== undefined && entry?.taxrate !== null
? entry.taxrate
: entry?.tax;
const comment =
entry?.comment === undefined || entry?.comment === null
? null
: String(entry.comment);
if (
!Number.isFinite(pu) ||
!Number.isFinite(amount) ||
!Number.isFinite(net) ||
!Number.isFinite(rebate) ||
@@ -42,7 +52,7 @@ export function normalizeLines(lines) {
throw new Error('Invalid lines');
}
return { sku, customname, amount, net, rebate, tax, comment };
return { sku, name, pu, amount, net, rebate, tax, comment };
});
}
@@ -72,28 +82,31 @@ export function parseDate(value) {
* @returns {object} The shaped line.
*/
export function shapeLine(row) {
const tax = row.taxrate !== undefined ? Number(row.taxrate) : Number(row.tax);
return {
id: row.id,
sku: row.sku ?? null,
customname: row.customname ?? null,
name: row.name ?? null,
pu: row.pu !== undefined && row.pu !== null ? Number(row.pu) : 1,
amount: row.amount,
net: row.net,
rebate: row.rebate,
tax: row.tax,
tax,
taxrate: tax,
comment: row.comment ?? null,
};
}
/**
* Inserts manifest lines for a document, storing each SKU directly
* and ensuring the tax rate.
* Inserts manifest lines for a document, storing each SKU and taxrate directly,
* and ensuring the tax rate exists in the tax catalog.
*
* @param {object} pool - The mssql connection pool.
* @param {object} tx - The transaction.
* @param {object} params - The insert parameters.
* @param {number} params.manifestId - The manifest id.
* @param {Array<object>} params.lines - The normalized lines.
* @param {Function} params.ensureTax - The tax ensure function.
* @param {Function} [params.ensureTax] - The tax ensure function.
* @returns {Promise<void>}
*/
export async function insertLines(
@@ -102,17 +115,20 @@ export async function insertLines(
{ manifestId, lines, ensureTax },
) {
for (const line of lines) {
const taxId = await ensureTax(line.tax, tx);
if (ensureTax) {
await ensureTax(line.tax, tx);
}
await request(pool, tx)
.input('manifestId', manifestId)
.input('sku', line.sku ?? null)
.input('customname', line.customname ?? null)
.input('name', line.name ?? null)
.input('pu', line.pu ?? 1)
.input('amount', line.amount)
.input('net', line.net)
.input('comment', line.comment)
.input('rebate', line.rebate)
.input('taxId', taxId)
.input('taxrate', line.tax)
.query(CREATE_LINE);
}
}