Add entity customer numbers and scope APIs via selected company.

Entity gets an optional unique-per-company number with allocate/peek/isNumberFree helpers. Company-scoped store methods no longer take companyId and resolve google_auth.selected_company instead.
This commit is contained in:
seb
2026-08-07 04:29:35 +02:00
parent 1e89e19863
commit 70e0a9ddc4
78 changed files with 940 additions and 772 deletions

View File

@@ -49,6 +49,8 @@ import entityGetById from './store/entity/getById.js';
import entityCreate from './store/entity/create.js';
import entityUpdate from './store/entity/update.js';
import entityDelete from './store/entity/delete.js';
import entityPeekNextNumber from './store/entity/peekNextNumber.js';
import entityIsNumberFree from './store/entity/isNumberFree.js';
import entityaddressList from './store/entityaddress/list.js';
import entityaddressCreate from './store/entityaddress/create.js';
import entityaddressUpdate from './store/entityaddress/update.js';
@@ -192,6 +194,8 @@ export default class LibDashStore {
create: entityCreate(pool),
update: entityUpdate(pool),
delete: entityDelete(pool),
peekNextNumber: entityPeekNextNumber(pool),
isNumberFree: entityIsNumberFree(pool),
};
this.entityaddress = {

View File

@@ -158,6 +158,7 @@ BEGIN
customergroup_id INT NOT NULL,
first_name NVARCHAR(255) NULL,
name NVARCHAR(255) NOT NULL,
[number] NVARCHAR(100) NULL,
created_at DATETIME2 NOT NULL CONSTRAINT DF_entity_created_at DEFAULT SYSUTCDATETIME(),
updated_at DATETIME2 NOT NULL CONSTRAINT DF_entity_updated_at DEFAULT SYSUTCDATETIME(),
CONSTRAINT FK_entity_customergroup FOREIGN KEY (customergroup_id) REFERENCES customergroup(id) ON DELETE CASCADE
@@ -165,6 +166,35 @@ BEGIN
END
`;
/** Unique (company_id, number) for non-null entity numbers via customergroup. */
export const INIT_ENTITY_COMPANY_NUMBER_UQ = `
IF OBJECT_ID(N'dbo.v_entity_company_number', N'V') IS NULL
BEGIN
EXEC('
CREATE VIEW dbo.v_entity_company_number
WITH SCHEMABINDING
AS
SELECT
e.id AS entity_id,
cg.company_id,
e.[number]
FROM dbo.entity AS e
INNER JOIN dbo.customergroup AS cg ON cg.id = e.customergroup_id
WHERE e.[number] IS NOT NULL
');
END
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = N'UQ_entity_company_number'
AND object_id = OBJECT_ID(N'dbo.v_entity_company_number')
)
BEGIN
CREATE UNIQUE CLUSTERED INDEX UQ_entity_company_number
ON dbo.v_entity_company_number (company_id, [number]);
END
`;
export const INIT_ENTITYADDRESS = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityaddress')
BEGIN
@@ -601,6 +631,43 @@ END
');
`;
// EXEC dbo.peeknextnumberwithprefixandpadding
// @company_id = 1, @type = N'entity', @value = @n OUTPUT;
export const INIT_PEEK_NEXT_NUMBER_WITH_PREFIX_AND_PADDING = `
IF OBJECT_ID(N'dbo.peeknextnumberwithprefixandpadding', N'P') IS NOT NULL
DROP PROCEDURE dbo.peeknextnumberwithprefixandpadding;
EXEC('
CREATE PROCEDURE dbo.peeknextnumberwithprefixandpadding
@company_id INT,
@type NVARCHAR(100),
@value NVARCHAR(100) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @prefix NVARCHAR(50);
DECLARE @padding INT;
DECLARE @num INT;
SELECT
@prefix = n.prefix,
@padding = n.padding,
@num = n.[next]
FROM numbers n WITH (UPDLOCK, ROWLOCK)
WHERE n.company_id = @company_id AND n.[type] = @type;
IF @@ROWCOUNT = 0
THROW 50000, ''Number type not found for company'', 1;
SET @value = @prefix + CASE
WHEN @padding > 0 THEN RIGHT(REPLICATE(''0'', @padding) + CAST(@num AS NVARCHAR(20)), @padding)
ELSE CAST(@num AS NVARCHAR(20))
END;
END
');
`;
export const INIT_COMPANYUSER = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'companyuser')
BEGIN
@@ -686,6 +753,7 @@ export const SCHEMA_SCRIPTS = [
INIT_CUSTOMERGROUP,
INIT_COMPANY_POS_CUSTOMERGROUP_FK,
INIT_ENTITY,
INIT_ENTITY_COMPANY_NUMBER_UQ,
INIT_ENTITYADDRESS,
INIT_ENTITYCONTACT,
INIT_UNIT,
@@ -709,6 +777,7 @@ export const SCHEMA_SCRIPTS = [
INIT_LEDGER,
INIT_NUMBERS,
INIT_GET_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
INIT_PEEK_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
INIT_COMPANYUSER,
INIT_ARTICLEHISTORY,
INIT_ENTITYHISTORY,

View File

@@ -6,6 +6,7 @@ export const ENTITY_COLUMNS = `
e.customergroup_id AS customergroupId,
e.first_name AS firstName,
e.name,
e.[number] AS number,
e.created_at AS createdAt,
e.updated_at AS updatedAt
`;
@@ -49,6 +50,16 @@ WHERE e.id = @id
AND cg.company_id = @companyId
`;
/** Occupied when another entity in the company already has this number. */
export const NUMBER_TAKEN_IN_COMPANY = `
SELECT TOP 1 e.id
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
AND e.[number] = @number
AND (@excludeEntityId IS NULL OR e.id <> @excludeEntityId)
`;
/** Customer entity: company-scoped and not Lieferanten/POS. */
export const GET_CUSTOMER_BY_ID = `
SELECT
@@ -87,15 +98,16 @@ WHERE e.id = @id
`;
export const CREATE = `
INSERT INTO entity (customergroup_id, first_name, name)
INSERT INTO entity (customergroup_id, first_name, name, [number])
OUTPUT
INSERTED.id,
INSERTED.customergroup_id AS customergroupId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.[number] AS number,
INSERTED.created_at AS createdAt,
INSERTED.updated_at AS updatedAt
VALUES (@customergroupId, @firstName, @name)
VALUES (@customergroupId, @firstName, @name, @number)
`;
export const UPDATE = `
@@ -103,12 +115,14 @@ UPDATE entity
SET
first_name = @firstName,
name = @name,
[number] = @number,
updated_at = SYSUTCDATETIME()
OUTPUT
INSERTED.id,
INSERTED.customergroup_id AS customergroupId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.[number] AS number,
INSERTED.created_at AS createdAt,
INSERTED.updated_at AS updatedAt
WHERE id = @id

View File

@@ -40,7 +40,12 @@ export const TABLES = [
'meta',
];
export const PROCEDURES = ['getnextnumberwithprefixandpadding'];
export const PROCEDURES = [
'getnextnumberwithprefixandpadding',
'peeknextnumberwithprefixandpadding',
];
export const VIEWS = ['v_entity_company_number'];
/** Drop before tables — breaks company ↔ customergroup cycle for purge. */
export const CONSTRAINTS = [
@@ -54,6 +59,10 @@ export function buildPurgeSql() {
(name) =>
`IF OBJECT_ID(N'dbo.${name}', N'P') IS NOT NULL DROP PROCEDURE dbo.[${name}];`,
),
...VIEWS.map(
(name) =>
`IF OBJECT_ID(N'dbo.${name}', N'V') IS NOT NULL DROP VIEW dbo.[${name}];`,
),
...CONSTRAINTS,
...TABLES.map(
(name) =>

View File

@@ -1,5 +1,5 @@
import { GET_ARTICLE_EXPORT, GET_CATEGORY_TREE_FLAT } from '../../sql/article.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
const ROOT_CATEGORY_NAME = 'Home';
@@ -19,18 +19,16 @@ function parseJsonArray(raw) {
*
* @example
* const fn = listExport(pool);
* const articles = await fn(42, 1, null);
* const articles = await fn(42, null);
* // => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
*/
export default function listExport(pool) {
const companyAccess = resolveCompanyAccess(pool);
const companyAccess = resolveSelectedCompanyAccess(pool);
const categoryAccess = resolveCategoryAccess(pool);
return async (googleAuthId, companyId, categoryId) => {
const { languageId, posCustomerGroupId, rootCategoryId } = await companyAccess(
googleAuthId,
companyId,
);
return async (googleAuthId, categoryId) => {
const { languageId, posCustomerGroupId, rootCategoryId } =
await companyAccess(googleAuthId);
// Default to the company's root category when no specific category is given.
let exportCategoryId = rootCategoryId;

View File

@@ -1,5 +1,5 @@
import { SEARCH_ARTICLES } from '../../sql/article.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Returns a lightweight search index of all articles for a company.
@@ -9,14 +9,14 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = listSearchIndex(pool);
* const articles = await fn(42, 1);
* const articles = await fn(42);
* // => [{ id, sku, name, eans, categoryIds }]
*/
export default function listSearchIndex(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId) => {
const { rootCategoryId, languageId } = await access(googleAuthId, companyId);
return async (googleAuthId) => {
const { companyId, rootCategoryId, languageId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/brand.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a brand for a company by its id.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 3);
* await fn(42, 3);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Brand not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
try {
const result = await pool

View File

@@ -1,5 +1,5 @@
import { LIST_BY_COMPANY } from '../../sql/brand.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Lists the brands of a company.
@@ -9,14 +9,14 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = list(pool);
* const brands = await fn(42, 1);
* const brands = await fn(42);
* // => [{ id, name, gsprpinfo }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId) => {
await access(googleAuthId, companyId);
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -3,7 +3,7 @@ import {
GET_BY_COMPANY_AND_NAME,
UPDATE_BY_ID,
} from '../../sql/brand.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Creates or updates a brand for a company, keyed by name.
@@ -13,20 +13,20 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = upsert(pool);
* const brand = await fn(42, 1, { name: 'Coca-Cola', gsprpinfo: 'x' });
* const brand = await fn(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
* // => { id, name, gsprpinfo }
*/
export default function upsert(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, input = {}) => {
return async (googleAuthId, input = {}) => {
const name = input.name?.trim();
if (!name) {
throw new Error('Invalid brand');
}
const gsprpinfo = input.gsprpinfo ?? null;
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const existing = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { GET_TREE_FOR_COMPANY } from '../../sql/category.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import queryCategoryArticles from './queryCategoryArticles.js';
import getCategoryPosterminalIds from '../posterminal/getCategoryPosterminalIds.js';
@@ -12,19 +12,17 @@ import getCategoryPosterminalIds from '../posterminal/getCategoryPosterminalIds.
*
* @example
* const fn = getTreeForCompany(pool);
* const tree = await fn(42, 1);
* const tree = await fn(42);
* // => { languageId, tree: [{ id, posterminalIds, ... }], articles: [...] }
*/
export default function getTreeForCompany(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const articles = queryCategoryArticles(pool);
const posterminalIds = getCategoryPosterminalIds(pool);
return async (googleAuthId, companyId) => {
const { rootCategoryId, languageId, posCustomerGroupId } = await access(
googleAuthId,
companyId,
);
return async (googleAuthId) => {
const { companyId, rootCategoryId, languageId, posCustomerGroupId } =
await access(googleAuthId);
const [treeResult, articleRows, ptMap] = await Promise.all([
pool

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_COMPANY_AND_KEY } from '../../sql/companysetting.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a company setting by key.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 'theme');
* await fn(42, 'theme');
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, key) => {
return async (googleAuthId, key) => {
if (!key?.trim()) {
throw new Error('Setting not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { GET_BY_COMPANY_AND_KEY } from '../../sql/companysetting.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeSetting from './shapeSetting.js';
/**
@@ -10,18 +10,18 @@ import shapeSetting from './shapeSetting.js';
*
* @example
* const fn = get(pool);
* const setting = await fn(42, 1, 'theme');
* const setting = await fn(42, 'theme');
* // => { key, value, frontend }
*/
export default function get(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, key, { frontendOnly = false } = {}) => {
return async (googleAuthId, key, { frontendOnly = false } = {}) => {
if (!key?.trim()) {
return undefined;
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -2,7 +2,7 @@ import {
LIST_BY_COMPANY,
LIST_BY_COMPANY_FRONTEND,
} from '../../sql/companysetting.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeSetting from './shapeSetting.js';
/**
@@ -13,14 +13,14 @@ import shapeSetting from './shapeSetting.js';
*
* @example
* const fn = list(pool);
* const settings = await fn(42, 1, { frontendOnly: true });
* const settings = await fn(42, { frontendOnly: true });
* // => [{ key, value, frontend }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, { frontendOnly = false } = {}) => {
await access(googleAuthId, companyId);
return async (googleAuthId, { frontendOnly = false } = {}) => {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { GET_BY_COMPANY_AND_KEY, UPSERT } from '../../sql/companysetting.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeSetting from './shapeSetting.js';
/**
@@ -10,13 +10,13 @@ import shapeSetting from './shapeSetting.js';
*
* @example
* const fn = set(pool);
* const setting = await fn(42, 1, { key: 'theme', value: 'dark', frontend: true });
* const setting = await fn(42, { key: 'theme', value: 'dark', frontend: true });
* // => { key, value, frontend }
*/
export default function set(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, input = {}) => {
return async (googleAuthId, input = {}) => {
const key = input.key?.trim();
if (!key) {
throw new Error('Invalid setting');
@@ -26,7 +26,7 @@ export default function set(pool) {
}
const frontend = Boolean(input.frontend);
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
await pool
.request()

View File

@@ -0,0 +1,35 @@
import { GET_BY_ID } from '../../sql/google_auth.js';
import resolveCompanyAccess from './resolveCompanyAccess.js';
/**
* Resolves the user's selected company and checks company membership.
* @param {object} pool
* @returns {(googleAuthId: number) => Promise<object>}
*/
export default function resolveSelectedCompanyAccess(pool) {
const access = resolveCompanyAccess(pool);
return async (googleAuthId) => {
if (!Number.isFinite(googleAuthId)) {
throw new Error('Not authorized');
}
const result = await pool
.request()
.input('googleAuthId', googleAuthId)
.query(GET_BY_ID);
const row = result.recordset[0];
if (!row) {
throw new Error('Not authorized');
}
const companyId = row.selectedCompanyId;
if (companyId == null) {
throw new Error('No company selected');
}
const ctx = await access(googleAuthId, companyId);
return { companyId, ...ctx };
};
}

View File

@@ -1,6 +1,6 @@
import { CREATE, GET_NEXT_SORT } from '../../sql/customergroup.js';
import { POS_GROUP_NAME, SUPPLIER_GROUP_NAME } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
const RESERVED_NAMES = new Set([POS_GROUP_NAME, SUPPLIER_GROUP_NAME]);
@@ -16,13 +16,13 @@ function shape(row) {
*
* @example
* const fn = create(pool);
* const group = await fn(42, 1, { name: 'Wholesale' });
* const group = await fn(42, { name: 'Wholesale' });
* // => { id, name, sort }
*/
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, { name } = {}) => {
return async (googleAuthId, { name } = {}) => {
const cleaned = name?.trim();
if (!cleaned) {
throw new Error('Name required');
@@ -31,7 +31,7 @@ export default function create(pool) {
throw new Error('Name not allowed');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const next = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/customergroup.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a customer group for a company by its id.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 5);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Customer group not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
try {
const result = await pool

View File

@@ -1,5 +1,5 @@
import { LIST_BY_COMPANY } from '../../sql/customergroup.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Lists the customer groups of a company.
@@ -9,14 +9,14 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = list(pool);
* const groups = await fn(42, 1);
* const groups = await fn(42);
* // => [{ id, name, sort }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId) => {
await access(googleAuthId, companyId);
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -3,7 +3,7 @@ import {
UPDATE_SORT,
} from '../../sql/customergroup.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Reorders the customer groups of a company.
@@ -13,18 +13,18 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = order(pool);
* const result = await fn(42, 1, { customergroupIds: [3, 1, 2] });
* const result = await fn(42, { customergroupIds: [3, 1, 2] });
* // => { customergroupIds: [3, 1, 2] }
*/
export default function order(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, { customergroupIds } = {}) => {
return async (googleAuthId, { customergroupIds } = {}) => {
if (!Array.isArray(customergroupIds)) {
throw new Error('Invalid customer group order');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const groups = await pool
.request()

View File

@@ -1,6 +1,6 @@
import { GET_BY_COMPANY_AND_ID, UPDATE } from '../../sql/customergroup.js';
import { POS_GROUP_NAME, SUPPLIER_GROUP_NAME } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
const LOCKED_NAMES = new Set([POS_GROUP_NAME, SUPPLIER_GROUP_NAME]);
@@ -16,13 +16,13 @@ function shape(row) {
*
* @example
* const fn = update(pool);
* const group = await fn(42, 1, 5, { name: 'Retail' });
* const group = await fn(42, 5, { name: 'Retail' });
* // => { id, name, sort }
*/
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id, { name } = {}) => {
return async (googleAuthId, id, { name } = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Customer group not found');
}
@@ -32,7 +32,7 @@ export default function update(pool) {
throw new Error('Name required');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const existing = await pool
.request()

View File

@@ -1,6 +1,6 @@
import { CREATE_MANIFEST, DELIVERY_TYPE } from '../../sql/delivery.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import taxEnsure from '../tax/ensure.js';
import {
loadDelivery,
@@ -17,14 +17,14 @@ import { assertSupplier, insertLines } from './writeLines.js';
*
* @example
* const fn = create(pool);
* const delivery = await fn(42, 1, { number: 'D-1', date: '2026-01-01', supplierId: 5, lines: [...] });
* const delivery = await fn(42, { number: 'D-1', date: '2026-01-01', supplierId: 5, lines: [...] });
* // => { id, number, type, entityId, date, lines: [...] }
*/
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const ensureTax = taxEnsure(pool);
return async (googleAuthId, companyId, body = {}) => {
return async (googleAuthId, body = {}) => {
const number = body.number?.trim?.() ? body.number.trim() : null;
const date = parseDeliveryDate(body.date);
if (!number || !date) {
@@ -33,7 +33,7 @@ export default function create(pool) {
const lines = normalizeLines(body.lines ?? []);
const { rootCategoryId } = await access(googleAuthId, companyId);
const { companyId, rootCategoryId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();

View File

@@ -1,5 +1,5 @@
import { DELETE_MANIFEST, DELIVERY_TYPE } from '../../sql/delivery.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a delivery by its id within a company.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 5);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Delivery not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,4 +1,4 @@
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import { loadDelivery } from './helpers.js';
/**
@@ -9,18 +9,18 @@ import { loadDelivery } from './helpers.js';
*
* @example
* const fn = getById(pool);
* const delivery = await fn(42, 1, 5);
* const delivery = await fn(42, 5);
* // => { id, number, type, entityId, date, lines: [...] }
*/
export default function getById(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Delivery not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const delivery = await loadDelivery(pool, null, companyId, id);
if (!delivery) {

View File

@@ -1,5 +1,5 @@
import { DELIVERY_TYPE, LIST_DELIVERIES } from '../../sql/delivery.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import { shapeSummary } from './helpers.js';
/**
@@ -10,14 +10,14 @@ import { shapeSummary } from './helpers.js';
*
* @example
* const fn = list(pool);
* const deliveries = await fn(42, 1);
* const deliveries = await fn(42);
* // => [{ id, number, type, entityId, date }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId) => {
await access(googleAuthId, companyId);
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -5,7 +5,7 @@ import {
UPDATE_MANIFEST,
} from '../../sql/delivery.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import taxEnsure from '../tax/ensure.js';
import {
loadDelivery,
@@ -23,14 +23,14 @@ import { assertSupplier, insertLines } from './writeLines.js';
*
* @example
* const fn = update(pool);
* const delivery = await fn(42, 1, 5, { number: 'D-2', lines: [...] });
* const delivery = await fn(42, 5, { number: 'D-2', lines: [...] });
* // => { id, number, type, entityId, date, lines: [...] }
*/
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const ensureTax = taxEnsure(pool);
return async (googleAuthId, companyId, id, patch = {}) => {
return async (googleAuthId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Delivery not found');
}
@@ -52,7 +52,7 @@ export default function update(pool) {
normalizedLines = normalizeLines(patch.lines);
}
const { rootCategoryId } = await access(googleAuthId, companyId);
const { companyId, rootCategoryId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();

View File

@@ -1,40 +1,30 @@
import { CREATE } from '../../sql/entity.js';
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
import nextNumber from '../numbers/nextNumber.js';
import shape from './shape.js';
/**
* Creates a new entity (customer) in a customer group, recording history.
* Optional `number`; if omitted, allocates via `getnextnumberwithprefixandpadding`
* (`type` `entity`).
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates an entity.
*
* @example
* const fn = create(pool);
* const entity = await fn(42, 1, { customergroupId: 5, name: 'Doe', firstName: 'John' });
* // => { id, customergroupId, firstName, name, createdAt, updatedAt }
* const entity = await fn(42, { customergroupId: 5, name: 'Doe', firstName: 'John' });
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const groupAccess = resolveCustomerGroupAccess(pool);
const allocateNumber = nextNumber(pool);
return async (
googleAuthId,
companyId,
{ customergroupId, name, firstName = null } = {},
) => {
return async (googleAuthId, { customergroupId, name, firstName = null, number } = {}) => {
const cleaned = name?.trim();
if (!cleaned) {
throw new Error('Name required');
@@ -45,17 +35,26 @@ export default function create(pool) {
? null
: String(firstName).trim() || null;
const { companyuserId } = await access(googleAuthId, companyId);
const explicitNumber =
number == null || number === ''
? null
: String(number).trim() || null;
const { companyId, companyuserId } = await access(googleAuthId);
await groupAccess(companyId, customergroupId);
const tx = pool.transaction();
await tx.begin();
try {
const entityNumber =
explicitNumber ?? (await allocateNumber(companyId, 'entity', tx));
const created = await request(pool, tx)
.input('customergroupId', customergroupId)
.input('firstName', first)
.input('name', cleaned)
.input('number', entityNumber)
.query(CREATE);
const row = created.recordset[0];
@@ -74,6 +73,9 @@ export default function create(pool) {
} catch {
// ignore
}
if (err.number === 2601 || err.number === 2627) {
throw new Error('Number already in use');
}
throw err;
}
};

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes an entity by its id within a company.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 5);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,16 +1,6 @@
import { GET_BY_COMPANY_AND_ID } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shape from './shape.js';
/**
* Returns a single entity by its id within a company.
@@ -20,18 +10,18 @@ function shape(row) {
*
* @example
* const fn = getById(pool);
* const entity = await fn(42, 1, 5);
* // => { id, customergroupId, firstName, name, createdAt, updatedAt }
* const entity = await fn(42, 5);
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
export default function getById(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -0,0 +1,46 @@
import { NUMBER_TAKEN_IN_COMPANY } from '../../sql/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Returns whether a customer number is free within the company.
* Pass `excludeEntityId` when editing so the entity's own number counts as free.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that checks number availability.
*
* @example
* const fn = isNumberFree(pool);
* const free = await fn(42, 'C-100');
* // => true
* const freeOnEdit = await fn(42, 'C-100', { excludeEntityId: 5 });
* // => true
*/
export default function isNumberFree(pool) {
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, number, { excludeEntityId } = {}) => {
const cleaned =
number == null || number === '' ? null : String(number).trim() || null;
if (!cleaned) {
throw new Error('Number required');
}
if (
excludeEntityId != null &&
!Number.isFinite(excludeEntityId)
) {
throw new Error('Entity not found');
}
const { companyId } = await access(googleAuthId);
const result = await pool
.request()
.input('companyId', companyId)
.input('number', cleaned)
.input('excludeEntityId', excludeEntityId ?? null)
.query(NUMBER_TAKEN_IN_COMPANY);
return !result.recordset[0];
};
}

View File

@@ -5,19 +5,9 @@ import {
POS_GROUP_NAME,
SUPPLIER_GROUP_NAME,
} from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
import shape from './shape.js';
/**
* Lists the entities (customers) of a company, optionally filtered by customer
@@ -28,19 +18,15 @@ function shape(row) {
*
* @example
* const fn = list(pool);
* const entities = await fn(42, 1, { customergroupId: 5 });
* // => [{ id, customergroupId, firstName, name, createdAt, updatedAt }]
* const entities = await fn(42, { customergroupId: 5 });
* // => [{ id, customergroupId, firstName, name, number, createdAt, updatedAt }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const groupAccess = resolveCustomerGroupAccess(pool);
return async (
googleAuthId,
companyId,
{ customergroupId, role } = {},
) => {
await access(googleAuthId, companyId);
return async (googleAuthId, { customergroupId, role } = {}) => {
const { companyId } = await access(googleAuthId);
if (role != null && role !== 'customer') {
throw new Error('Invalid role');

View File

@@ -0,0 +1,24 @@
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import peekNumber from '../numbers/peekNumber.js';
/**
* Returns the next customer number that would be allocated for the company
* (`numbers` type `entity`) without consuming it.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that peeks the next entity number.
*
* @example
* const fn = peekNextNumber(pool);
* const number = await fn(42);
* // => '1'
*/
export default function peekNextNumber(pool) {
const access = resolveSelectedCompanyAccess(pool);
const peek = peekNumber(pool);
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
return peek(companyId, 'entity');
};
}

11
src/store/entity/shape.js Normal file
View File

@@ -0,0 +1,11 @@
export default function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
number: row.number ?? null,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}

View File

@@ -1,45 +1,37 @@
import { GET_BY_COMPANY_AND_ID, UPDATE } from '../../sql/entity.js';
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
function shape(row) {
return {
id: row.id,
customergroupId: row.customergroupId,
firstName: row.firstName ?? null,
name: row.name,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shape from './shape.js';
/**
* Updates the name and/or first name of an entity, recording history.
* Updates name, first name, and/or customer number of an entity, recording history.
* Optional `number` sets the customer number explicitly (does not auto-allocate).
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates an entity.
*
* @example
* const fn = update(pool);
* const entity = await fn(42, 1, 5, { name: 'Doe', firstName: 'Jane' });
* // => { id, customergroupId, firstName, name, createdAt, updatedAt }
* const entity = await fn(42, 5, { name: 'Doe', firstName: 'Jane', number: 'C-42' });
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id, patch = {}) => {
return async (googleAuthId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');
}
const hasName = Object.prototype.hasOwnProperty.call(patch, 'name');
const hasFirstName = Object.prototype.hasOwnProperty.call(patch, 'firstName');
if (!hasName && !hasFirstName) {
const hasNumber = Object.prototype.hasOwnProperty.call(patch, 'number');
if (!hasName && !hasFirstName && !hasNumber) {
throw new Error('Name required');
}
const { companyuserId } = await access(googleAuthId, companyId);
const { companyId, companyuserId } = await access(googleAuthId);
const existing = await pool
.request()
@@ -68,6 +60,14 @@ export default function update(pool) {
: String(patch.firstName).trim() || null;
}
let number = current.number ?? null;
if (hasNumber) {
number =
patch.number == null || patch.number === ''
? null
: String(patch.number).trim() || null;
}
const historyLines = [];
const oldFirst = current.firstName ?? '';
const newFirst = firstName ?? '';
@@ -81,6 +81,13 @@ export default function update(pool) {
`Name geändert von "${current.name}" auf "${name}"`,
);
}
const oldNumber = current.number ?? '';
const newNumber = number ?? '';
if (oldNumber !== newNumber) {
historyLines.push(
`Nummer geändert von "${oldNumber}" auf "${newNumber}"`,
);
}
if (!historyLines.length) {
return shape(current);
@@ -94,6 +101,7 @@ export default function update(pool) {
.input('id', id)
.input('firstName', firstName)
.input('name', name)
.input('number', number)
.query(UPDATE);
const row = updated.recordset[0];
@@ -115,6 +123,9 @@ export default function update(pool) {
} catch {
// ignore
}
if (err.number === 2601 || err.number === 2627) {
throw new Error('Number already in use');
}
throw err;
}
};

View File

@@ -1,5 +1,5 @@
import { CREATE, GET_NEXT_SORT } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
@@ -32,14 +32,14 @@ function optionalString(value) {
*
* @example
* const fn = create(pool);
* const address = await fn(42, 1, 5, { addressType: 'billing', street: 'Main St', city: 'Berlin' });
* const address = await fn(42, 5, { addressType: 'billing', street: 'Main St', city: 'Berlin' });
* // => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
*/
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, input = {}) => {
return async (googleAuthId, entityId, input = {}) => {
const addressType = input.addressType?.trim?.()
? input.addressType.trim()
: null;
@@ -47,7 +47,7 @@ export default function create(pool) {
throw new Error('Address type required');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
let sort = input.sort;

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
/**
@@ -10,18 +10,18 @@ import resolveEntityAccess from '../entity/resolveEntityAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 5, 9);
* await fn(42, 5, 9);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id) => {
return async (googleAuthId, entityId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Address not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
const result = await pool

View File

@@ -1,5 +1,5 @@
import { LIST_BY_ENTITY } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
@@ -26,15 +26,15 @@ function shape(row) {
*
* @example
* const fn = list(pool);
* const addresses = await fn(42, 1, 5);
* const addresses = await fn(42, 5);
* // => [{ id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId) => {
await access(googleAuthId, companyId);
return async (googleAuthId, entityId) => {
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
const result = await pool

View File

@@ -1,5 +1,5 @@
import { GET_BY_ENTITY_AND_ID, UPDATE } from '../../sql/entityaddress.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
@@ -32,19 +32,19 @@ function optionalString(value) {
*
* @example
* const fn = update(pool);
* const address = await fn(42, 1, 5, 9, { city: 'Hamburg' });
* const address = await fn(42, 5, 9, { city: 'Hamburg' });
* // => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
*/
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id, patch = {}) => {
return async (googleAuthId, entityId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Address not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
const existing = await pool

View File

@@ -1,5 +1,5 @@
import { CREATE, GET_NEXT_SORT } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
@@ -20,14 +20,14 @@ function shape(row) {
*
* @example
* const fn = create(pool);
* const contact = await fn(42, 1, 5, { contactType: 'email', contact: 'a@b.c' });
* const contact = await fn(42, 5, { contactType: 'email', contact: 'a@b.c' });
* // => { id, entityId, contactType, contact, sort }
*/
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, input = {}) => {
return async (googleAuthId, entityId, input = {}) => {
const contactType = input.contactType?.trim?.()
? input.contactType.trim()
: null;
@@ -36,7 +36,7 @@ export default function create(pool) {
throw new Error('Contact required');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
let sort = input.sort;

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
/**
@@ -10,18 +10,18 @@ import resolveEntityAccess from '../entity/resolveEntityAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 5, 9);
* await fn(42, 5, 9);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id) => {
return async (googleAuthId, entityId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Contact not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
const result = await pool

View File

@@ -1,5 +1,5 @@
import { LIST_BY_ENTITY } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
@@ -20,15 +20,15 @@ function shape(row) {
*
* @example
* const fn = list(pool);
* const contacts = await fn(42, 1, 5);
* const contacts = await fn(42, 5);
* // => [{ id, entityId, contactType, contact, sort }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId) => {
await access(googleAuthId, companyId);
return async (googleAuthId, entityId) => {
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
const result = await pool

View File

@@ -1,5 +1,5 @@
import { GET_BY_ENTITY_AND_ID, UPDATE } from '../../sql/entitycontact.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
function shape(row) {
@@ -20,19 +20,19 @@ function shape(row) {
*
* @example
* const fn = update(pool);
* const contact = await fn(42, 1, 5, 9, { contact: 'b@c.d' });
* const contact = await fn(42, 5, 9, { contact: 'b@c.d' });
* // => { id, entityId, contactType, contact, sort }
*/
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
return async (googleAuthId, companyId, entityId, id, patch = {}) => {
return async (googleAuthId, entityId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Contact not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);
const existing = await pool

View File

@@ -15,7 +15,7 @@ import {
SUPPLIER_GROUP_NAME,
} from '../../sql/entity.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import taxEnsure from '../tax/ensure.js';
function shapeSummary(type, row) {
@@ -179,7 +179,7 @@ async function loadDoc(pool, tx, { type, companyId, id }) {
* `label` → error prefix (`Quote` / `Order` → "Quote not found")
*/
export default function createManifestDocStore(pool, { type, label }) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
const ensureTax = taxEnsure(pool);
const notFound = `${label} not found`;
const invalid = `Invalid ${label.toLowerCase()}`;
@@ -188,11 +188,10 @@ export default function createManifestDocStore(pool, { type, label }) {
* Lists the quote/order documents of a company.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} companyId - The company id.
* @returns {Promise<Array<object>>} The document summaries.
*/
async function list(googleAuthId, companyId) {
await access(googleAuthId, companyId);
async function list(googleAuthId) {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()
@@ -207,16 +206,15 @@ export default function createManifestDocStore(pool, { type, label }) {
* Returns a single quote/order document by its id within a company.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} companyId - The company id.
* @param {number} id - The document id.
* @returns {Promise<object|null>} The document with its lines, or null if not found.
*/
async function getById(googleAuthId, companyId, id) {
async function getById(googleAuthId, id) {
if (!Number.isFinite(id)) {
throw new Error(notFound);
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const doc = await loadDoc(pool, null, { type, companyId, id });
if (!doc) {
@@ -229,11 +227,10 @@ export default function createManifestDocStore(pool, { type, label }) {
* Creates a new quote/order document with its lines.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} companyId - The company id.
* @param {object} body - The document payload (`number`, `date`, `entityId`, `lines`).
* @returns {Promise<object>} The created document with its lines.
*/
async function create(googleAuthId, companyId, body = {}) {
async function create(googleAuthId, body = {}) {
const number = body.number?.trim?.() ? body.number.trim() : null;
const date = parseDate(body.date);
if (!number || !date) {
@@ -241,7 +238,7 @@ export default function createManifestDocStore(pool, { type, label }) {
}
const lines = normalizeLines(body.lines ?? []);
const { rootCategoryId } = await access(googleAuthId, companyId);
const { companyId, rootCategoryId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();
@@ -288,12 +285,11 @@ export default function createManifestDocStore(pool, { type, label }) {
* Updates a quote/order document's header and/or lines.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} companyId - The company id.
* @param {number} id - The document id.
* @param {object} patch - The fields to update (`number`, `date`, `entityId`, `lines`).
* @returns {Promise<object>} The updated document with its lines.
*/
async function update(googleAuthId, companyId, id, patch = {}) {
async function update(googleAuthId, id, patch = {}) {
if (!Number.isFinite(id)) {
throw new Error(notFound);
}
@@ -312,7 +308,7 @@ export default function createManifestDocStore(pool, { type, label }) {
normalizedLines = normalizeLines(patch.lines);
}
const { rootCategoryId } = await access(googleAuthId, companyId);
const { companyId, rootCategoryId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();
@@ -396,16 +392,15 @@ export default function createManifestDocStore(pool, { type, label }) {
* Deletes a quote/order document by its id within a company.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} companyId - The company id.
* @param {number} id - The document id.
* @returns {Promise<void>}
*/
async function del(googleAuthId, companyId, id) {
async function del(googleAuthId, id) {
if (!Number.isFinite(id)) {
throw new Error(notFound);
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -0,0 +1,19 @@
import { ENSURE_TYPE } from '../../sql/numbers.js';
import request from '../request.js';
export default function nextNumber(pool) {
return async (companyId, type, tx) => {
await request(pool, tx)
.input('companyId', companyId)
.input('type', type)
.query(ENSURE_TYPE);
const req = request(pool, tx);
req.input('company_id', companyId);
req.input('type', type);
req.output('value');
const result = await req.execute('getnextnumberwithprefixandpadding');
return result.output.value;
};
}

View File

@@ -1,21 +1,6 @@
import { ENSURE_TYPE } from '../../sql/numbers.js';
import request from '../request.js';
import nextNumber from './nextNumber.js';
export default function nextSku(pool) {
return async (companyId, tx) => {
const type = 'sku';
await request(pool, tx)
.input('companyId', companyId)
.input('type', type)
.query(ENSURE_TYPE);
const req = request(pool, tx);
req.input('company_id', companyId);
req.input('type', type);
req.output('value');
const result = await req.execute('getnextnumberwithprefixandpadding');
return result.output.value;
};
const allocate = nextNumber(pool);
return (companyId, tx) => allocate(companyId, 'sku', tx);
}

View File

@@ -0,0 +1,19 @@
import { ENSURE_TYPE } from '../../sql/numbers.js';
import request from '../request.js';
export default function peekNumber(pool) {
return async (companyId, type, tx) => {
await request(pool, tx)
.input('companyId', companyId)
.input('type', type)
.query(ENSURE_TYPE);
const req = request(pool, tx);
req.input('company_id', companyId);
req.input('type', type);
req.output('value');
const result = await req.execute('peeknextnumberwithprefixandpadding');
return result.output.value;
};
}

View File

@@ -10,7 +10,7 @@ import createManifestDocStore from '../manifestDoc/createStore.js';
*
* @example
* const store = createOrderStore(pool);
* const orders = await store.list(42, 1);
* const orders = await store.list(42);
* // => [{ id, number, type, entityId, date }]
*/
export default function createOrderStore(pool) {

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/posterminal.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a posterminal by its id within a company.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 5);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Posterminal not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { LIST_BY_COMPANY } from '../../sql/posterminal.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Lists the posterminals of a company.
@@ -9,14 +9,14 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = list(pool);
* const terminals = await fn(42, 1);
* const terminals = await fn(42);
* // => [{ id, name, token, pairingpin }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId) => {
await access(googleAuthId, companyId);
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { GET_BY_ID, SET_OFF, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Removes a category assignment from a posterminal (turns it off).
@@ -9,18 +9,18 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = setOff(pool);
* const result = await fn(42, 1, 5, 9);
* const result = await fn(42, 5, 9);
* // => { id, categoryIds: [...] }
*/
export default function setOff(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, posterminalId, categoryId) => {
return async (googleAuthId, posterminalId, categoryId) => {
if (!Number.isFinite(posterminalId) || !Number.isFinite(categoryId)) {
throw new Error('Invalid posterminal or category');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const existing = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { GET_BY_ID, SET_OFF_ALL, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Removes all category assignments from a posterminal.
@@ -9,18 +9,18 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = setOffAll(pool);
* const result = await fn(42, 1, 5);
* const result = await fn(42, 5);
* // => { id, categoryIds: [] }
*/
export default function setOffAll(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, posterminalId) => {
return async (googleAuthId, posterminalId) => {
if (!Number.isFinite(posterminalId)) {
throw new Error('Invalid posterminal');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
// Verify posterminal belongs to company
const pt = await pool

View File

@@ -1,5 +1,5 @@
import { GET_BY_ID, SET_ON, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Assigns a category to a posterminal (turns it on for that category).
@@ -9,18 +9,18 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = setOn(pool);
* const result = await fn(42, 1, 5, 9);
* const result = await fn(42, 5, 9);
* // => { id, categoryIds: [9, ...] }
*/
export default function setOn(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, posterminalId, categoryId) => {
return async (googleAuthId, posterminalId, categoryId) => {
if (!Number.isFinite(posterminalId) || !Number.isFinite(categoryId)) {
throw new Error('Invalid posterminal or category');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const existing = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { GET_BY_ID, SET_ON_ALL, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import { GET_COMPANY_ROOT_CATEGORY } from '../../sql/company.js';
/**
@@ -10,18 +10,18 @@ import { GET_COMPANY_ROOT_CATEGORY } from '../../sql/company.js';
*
* @example
* const fn = setOnAll(pool);
* const result = await fn(42, 1, 5);
* const result = await fn(42, 5);
* // => { id, categoryIds: [...] }
*/
export default function setOnAll(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, posterminalId) => {
return async (googleAuthId, posterminalId) => {
if (!Number.isFinite(posterminalId)) {
throw new Error('Invalid posterminal');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
// Verify posterminal belongs to company
const pt = await pool

View File

@@ -1,5 +1,5 @@
import { GET_BY_ID, CREATE, UPDATE } from '../../sql/posterminal.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Creates or updates a posterminal for a company.
@@ -9,13 +9,13 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = upsert(pool);
* const terminal = await fn(42, 1, { name: 'Kasse 1', pairingpin: '1234', token: 'abc' });
* const terminal = await fn(42, { name: 'Kasse 1', pairingpin: '1234', token: 'abc' });
* // => { id, name, token, pairingpin }
*/
export default function upsert(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, input = {}) => {
return async (googleAuthId, input = {}) => {
const name = input.name?.trim();
if (!name) {
throw new Error('Invalid posterminal');
@@ -27,7 +27,7 @@ export default function upsert(pool) {
const token = input.token ?? null;
const id = input.id ?? null;
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
if (id) {
const existing = await pool

View File

@@ -10,7 +10,7 @@ import createManifestDocStore from '../manifestDoc/createStore.js';
*
* @example
* const store = createQuoteStore(pool);
* const quotes = await store.list(42, 1);
* const quotes = await store.list(42);
* // => [{ id, number, type, entityId, date }]
*/
export default function createQuoteStore(pool) {

View File

@@ -1,7 +1,7 @@
import { CREATE } from '../../sql/entity.js';
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import ensureLieferantenGroup from './ensureLieferantenGroup.js';
/**
@@ -12,19 +12,19 @@ import ensureLieferantenGroup from './ensureLieferantenGroup.js';
*
* @example
* const fn = create(pool);
* const supplier = await fn(42, 1, { name: 'Acme Supplies' });
* const supplier = await fn(42, { name: 'Acme Supplies' });
* // => { id, name, createdAt, updatedAt }
*/
export default function create(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, { name } = {}) => {
return async (googleAuthId, { name } = {}) => {
const cleaned = name?.trim();
if (!cleaned) {
throw new Error('Name required');
}
const { companyuserId } = await access(googleAuthId, companyId);
const { companyId, companyuserId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();
@@ -36,6 +36,7 @@ export default function create(pool) {
.input('customergroupId', customergroupId)
.input('firstName', null)
.input('name', cleaned)
.input('number', null)
.query(CREATE);
const row = created.recordset[0];

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_ID, SUPPLIER_GROUP_NAME } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a supplier by its id within a company.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 5);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id) => {
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Supplier not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { LIST_SUPPLIERS_BY_COMPANY, SUPPLIER_GROUP_NAME } from '../../sql/entity.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Lists the suppliers of a company.
@@ -9,14 +9,14 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = list(pool);
* const suppliers = await fn(42, 1);
* const suppliers = await fn(42);
* // => [{ id, name, createdAt, updatedAt }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId) => {
await access(googleAuthId, companyId);
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -6,7 +6,7 @@ import {
} from '../../sql/entity.js';
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
import request from '../request.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
function shape(row) {
return {
@@ -25,13 +25,13 @@ function shape(row) {
*
* @example
* const fn = update(pool);
* const supplier = await fn(42, 1, 5, { name: 'Acme GmbH' });
* const supplier = await fn(42, 5, { name: 'Acme GmbH' });
* // => { id, name, createdAt, updatedAt }
*/
export default function update(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, id, { name } = {}) => {
return async (googleAuthId, id, { name } = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Supplier not found');
}
@@ -41,7 +41,7 @@ export default function update(pool) {
throw new Error('Name required');
}
const { companyuserId } = await access(googleAuthId, companyId);
const { companyId, companyuserId } = await access(googleAuthId);
const existing = await pool
.request()

View File

@@ -1,5 +1,5 @@
import { DELETE_BY_COMPANY_AND_SI } from '../../sql/unit.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a unit for a company by its SI code.
@@ -9,17 +9,17 @@ import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
*
* @example
* const fn = del(pool);
* await fn(42, 1, 'l');
* await fn(42, 'l');
*/
export default function del(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, si) => {
return async (googleAuthId, si) => {
if (!si?.trim()) {
throw new Error('Unit not found');
}
await access(googleAuthId, companyId);
const { companyId } = await access(googleAuthId);
try {
const result = await pool

View File

@@ -1,5 +1,5 @@
import { LIST_BY_COMPANY } from '../../sql/unit.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeUnit from './shapeUnit.js';
/**
@@ -10,14 +10,14 @@ import shapeUnit from './shapeUnit.js';
*
* @example
* const fn = list(pool);
* const units = await fn(42, 1);
* const units = await fn(42);
* // => [{ id, si, basesi, basesifactor, symbol, name }]
*/
export default function list(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId) => {
const { languageId } = await access(googleAuthId, companyId);
return async (googleAuthId) => {
const { companyId, languageId } = await access(googleAuthId);
const result = await pool
.request()

View File

@@ -9,7 +9,7 @@ import {
GET_BY_UNIT_AND_LANGUAGE,
UPDATE_BY_UNIT_AND_LANGUAGE,
} from '../../sql/unittext.js';
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import request from '../request.js';
import shapeUnit from './shapeUnit.js';
@@ -38,15 +38,15 @@ function validateUnit({ si, basesi, basesifactor, symbol, name }) {
*
* @example
* const fn = upsert(pool);
* const unit = await fn(42, 1, { si: 'l', basesi: 'm3', basesifactor: 0.001, symbol: 'L', name: 'Liter' });
* const unit = await fn(42, { si: 'l', basesi: 'm3', basesifactor: 0.001, symbol: 'L', name: 'Liter' });
* // => { id, si, basesi, basesifactor, symbol, name }
*/
export default function upsert(pool) {
const access = resolveCompanyAccess(pool);
const access = resolveSelectedCompanyAccess(pool);
return async (googleAuthId, companyId, input) => {
return async (googleAuthId, input) => {
const unit = validateUnit(input);
const { languageId } = await access(googleAuthId, companyId);
const { companyId, languageId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();