This commit is contained in:
seb
2026-09-27 21:01:30 +02:00
parent f9c570a036
commit 0cafa11a32
34 changed files with 2010 additions and 1534 deletions

View File

@@ -1,17 +1,24 @@
import { CREATE as MSSQL_CREATE } from '../../providers/mssql/queries/warehouse.js';
import { CREATE as SQLITE_CREATE } from '../../providers/sqlite3/queries/warehouse.js';
import {
CREATE as MSSQL_CREATE,
GET_NEXT_SORT as MSSQL_GET_NEXT_SORT,
} from '../../providers/mssql/queries/warehouse.js';
import {
CREATE as SQLITE_CREATE,
GET_NEXT_SORT as SQLITE_GET_NEXT_SORT,
} from '../../providers/sqlite3/queries/warehouse.js';
import { isUniqueConstraintError } from '../../providers/provider.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js';
function shape(row) {
return { id: row.id, name: row.name };
return { id: row.id, name: row.name, sort: row.sort };
}
export default function create(provider) {
const access = resolveSelectedCompanyAccess(provider);
const sql =
provider?.dialect === 'sqlite3' ? SQLITE_CREATE : MSSQL_CREATE;
const isSqlite = provider?.dialect === 'sqlite3';
const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT;
const sql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
/**
* Creates a new warehouse for the user's selected company.
@@ -19,15 +26,16 @@ export default function create(provider) {
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - Warehouse input.
* @param {string} input.name - The warehouse name.
* @param {number} [input.sort] - Optional sort order (auto-increments if omitted).
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, name: string}>} The created warehouse.
* @throws {Error} If name is missing or already exists.
* @returns {Promise<{id: number, name: string, sort: number}>} The created warehouse.
* @throws {Error} If name is missing, sort is invalid, or name already exists.
*
* @example
* const wh = await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
* // => { id: 1, name: 'Hauptlager' }
* // => { id: 1, name: 'Hauptlager', sort: 0 }
*/
return async (googleAuthId, { name } = {}, tx) => {
return async (googleAuthId, { name, sort: explicitSort } = {}, tx) => {
const cleaned = typeof name === 'string' ? name.trim() : '';
if (!cleaned) {
throw new Error('Name required');
@@ -36,13 +44,27 @@ export default function create(provider) {
const runner = makeRunner(provider, tx);
const { companyId } = await access(googleAuthId, runner);
let sort = explicitSort;
if (sort === undefined || sort === null) {
const next = await runner.query(getSortSql, { companyId });
sort = next.rows[0]?.sort ?? 0;
} else if (!Number.isFinite(sort)) {
throw new Error('Invalid sort');
}
try {
const result = await runner.query(sql, {
companyId,
name: cleaned,
sort,
});
const row = result.rows[0] ?? { id: result.insertId, name: cleaned };
const row = result.rows[0] ?? {
id: result.insertId,
company_id: companyId,
name: cleaned,
sort,
};
return shape(row);
} catch (err) {
if (

View File

@@ -4,7 +4,7 @@ import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyA
import makeRunner from '../runner.js';
function shape(row) {
return { id: row.id, name: row.name };
return { id: row.id, name: row.name, sort: row.sort };
}
export default function getById(provider) {
@@ -18,11 +18,11 @@ export default function getById(provider) {
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The warehouse ID.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, name: string}|undefined>} The warehouse or undefined.
* @returns {Promise<{id: number, name: string, sort: number}|undefined>} The warehouse or undefined.
*
* @example
* const wh = await store.warehouse.getById(googleAuthId, 1);
* // => { id: 1, name: 'Hauptlager' }
* // => { id: 1, name: 'Hauptlager', sort: 0 }
*/
return async (googleAuthId, id, tx) => {
const numericId = Number(id);

View File

@@ -4,7 +4,7 @@ import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyA
import makeRunner from '../runner.js';
function shape(row) {
return { id: row.id, name: row.name };
return { id: row.id, name: row.name, sort: row.sort };
}
export default function list(provider) {
@@ -17,11 +17,11 @@ export default function list(provider) {
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<Array<{id: number, name: string}>>} List of warehouses.
* @returns {Promise<Array<{id: number, name: string, sort: number}>>} List of warehouses.
*
* @example
* const warehouses = await store.warehouse.list(googleAuthId);
* // => [{ id: 1, name: 'Hauptlager' }]
* // => [{ id: 1, name: 'Hauptlager', sort: 0 }]
*/
return async (googleAuthId, tx) => {
const runner = makeRunner(provider, tx);

View File

@@ -0,0 +1,76 @@
import {
LIST_IDS_BY_COMPANY as MSSQL_LIST_IDS_BY_COMPANY,
UPDATE_SORT as MSSQL_UPDATE_SORT,
} from '../../providers/mssql/queries/warehouse.js';
import {
LIST_IDS_BY_COMPANY as SQLITE_LIST_IDS_BY_COMPANY,
UPDATE_SORT as SQLITE_UPDATE_SORT,
} from '../../providers/sqlite3/queries/warehouse.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js';
export default function order(provider) {
const access = resolveSelectedCompanyAccess(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const listIdsSql = isSqlite
? SQLITE_LIST_IDS_BY_COMPANY
: MSSQL_LIST_IDS_BY_COMPANY;
const updateSortSql = isSqlite ? SQLITE_UPDATE_SORT : MSSQL_UPDATE_SORT;
/**
* Reorders the warehouses of the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The reorder input.
* @param {Array<number>} input.warehouseIds - The warehouse ids in their new order.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{warehouseIds: Array<number>}>} The applied order.
* @throws {Error} If the order is invalid.
*/
return async (googleAuthId, { warehouseIds } = {}, tx) => {
if (!Array.isArray(warehouseIds)) {
throw new Error('Invalid warehouse order');
}
const runOrder = async (runner) => {
const { companyId } = await access(googleAuthId, runner);
const items = await runner.query(listIdsSql, { companyId });
const existing = new Set(items.rows.map((r) => r.id));
if (warehouseIds.length !== existing.size) {
throw new Error('Invalid warehouse order');
}
const seen = new Set();
for (const warehouseId of warehouseIds) {
if (
!Number.isFinite(warehouseId) ||
!existing.has(warehouseId) ||
seen.has(warehouseId)
) {
throw new Error('Invalid warehouse order');
}
seen.add(warehouseId);
}
for (let sort = 0; sort < warehouseIds.length; sort++) {
await runner.query(updateSortSql, {
id: warehouseIds[sort],
companyId,
sort,
});
}
return { warehouseIds };
};
if (tx) {
return runOrder(makeRunner(provider, tx));
}
if (typeof provider?.transaction === 'function') {
return provider.transaction(runOrder);
}
return runOrder(makeRunner(provider));
};
}

View File

@@ -11,7 +11,7 @@ import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyA
import makeRunner from '../runner.js';
function shape(row) {
return { id: row.id, name: row.name };
return { id: row.id, name: row.name, sort: row.sort };
}
export default function update(provider) {
@@ -21,47 +21,66 @@ export default function update(provider) {
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
/**
* Updates a warehouse name for the user's selected company.
* Updates a warehouse name and/or sort order for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The warehouse ID to update.
* @param {object} input - Update payload.
* @param {string} input.name - The new warehouse name.
* @param {string} [input.name] - The new warehouse name.
* @param {number} [input.sort] - The new sort order.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<{id: number, name: string}>} The updated warehouse.
* @throws {Error} If warehouse not found, name is missing, or name already exists.
* @returns {Promise<{id: number, name: string, sort: number}>} The updated warehouse.
* @throws {Error} If warehouse not found, name is empty, sort is invalid, or name already exists.
*
* @example
* const updated = await store.warehouse.update(googleAuthId, 1, { name: 'Zentrallager' });
* // => { id: 1, name: 'Zentrallager' }
* const updated = await store.warehouse.update(googleAuthId, 1, { name: 'Zentrallager', sort: 1 });
* // => { id: 1, name: 'Zentrallager', sort: 1 }
*/
return async (googleAuthId, id, { name } = {}, tx) => {
return async (googleAuthId, id, input = {}, tx) => {
const numericId = Number(id);
if (!Number.isInteger(numericId) || numericId <= 0) {
throw new Error('Warehouse not found');
}
const cleaned = typeof name === 'string' ? name.trim() : '';
if (!cleaned) {
throw new Error('Name required');
}
const runner = makeRunner(provider, tx);
const { companyId } = await access(googleAuthId, runner);
const existing = await runner.query(getSql, { id: numericId, companyId });
if (!existing.rows[0]) {
const current = existing.rows[0];
if (!current) {
throw new Error('Warehouse not found');
}
let name = current.name;
if (input.name !== undefined) {
const cleaned = typeof input.name === 'string' ? input.name.trim() : '';
if (!cleaned) {
throw new Error('Name required');
}
name = cleaned;
}
let sort = current.sort;
if (input.sort !== undefined) {
if (!Number.isFinite(input.sort)) {
throw new Error('Invalid sort');
}
sort = input.sort;
}
if (input.name === undefined && input.sort === undefined) {
throw new Error('Name required');
}
try {
await runner.query(updateSql, {
id: numericId,
companyId,
name: cleaned,
name,
sort,
});
return shape({ id: numericId, name: cleaned });
return shape({ id: numericId, name, sort });
} catch (err) {
if (
isUniqueConstraintError(err) ||