Add ledger, address/contact sort, and tighter schema-graph layout.
Replace manifestarticle.transactedamount with an append-only ledger, add sort on entity address/contact, and allow same-rank FKs with crossing-aware exits in the graph.
This commit is contained in:
@@ -178,6 +178,7 @@ BEGIN
|
||||
city NVARCHAR(255) NULL,
|
||||
county NVARCHAR(255) NULL,
|
||||
addresstype NVARCHAR(50) NOT NULL,
|
||||
sort INT NOT NULL CONSTRAINT DF_entityaddress_sort DEFAULT 0,
|
||||
CONSTRAINT FK_entityaddress_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
@@ -191,6 +192,7 @@ BEGIN
|
||||
entity_id INT NOT NULL,
|
||||
contacttype NVARCHAR(50) NOT NULL,
|
||||
contact NVARCHAR(255) NOT NULL,
|
||||
sort INT NOT NULL CONSTRAINT DF_entitycontact_sort DEFAULT 0,
|
||||
CONSTRAINT FK_entitycontact_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
@@ -520,7 +522,6 @@ BEGIN
|
||||
article_id INT NULL,
|
||||
amount FLOAT NOT NULL,
|
||||
net FLOAT NOT NULL,
|
||||
transactedamount FLOAT NOT NULL,
|
||||
comment NVARCHAR(MAX) NULL,
|
||||
rebate FLOAT NOT NULL CONSTRAINT DF_manifestarticle_rebate DEFAULT 0,
|
||||
tax_id INT NOT NULL,
|
||||
@@ -531,6 +532,21 @@ BEGIN
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_LEDGER = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'ledger')
|
||||
BEGIN
|
||||
CREATE TABLE ledger (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
manifestarticle_id INT NOT NULL,
|
||||
transaction_type NVARCHAR(100) NOT NULL,
|
||||
amount FLOAT NOT NULL,
|
||||
transaction_text NVARCHAR(MAX) NOT NULL,
|
||||
prev_hash NVARCHAR(128) NULL,
|
||||
CONSTRAINT FK_ledger_manifestarticle FOREIGN KEY (manifestarticle_id) REFERENCES manifestarticle(id) ON DELETE NO ACTION
|
||||
) WITH (LEDGER = ON (APPEND_ONLY = ON));
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_NUMBERS = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'numbers')
|
||||
BEGIN
|
||||
@@ -688,6 +704,7 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_PRICE,
|
||||
INIT_MANIFEST,
|
||||
INIT_MANIFESTARTICLE,
|
||||
INIT_LEDGER,
|
||||
INIT_NUMBERS,
|
||||
INIT_GET_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
|
||||
INIT_COMPANYUSER,
|
||||
|
||||
@@ -33,7 +33,6 @@ SELECT
|
||||
a.sku,
|
||||
ma.amount,
|
||||
ma.net,
|
||||
ma.transactedamount AS transactedAmount,
|
||||
ma.rebate,
|
||||
t.rate AS tax,
|
||||
ma.comment
|
||||
@@ -79,7 +78,6 @@ INSERT INTO manifestarticle (
|
||||
article_id,
|
||||
amount,
|
||||
net,
|
||||
transactedamount,
|
||||
comment,
|
||||
rebate,
|
||||
tax_id
|
||||
@@ -90,7 +88,6 @@ VALUES (
|
||||
@articleId,
|
||||
@amount,
|
||||
@net,
|
||||
@transactedAmount,
|
||||
@comment,
|
||||
@rebate,
|
||||
@taxId
|
||||
|
||||
@@ -8,7 +8,8 @@ export const ADDRESS_COLUMNS = `
|
||||
zip,
|
||||
city,
|
||||
county,
|
||||
addresstype AS addressType
|
||||
addresstype AS addressType,
|
||||
sort
|
||||
`;
|
||||
|
||||
export const LIST_BY_ENTITY = `
|
||||
@@ -16,7 +17,7 @@ SELECT
|
||||
${ADDRESS_COLUMNS}
|
||||
FROM entityaddress
|
||||
WHERE entity_id = @entityId
|
||||
ORDER BY id
|
||||
ORDER BY sort ASC, id ASC
|
||||
`;
|
||||
|
||||
export const GET_BY_ENTITY_AND_ID = `
|
||||
@@ -26,6 +27,12 @@ FROM entityaddress
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const GET_NEXT_SORT = `
|
||||
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
|
||||
FROM entityaddress
|
||||
WHERE entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO entityaddress (
|
||||
entity_id,
|
||||
@@ -36,7 +43,8 @@ INSERT INTO entityaddress (
|
||||
zip,
|
||||
city,
|
||||
county,
|
||||
addresstype
|
||||
addresstype,
|
||||
sort
|
||||
)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
@@ -48,7 +56,8 @@ OUTPUT
|
||||
INSERTED.zip,
|
||||
INSERTED.city,
|
||||
INSERTED.county,
|
||||
INSERTED.addresstype AS addressType
|
||||
INSERTED.addresstype AS addressType,
|
||||
INSERTED.sort
|
||||
VALUES (
|
||||
@entityId,
|
||||
@firstName,
|
||||
@@ -58,7 +67,8 @@ VALUES (
|
||||
@zip,
|
||||
@city,
|
||||
@county,
|
||||
@addressType
|
||||
@addressType,
|
||||
@sort
|
||||
)
|
||||
`;
|
||||
|
||||
@@ -72,7 +82,8 @@ SET
|
||||
zip = @zip,
|
||||
city = @city,
|
||||
county = @county,
|
||||
addresstype = @addressType
|
||||
addresstype = @addressType,
|
||||
sort = @sort
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.entity_id AS entityId,
|
||||
@@ -83,7 +94,8 @@ OUTPUT
|
||||
INSERTED.zip,
|
||||
INSERTED.city,
|
||||
INSERTED.county,
|
||||
INSERTED.addresstype AS addressType
|
||||
INSERTED.addresstype AS addressType,
|
||||
INSERTED.sort
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ export const CONTACT_COLUMNS = `
|
||||
id,
|
||||
entity_id AS entityId,
|
||||
contacttype AS contactType,
|
||||
contact
|
||||
contact,
|
||||
sort
|
||||
`;
|
||||
|
||||
export const LIST_BY_ENTITY = `
|
||||
@@ -10,7 +11,7 @@ SELECT
|
||||
${CONTACT_COLUMNS}
|
||||
FROM entitycontact
|
||||
WHERE entity_id = @entityId
|
||||
ORDER BY id
|
||||
ORDER BY sort ASC, id ASC
|
||||
`;
|
||||
|
||||
export const GET_BY_ENTITY_AND_ID = `
|
||||
@@ -20,24 +21,32 @@ FROM entitycontact
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const GET_NEXT_SORT = `
|
||||
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
|
||||
FROM entitycontact
|
||||
WHERE entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO entitycontact (entity_id, contacttype, contact)
|
||||
INSERT INTO entitycontact (entity_id, contacttype, contact, sort)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.entity_id AS entityId,
|
||||
INSERTED.contacttype AS contactType,
|
||||
INSERTED.contact
|
||||
VALUES (@entityId, @contactType, @contact)
|
||||
INSERTED.contact,
|
||||
INSERTED.sort
|
||||
VALUES (@entityId, @contactType, @contact, @sort)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE entitycontact
|
||||
SET contacttype = @contactType, contact = @contact
|
||||
SET contacttype = @contactType, contact = @contact, sort = @sort
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.entity_id AS entityId,
|
||||
INSERTED.contacttype AS contactType,
|
||||
INSERTED.contact
|
||||
INSERTED.contact,
|
||||
INSERTED.sort
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export const TABLES = [
|
||||
'posterminalcategory',
|
||||
'posterminal',
|
||||
'numbers',
|
||||
'ledger',
|
||||
'manifestarticle',
|
||||
'manifest',
|
||||
'price',
|
||||
|
||||
@@ -22,7 +22,6 @@ export function shapeLine(row) {
|
||||
sku: row.sku ?? null,
|
||||
amount: row.amount,
|
||||
net: row.net,
|
||||
transactedAmount: row.transactedAmount,
|
||||
rebate: row.rebate,
|
||||
tax: row.tax,
|
||||
comment: row.comment ?? null,
|
||||
@@ -80,10 +79,6 @@ export function normalizeLines(lines) {
|
||||
const rebate =
|
||||
entry?.rebate === undefined || entry?.rebate === null ? 0 : entry.rebate;
|
||||
const tax = entry?.tax;
|
||||
const transactedAmount =
|
||||
entry?.transactedAmount === undefined || entry?.transactedAmount === null
|
||||
? amount
|
||||
: entry.transactedAmount;
|
||||
const comment =
|
||||
entry?.comment === undefined || entry?.comment === null
|
||||
? null
|
||||
@@ -93,12 +88,11 @@ export function normalizeLines(lines) {
|
||||
!Number.isFinite(amount) ||
|
||||
!Number.isFinite(net) ||
|
||||
!Number.isFinite(rebate) ||
|
||||
!Number.isFinite(tax) ||
|
||||
!Number.isFinite(transactedAmount)
|
||||
!Number.isFinite(tax)
|
||||
) {
|
||||
throw new Error('Invalid lines');
|
||||
}
|
||||
|
||||
return { sku, amount, net, rebate, tax, transactedAmount, comment };
|
||||
return { sku, amount, net, rebate, tax, comment };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ export async function insertLines(
|
||||
.input('articleId', articleId)
|
||||
.input('amount', line.amount)
|
||||
.input('net', line.net)
|
||||
.input('transactedAmount', line.transactedAmount)
|
||||
.input('comment', line.comment)
|
||||
.input('rebate', line.rebate)
|
||||
.input('taxId', taxId)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CREATE } from '../../sql/entityaddress.js';
|
||||
import { CREATE, GET_NEXT_SORT } from '../../sql/entityaddress.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
@@ -14,6 +14,7 @@ function shape(row) {
|
||||
city: row.city ?? null,
|
||||
county: row.county ?? null,
|
||||
addressType: row.addressType,
|
||||
sort: row.sort,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +39,17 @@ export default function create(pool) {
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
let sort = input.sort;
|
||||
if (sort === undefined || sort === null) {
|
||||
const next = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
.query(GET_NEXT_SORT);
|
||||
sort = next.recordset[0].sort;
|
||||
} else if (!Number.isFinite(sort)) {
|
||||
throw new Error('Invalid sort');
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
@@ -49,6 +61,7 @@ export default function create(pool) {
|
||||
.input('city', optionalString(input.city))
|
||||
.input('county', optionalString(input.county))
|
||||
.input('addressType', addressType)
|
||||
.input('sort', sort)
|
||||
.query(CREATE);
|
||||
|
||||
return shape(result.recordset[0]);
|
||||
|
||||
@@ -14,6 +14,7 @@ function shape(row) {
|
||||
city: row.city ?? null,
|
||||
county: row.county ?? null,
|
||||
addressType: row.addressType,
|
||||
sort: row.sort,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ function shape(row) {
|
||||
city: row.city ?? null,
|
||||
county: row.county ?? null,
|
||||
addressType: row.addressType,
|
||||
sort: row.sort,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,6 +82,14 @@ export default function update(pool) {
|
||||
}
|
||||
}
|
||||
|
||||
let sort = current.sort;
|
||||
if (Object.prototype.hasOwnProperty.call(patch, 'sort')) {
|
||||
if (!Number.isFinite(patch.sort)) {
|
||||
throw new Error('Invalid sort');
|
||||
}
|
||||
sort = patch.sort;
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
@@ -93,6 +102,7 @@ export default function update(pool) {
|
||||
.input('city', city)
|
||||
.input('county', county)
|
||||
.input('addressType', addressType)
|
||||
.input('sort', sort)
|
||||
.query(UPDATE);
|
||||
|
||||
const row = result.recordset[0];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CREATE } from '../../sql/entitycontact.js';
|
||||
import { CREATE, GET_NEXT_SORT } from '../../sql/entitycontact.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
@@ -8,6 +8,7 @@ function shape(row) {
|
||||
entityId: row.entityId,
|
||||
contactType: row.contactType,
|
||||
contact: row.contact,
|
||||
sort: row.sort,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,11 +28,23 @@ export default function create(pool) {
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
let sort = input.sort;
|
||||
if (sort === undefined || sort === null) {
|
||||
const next = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
.query(GET_NEXT_SORT);
|
||||
sort = next.recordset[0].sort;
|
||||
} else if (!Number.isFinite(sort)) {
|
||||
throw new Error('Invalid sort');
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
.input('contactType', contactType)
|
||||
.input('contact', contact)
|
||||
.input('sort', sort)
|
||||
.query(CREATE);
|
||||
|
||||
return shape(result.recordset[0]);
|
||||
|
||||
@@ -8,6 +8,7 @@ function shape(row) {
|
||||
entityId: row.entityId,
|
||||
contactType: row.contactType,
|
||||
contact: row.contact,
|
||||
sort: row.sort,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ function shape(row) {
|
||||
entityId: row.entityId,
|
||||
contactType: row.contactType,
|
||||
contact: row.contact,
|
||||
sort: row.sort,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,12 +51,21 @@ export default function update(pool) {
|
||||
throw new Error('Contact required');
|
||||
}
|
||||
|
||||
let sort = current.sort;
|
||||
if (Object.prototype.hasOwnProperty.call(patch, 'sort')) {
|
||||
if (!Number.isFinite(patch.sort)) {
|
||||
throw new Error('Invalid sort');
|
||||
}
|
||||
sort = patch.sort;
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('entityId', entityId)
|
||||
.input('contactType', contactType)
|
||||
.input('contact', contact)
|
||||
.input('sort', sort)
|
||||
.query(UPDATE);
|
||||
|
||||
const row = result.recordset[0];
|
||||
|
||||
Reference in New Issue
Block a user