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 = { type DeliveryLine = {
id: number, id: number,
sku: string | null, sku: string | null,
customname: string | null, name: string | null,
pu: number,
amount: number, amount: number,
net: number, net: number,
rebate: number, rebate: number,
tax: number, // tax.rate tax: number, // manifestarticle.taxrate (also exposed as taxrate)
taxrate: number,
comment: string | null, comment: string | null,
} }
type Delivery = DeliverySummary & { lines: DeliveryLine[] } 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`. `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, id INT IDENTITY(1,1) PRIMARY KEY,
manifest_id INT NOT NULL, manifest_id INT NOT NULL,
sku NVARCHAR(100) 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, amount FLOAT NOT NULL,
net FLOAT NOT NULL, net FLOAT NOT NULL,
comment NVARCHAR(MAX) NULL, comment NVARCHAR(MAX) NULL,
rebate FLOAT NOT NULL CONSTRAINT DF_manifestarticle_rebate DEFAULT 0, rebate FLOAT NOT NULL CONSTRAINT DF_manifestarticle_rebate DEFAULT 0,
tax_id INT NOT NULL, 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, 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
); );
END END
`; `;

View File

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

View File

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

View File

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