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,39 +1,44 @@
import { DELETE_BY_COMPANY_AND_SI } from '../../providers/mssql/queries/unit.js';
import { DELETE_BY_COMPANY_AND_SI as MSSQL_DELETE } from '../../providers/mssql/queries/unit.js';
import { DELETE_BY_COMPANY_AND_SI as SQLITE_DELETE } from '../../providers/sqlite3/queries/unit.js';
import { isForeignKeyConstraintError } from '../../providers/provider.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js';
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
export default function del(provider) {
const access = resolveSelectedCompanyAccess(provider);
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
/**
* Deletes a unit for the user's selected company by its SI code.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} si - The SI code of the unit to delete.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<void>}
* @throws {Error} If the unit is not found.
*
* @example
* await store.unit.delete(42, 'l');
*/
return async (googleAuthId, si) => {
return async (googleAuthId, si, tx) => {
if (!si?.trim()) {
throw new Error('Unit not found');
}
const { companyId } = await access(googleAuthId);
const runner = makeRunner(provider, tx);
const { companyId } = await access(googleAuthId, tx);
try {
const result = await pool
.request()
.input('companyId', companyId)
.input('si', si.trim())
.query(DELETE_BY_COMPANY_AND_SI);
const result = await runner.query(sql, {
companyId,
si: si.trim(),
});
if (!result.rowsAffected[0]) {
if (!result.rowsAffected) {
throw new Error('Unit not found');
}
} catch (err) {
if (err.number === 547) {
if (isForeignKeyConstraintError(err)) {
throw new Error('Unit in use');
}
throw err;

View File

@@ -1,46 +1,68 @@
import { CREATE, GET_BY_SI } from '../../providers/mssql/queries/unit.js';
import {
CREATE as CREATE_UNITTEXT,
GET_BY_UNIT_AND_LANGUAGE,
CREATE as MSSQL_CREATE,
GET_BY_SI as MSSQL_GET_BY_SI,
} from '../../providers/mssql/queries/unit.js';
import {
CREATE as SQLITE_CREATE,
GET_BY_SI as SQLITE_GET_BY_SI,
} from '../../providers/sqlite3/queries/unit.js';
import {
CREATE as MSSQL_CREATE_UNITTEXT,
GET_BY_UNIT_AND_LANGUAGE as MSSQL_GET_BY_UNIT_AND_LANGUAGE,
} from '../../providers/mssql/queries/unittext.js';
import request from '../request.js';
import {
CREATE as SQLITE_CREATE_UNITTEXT,
GET_BY_UNIT_AND_LANGUAGE as SQLITE_GET_BY_UNIT_AND_LANGUAGE,
} from '../../providers/sqlite3/queries/unittext.js';
import makeRunner from '../runner.js';
const DEFAULT_SI = 'pc';
const DEFAULT_SYMBOL = 'pc';
const DEFAULT_NAME = 'Piece';
export default function ensureDefaultUnit(pool) {
return async (tx, languageId, companyId) => {
const existing = await request(pool, tx)
.input('companyId', companyId)
.input('si', DEFAULT_SI)
.query(GET_BY_SI);
export default function ensureDefaultUnit(provider) {
const isSqlite = provider?.dialect === 'sqlite3';
const getBySiSql = isSqlite ? SQLITE_GET_BY_SI : MSSQL_GET_BY_SI;
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
const getUnitTextSql = isSqlite
? SQLITE_GET_BY_UNIT_AND_LANGUAGE
: MSSQL_GET_BY_UNIT_AND_LANGUAGE;
const createUnitTextSql = isSqlite
? SQLITE_CREATE_UNITTEXT
: MSSQL_CREATE_UNITTEXT;
let unitId = existing.recordset[0]?.id;
return async (tx, languageId, companyId) => {
const runner = makeRunner(provider, tx);
const existing = await runner.query(getBySiSql, {
companyId,
si: DEFAULT_SI,
});
let unitId = existing.rows[0]?.id;
if (!unitId) {
const created = await request(pool, tx)
.input('companyId', companyId)
.input('si', DEFAULT_SI)
.input('basesi', DEFAULT_SI)
.input('basesifactor', 1)
.query(CREATE);
unitId = created.recordset[0].id;
const created = await runner.query(createSql, {
companyId,
si: DEFAULT_SI,
basesi: DEFAULT_SI,
basesifactor: 1,
});
unitId = created.rows[0]?.id ?? created.insertId;
}
if (languageId != null) {
const text = await request(pool, tx)
.input('unitId', unitId)
.input('languageId', languageId)
.query(GET_BY_UNIT_AND_LANGUAGE);
const text = await runner.query(getUnitTextSql, {
unitId,
languageId,
});
if (!text.recordset[0]) {
await request(pool, tx)
.input('unitId', unitId)
.input('languageId', languageId)
.input('symbol', DEFAULT_SYMBOL)
.input('name', DEFAULT_NAME)
.query(CREATE_UNITTEXT);
if (!text.rows[0]) {
await runner.query(createUnitTextSql, {
unitId,
languageId,
symbol: DEFAULT_SYMBOL,
name: DEFAULT_NAME,
});
}
}

View File

@@ -1,29 +1,36 @@
import { LIST_BY_COMPANY } from '../../providers/mssql/queries/unit.js';
import { LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY } from '../../providers/mssql/queries/unit.js';
import { LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY } from '../../providers/sqlite3/queries/unit.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeUnit from './shapeUnit.js';
import makeRunner from '../runner.js';
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 units available for 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, si: string, basesi: string, basesifactor: number, symbol: string, name: string}>>} The units.
*
* @example
* const units = await store.unit.list(42);
* // => [{ id, si, basesi, basesifactor, symbol, name }]
*/
return async (googleAuthId) => {
const { companyId, languageId } = await access(googleAuthId);
return async (googleAuthId, tx) => {
const runner = makeRunner(provider, tx);
const { companyId, languageId } = await access(googleAuthId, tx);
const result = await pool
.request()
.input('companyId', companyId)
.input('languageId', languageId)
.query(LIST_BY_COMPANY);
const result = await runner.query(sql, {
companyId,
languageId,
});
return result.recordset.map(shapeUnit);
return result.rows.map(shapeUnit);
};
}

View File

@@ -1,17 +1,28 @@
import {
CREATE as CREATE_UNIT,
GET_BY_SI,
GET_FULL_BY_SI,
UPDATE_BY_SI,
CREATE as MSSQL_CREATE_UNIT,
GET_BY_SI as MSSQL_GET_BY_SI,
GET_FULL_BY_SI as MSSQL_GET_FULL_BY_SI,
UPDATE_BY_SI as MSSQL_UPDATE_BY_SI,
} from '../../providers/mssql/queries/unit.js';
import {
CREATE as CREATE_UNITTEXT,
GET_BY_UNIT_AND_LANGUAGE,
UPDATE_BY_UNIT_AND_LANGUAGE,
CREATE as SQLITE_CREATE_UNIT,
GET_BY_SI as SQLITE_GET_BY_SI,
GET_FULL_BY_SI as SQLITE_GET_FULL_BY_SI,
UPDATE_BY_SI as SQLITE_UPDATE_BY_SI,
} from '../../providers/sqlite3/queries/unit.js';
import {
CREATE as MSSQL_CREATE_UNITTEXT,
GET_BY_UNIT_AND_LANGUAGE as MSSQL_GET_BY_UNIT_AND_LANGUAGE,
UPDATE_BY_UNIT_AND_LANGUAGE as MSSQL_UPDATE_BY_UNIT_AND_LANGUAGE,
} from '../../providers/mssql/queries/unittext.js';
import {
CREATE as SQLITE_CREATE_UNITTEXT,
GET_BY_UNIT_AND_LANGUAGE as SQLITE_GET_BY_UNIT_AND_LANGUAGE,
UPDATE_BY_UNIT_AND_LANGUAGE as SQLITE_UPDATE_BY_UNIT_AND_LANGUAGE,
} from '../../providers/sqlite3/queries/unittext.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import request from '../request.js';
import shapeUnit from './shapeUnit.js';
import makeRunner from '../runner.js';
function validateUnit({ si, basesi, basesifactor, symbol, name }) {
if (!si?.trim() || !basesi?.trim() || !symbol?.trim() || !name?.trim()) {
@@ -30,8 +41,23 @@ function validateUnit({ si, basesi, basesifactor, symbol, name }) {
};
}
export default function upsert(pool) {
const access = resolveSelectedCompanyAccess(pool);
export default function upsert(provider) {
const access = resolveSelectedCompanyAccess(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const getBySiSql = isSqlite ? SQLITE_GET_BY_SI : MSSQL_GET_BY_SI;
const getFullSql = isSqlite ? SQLITE_GET_FULL_BY_SI : MSSQL_GET_FULL_BY_SI;
const createUnitSql = isSqlite ? SQLITE_CREATE_UNIT : MSSQL_CREATE_UNIT;
const updateUnitSql = isSqlite ? SQLITE_UPDATE_BY_SI : MSSQL_UPDATE_BY_SI;
const getUnitTextSql = isSqlite
? SQLITE_GET_BY_UNIT_AND_LANGUAGE
: MSSQL_GET_BY_UNIT_AND_LANGUAGE;
const createUnitTextSql = isSqlite
? SQLITE_CREATE_UNITTEXT
: MSSQL_CREATE_UNITTEXT;
const updateUnitTextSql = isSqlite
? SQLITE_UPDATE_BY_UNIT_AND_LANGUAGE
: MSSQL_UPDATE_BY_UNIT_AND_LANGUAGE;
/**
* Creates or updates a unit for the user's selected company, keyed by its SI code.
@@ -43,6 +69,7 @@ export default function upsert(pool) {
* @param {number} input.basesifactor - The base SI factor (must be > 0).
* @param {string} input.symbol - The unit symbol.
* @param {string} input.name - The unit name.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, si: string, basesi: string, basesifactor: number, symbol: string, name: string}>} The upserted unit.
* @throws {Error} If the unit is invalid.
*
@@ -50,83 +77,79 @@ export default function upsert(pool) {
* const unit = await store.unit.upsert(42, { si: 'l', basesi: 'm3', basesifactor: 0.001, symbol: 'L', name: 'Liter' });
* // => { id, si, basesi, basesifactor, symbol, name }
*/
return async (googleAuthId, input) => {
return async (googleAuthId, input, tx) => {
const unit = validateUnit(input);
const { companyId, languageId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();
const runUpsert = async (runner) => {
const { companyId, languageId } = await access(googleAuthId, runner);
try {
const existing = await request(pool, tx)
.input('companyId', companyId)
.input('si', unit.si)
.query(GET_BY_SI);
const existing = await runner.query(getBySiSql, {
companyId,
si: unit.si,
});
let unitId = existing.recordset[0]?.id;
let unitId = existing.rows[0]?.id;
if (!unitId) {
const created = await request(pool, tx)
.input('companyId', companyId)
.input('si', unit.si)
.input('basesi', unit.basesi)
.input('basesifactor', unit.basesifactor)
.query(CREATE_UNIT);
unitId = created.recordset[0].id;
const created = await runner.query(createUnitSql, {
companyId,
si: unit.si,
basesi: unit.basesi,
basesifactor: unit.basesifactor,
});
unitId = created.rows[0]?.id ?? created.insertId;
await request(pool, tx)
.input('unitId', unitId)
.input('languageId', languageId)
.input('symbol', unit.symbol)
.input('name', unit.name)
.query(CREATE_UNITTEXT);
await runner.query(createUnitTextSql, {
unitId,
languageId,
symbol: unit.symbol,
name: unit.name,
});
} else {
await request(pool, tx)
.input('companyId', companyId)
.input('si', unit.si)
.input('basesi', unit.basesi)
.input('basesifactor', unit.basesifactor)
.query(UPDATE_BY_SI);
await runner.query(updateUnitSql, {
companyId,
si: unit.si,
basesi: unit.basesi,
basesifactor: unit.basesifactor,
});
const text = await request(pool, tx)
.input('unitId', unitId)
.input('languageId', languageId)
.query(GET_BY_UNIT_AND_LANGUAGE);
const text = await runner.query(getUnitTextSql, {
unitId,
languageId,
});
if (text.recordset[0]) {
await request(pool, tx)
.input('unitId', unitId)
.input('languageId', languageId)
.input('symbol', unit.symbol)
.input('name', unit.name)
.query(UPDATE_BY_UNIT_AND_LANGUAGE);
if (text.rows[0]) {
await runner.query(updateUnitTextSql, {
unitId,
languageId,
symbol: unit.symbol,
name: unit.name,
});
} else {
await request(pool, tx)
.input('unitId', unitId)
.input('languageId', languageId)
.input('symbol', unit.symbol)
.input('name', unit.name)
.query(CREATE_UNITTEXT);
await runner.query(createUnitTextSql, {
unitId,
languageId,
symbol: unit.symbol,
name: unit.name,
});
}
}
await tx.commit();
} catch (err) {
try {
await tx.rollback();
} catch {
// ignore
}
throw err;
const full = await runner.query(getFullSql, {
companyId,
si: unit.si,
languageId,
});
return shapeUnit(full.rows[0]);
};
if (tx) {
return runUpsert(makeRunner(provider, tx));
}
const result = await pool
.request()
.input('companyId', companyId)
.input('si', unit.si)
.input('languageId', languageId)
.query(GET_FULL_BY_SI);
return shapeUnit(result.recordset[0]);
if (typeof provider?.transaction === 'function') {
return provider.transaction(runUpsert);
}
return runUpsert(makeRunner(provider));
};
}