This commit is contained in:
seb
2026-09-22 07:25:16 +02:00
parent 4ed6260e91
commit cca131d83a
148 changed files with 5343 additions and 3466 deletions

View File

@@ -1,5 +1,7 @@
import { LIST_BY_COMPANY } from '../../providers/mssql/queries/numbers.js';
import { LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY } from '../../providers/mssql/queries/numbers.js';
import { LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY } from '../../providers/sqlite3/queries/numbers.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js';
function shape(row) {
return {
@@ -11,27 +13,29 @@ function shape(row) {
};
}
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
export default function list(provider) {
const access = resolveSelectedCompanyAccess(provider);
const sql =
provider?.dialect === 'sqlite3'
? SQLITE_LIST_BY_COMPANY
: MSSQL_LIST_BY_COMPANY;
/**
* Lists the number sequences of the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<Array<{id: number, type: string, prefix: string, padding: number, next: number}>>} The number sequences.
*
* @example
* const numbers = await store.numbers.list(42);
* // => [{ id, type, prefix, padding, next }]
*/
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
return async (googleAuthId, tx) => {
const { companyId } = await access(googleAuthId, tx);
const result = await pool
.request()
.input('companyId', companyId)
.query(LIST_BY_COMPANY);
const result = await makeRunner(provider, tx).query(sql, { companyId });
return result.recordset.map(shape);
return result.rows.map(shape);
};
}

View File

@@ -1,7 +1,28 @@
import { ENSURE_TYPE } from '../../providers/mssql/queries/numbers.js';
import request from '../request.js';
import {
ENSURE_TYPE as MSSQL_ENSURE_TYPE,
GET_FOR_UPDATE as MSSQL_GET_FOR_UPDATE,
INCREMENT as MSSQL_INCREMENT,
} from '../../providers/mssql/queries/numbers.js';
import {
ENSURE_TYPE as SQLITE_ENSURE_TYPE,
GET_FOR_UPDATE as SQLITE_GET_FOR_UPDATE,
INCREMENT as SQLITE_INCREMENT,
} from '../../providers/sqlite3/queries/numbers.js';
import makeRunner from '../runner.js';
function formatNumber(prefix, padding, num) {
const p = prefix ?? '';
const nStr = String(num);
const padded = padding > 0 ? nStr.padStart(padding, '0') : nStr;
return `${p}${padded}`;
}
export default function nextNumber(provider) {
const isSqlite = provider?.dialect === 'sqlite3';
const ensureSql = isSqlite ? SQLITE_ENSURE_TYPE : MSSQL_ENSURE_TYPE;
const getSql = isSqlite ? SQLITE_GET_FOR_UPDATE : MSSQL_GET_FOR_UPDATE;
const incSql = isSqlite ? SQLITE_INCREMENT : MSSQL_INCREMENT;
export default function nextNumber(pool) {
/**
* Allocates and returns the next number for a company and type, consuming it.
*
@@ -15,17 +36,24 @@ export default function nextNumber(pool) {
* // => '1'
*/
return async (companyId, type, tx) => {
await request(pool, tx)
.input('companyId', companyId)
.input('type', type)
.query(ENSURE_TYPE);
const doAlloc = async (runner) => {
await runner.query(ensureSql, { companyId, type });
const res = await runner.query(getSql, { companyId, type });
const row = res.rows[0];
if (!row) {
throw new Error('Number type not found for company');
}
const value = formatNumber(row.prefix, row.padding, row.next);
await runner.query(incSql, { id: row.id });
return value;
};
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;
if (tx) {
return doAlloc(makeRunner(provider, tx));
}
if (typeof provider?.transaction === 'function') {
return provider.transaction(doAlloc);
}
return doAlloc(makeRunner(provider));
};
}

View File

@@ -1,7 +1,7 @@
import nextNumber from './nextNumber.js';
export default function nextSku(pool) {
const allocate = nextNumber(pool);
export default function nextSku(provider) {
const allocate = nextNumber(provider);
/**
* Allocates and returns the next SKU for a company, consuming it.
*

View File

@@ -1,7 +1,25 @@
import { ENSURE_TYPE } from '../../providers/mssql/queries/numbers.js';
import request from '../request.js';
import {
ENSURE_TYPE as MSSQL_ENSURE_TYPE,
GET_FOR_UPDATE as MSSQL_GET_FOR_UPDATE,
} from '../../providers/mssql/queries/numbers.js';
import {
ENSURE_TYPE as SQLITE_ENSURE_TYPE,
GET_FOR_UPDATE as SQLITE_GET_FOR_UPDATE,
} from '../../providers/sqlite3/queries/numbers.js';
import makeRunner from '../runner.js';
function formatNumber(prefix, padding, num) {
const p = prefix ?? '';
const nStr = String(num);
const padded = padding > 0 ? nStr.padStart(padding, '0') : nStr;
return `${p}${padded}`;
}
export default function peekNumber(provider) {
const isSqlite = provider?.dialect === 'sqlite3';
const ensureSql = isSqlite ? SQLITE_ENSURE_TYPE : MSSQL_ENSURE_TYPE;
const getSql = isSqlite ? SQLITE_GET_FOR_UPDATE : MSSQL_GET_FOR_UPDATE;
export default function peekNumber(pool) {
/**
* Returns the next number for a company and type without consuming it.
*
@@ -15,17 +33,22 @@ export default function peekNumber(pool) {
* // => '1'
*/
return async (companyId, type, tx) => {
await request(pool, tx)
.input('companyId', companyId)
.input('type', type)
.query(ENSURE_TYPE);
const doPeek = async (runner) => {
await runner.query(ensureSql, { companyId, type });
const res = await runner.query(getSql, { companyId, type });
const row = res.rows[0];
if (!row) {
throw new Error('Number type not found for company');
}
return formatNumber(row.prefix, row.padding, row.next);
};
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;
if (tx) {
return doPeek(makeRunner(provider, tx));
}
if (typeof provider?.transaction === 'function') {
return provider.transaction(doPeek);
}
return doPeek(makeRunner(provider));
};
}

View File

@@ -1,8 +1,13 @@
import {
GET_BY_COMPANY_AND_ID,
UPDATE_BY_COMPANY_AND_ID,
GET_BY_COMPANY_AND_ID as MSSQL_GET_BY_COMPANY_AND_ID,
UPDATE_BY_COMPANY_AND_ID as MSSQL_UPDATE_BY_COMPANY_AND_ID,
} from '../../providers/mssql/queries/numbers.js';
import {
GET_BY_COMPANY_AND_ID as SQLITE_GET_BY_COMPANY_AND_ID,
UPDATE_BY_COMPANY_AND_ID as SQLITE_UPDATE_BY_COMPANY_AND_ID,
} from '../../providers/sqlite3/queries/numbers.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js';
function shape(row) {
return {
@@ -14,8 +19,15 @@ function shape(row) {
};
}
export default function update(pool) {
const access = resolveSelectedCompanyAccess(pool);
export default function update(provider) {
const access = resolveSelectedCompanyAccess(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const getSql = isSqlite
? SQLITE_GET_BY_COMPANY_AND_ID
: MSSQL_GET_BY_COMPANY_AND_ID;
const updateSql = isSqlite
? SQLITE_UPDATE_BY_COMPANY_AND_ID
: MSSQL_UPDATE_BY_COMPANY_AND_ID;
/**
* Updates the sequence settings of a number for the user's selected company.
@@ -31,6 +43,7 @@ export default function update(pool) {
* @param {string} [input.prefix] - The new prefix.
* @param {number} [input.padding] - The new padding length.
* @param {number} [input.next] - The new next value.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, type: string, prefix: string, padding: number, next: number}>} The updated number sequence.
* @throws {Error} If the number is not found or a field is invalid.
*
@@ -38,20 +51,17 @@ export default function update(pool) {
* const number = await store.numbers.update(42, 3, { prefix: 'A-', padding: 4, next: 100 });
* // => { id, type, prefix, padding, next }
*/
return async (googleAuthId, id, input = {}) => {
return async (googleAuthId, id, input = {}, tx) => {
if (!Number.isFinite(id)) {
throw new Error('Number not found');
}
const { companyId } = await access(googleAuthId);
const runner = makeRunner(provider, tx);
const { companyId } = await access(googleAuthId, tx);
const existing = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.query(GET_BY_COMPANY_AND_ID);
const existing = await runner.query(getSql, { id, companyId });
const current = existing.recordset[0];
const current = existing.rows[0];
if (!current) {
throw new Error('Number not found');
}
@@ -82,16 +92,15 @@ export default function update(pool) {
patch.next = next;
}
const result = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.input('prefix', patch.prefix)
.input('padding', patch.padding)
.input('next', patch.next)
.query(UPDATE_BY_COMPANY_AND_ID);
const result = await runner.query(updateSql, {
id,
companyId,
prefix: patch.prefix,
padding: patch.padding,
next: patch.next,
});
const row = result.recordset[0];
const row = result.rows[0];
if (!row) {
throw new Error('Number not found');
}