From 760f074b4eee9efb07059ebcff20ad2498e1f2fd Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 8 Oct 2026 21:50:38 +0200 Subject: [PATCH] u --- API.md | 2 +- API.order.md | 10 +- src/providers/mssql/queries/manifestDoc.js | 40 ++++++++ src/providers/sqlite3/queries/manifestDoc.js | 37 ++++++- src/store/order/list.js | 102 +++++++++++++++++-- 5 files changed, 177 insertions(+), 14 deletions(-) diff --git a/API.md b/API.md index e7a9c78..b56a045 100644 --- a/API.md +++ b/API.md @@ -181,7 +181,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - `create(googleAuthId: number, body: {number: string, date: string, entityId?: number, externalNumber?: string, lines?: Array<{sku?: string, name?: string, pu?: number, amount: number, net: number, rebate?: number, taxrate: number, comment?: string|null}>}, tx?: object): Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>` - `delete(googleAuthId: number, id: number, tx?: object): Promise` - `getById(googleAuthId: number, id: number, tx?: object): Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>` - - `list(googleAuthId: number, tx?: object): Promise>` + - `list(googleAuthId: number, opts?: {offset?: number, limit?: number}, tx?: object): Promise<{orders: Array<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, createdAt: string|null, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>, total: number, offset: number, limit: number}|Array>` - `update(googleAuthId: number, id: number, patch: {number?: string, date?: string, entityId?: number, externalNumber?: string, lines?: Array<{sku?: string, name?: string, pu?: number, amount: number, net: number, rebate?: number, taxrate: number, comment?: string|null}>}, tx?: object): Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>` - [manifestattachment](API.manifestattachment.md) - `getById(googleAuthId: number, manifestId: number, id: number, tx?: object): Promise<{id: number, manifestId: number, fileId: number, sort: number, hash: string, mimetype: string, name: string, size: number, created: Date, modified: Date, content: Buffer, thumbavif: Buffer|null, thumbx: number|null, thumby: number|null, meta: object|null}>` diff --git a/API.order.md b/API.order.md index a5979ce..cb2f109 100644 --- a/API.order.md +++ b/API.order.md @@ -104,17 +104,23 @@ Lists the orders of the user's selected company. | Name | Type | Description | | --- | --- | --- | | `googleAuthId` | `number` | The id of the Google auth user. | +| `[opts]` | `object` | Optional pagination options. | +| `[opts.offset=0]` | `number` | Number of orders to skip. | +| `[opts.limit=20]` | `number` | Number of orders to return. | | `[tx]` | `object` | Optional transaction. | **Returns** -`Promise>` — The orders. +`Promise<{orders: Array<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, createdAt: string|null, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>, total: number, offset: number, limit: number}|Array>` — The orders. **Example** ```js +const result = await store.order.list(42, { offset: 0, limit: 20 }); +// => { orders: [...], total: 100, offset: 0, limit: 20 } + const orders = await store.order.list(42); -// => [{ id, number, type, entityId, externalNumber, date }] +// => [{ id, number, type, entityId, externalNumber, date, createdAt, lines: [...] }] ``` ## `order.update` diff --git a/src/providers/mssql/queries/manifestDoc.js b/src/providers/mssql/queries/manifestDoc.js index e67810c..b45c68f 100644 --- a/src/providers/mssql/queries/manifestDoc.js +++ b/src/providers/mssql/queries/manifestDoc.js @@ -21,6 +21,46 @@ WHERE m.company_id = @companyId ORDER BY m.[date] DESC, m.id DESC `; +export const COUNT_MANIFESTS = ` +SELECT COUNT(*) AS total +FROM manifest m +WHERE m.company_id = @companyId + AND m.[type] = @type +`; + +export const LIST_MANIFESTS_PAGED = ` +SELECT + m.id, + m.[number], + m.[type], + m.entity_id AS entityId, + m.external_number AS externalNumber, + m.[date], + m.created_at AS createdAt +FROM manifest m +WHERE m.company_id = @companyId + AND m.[type] = @type +ORDER BY m.[date] DESC, m.id DESC +OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY +`; + +export const GET_MANIFEST_LINES_BY_MANIFEST_IDS = ` +SELECT + ma.id, + ma.manifest_id AS manifestId, + ma.sku, + ma.name, + ma.pu, + ma.amount, + ma.net, + ma.rebate, + ma.taxrate, + ma.comment +FROM manifestarticle ma +WHERE ma.manifest_id IN (SELECT CAST(value AS INT) FROM OPENJSON(@manifestIdsJson)) +ORDER BY ma.id +`; + export const GET_MANIFEST_HEADER = ` SELECT m.id, diff --git a/src/providers/sqlite3/queries/manifestDoc.js b/src/providers/sqlite3/queries/manifestDoc.js index a727a3e..acc7ed2 100644 --- a/src/providers/sqlite3/queries/manifestDoc.js +++ b/src/providers/sqlite3/queries/manifestDoc.js @@ -3,7 +3,40 @@ import { translateQueries } from './translate.js'; const queries = translateQueries(source, 'manifestDoc'); export const { - CHECK_ARTICLE_IS_PARENT, CREATE_LINE, CREATE_MANIFEST, DELETE_LINES_BY_MANIFEST, + CHECK_ARTICLE_IS_PARENT, COUNT_MANIFESTS, CREATE_LINE, CREATE_MANIFEST, DELETE_LINES_BY_MANIFEST, DELETE_MANIFEST, GET_MANIFEST_HEADER, GET_MANIFEST_LINES, LIST_MANIFESTS, ORDER_TYPE, QUOTE_TYPE, UPDATE_MANIFEST, -} = queries; \ No newline at end of file +} = queries; + +export const LIST_MANIFESTS_PAGED = ` +SELECT + m.id, + m."number", + m."type", + m.entity_id AS entityId, + m.external_number AS externalNumber, + m."date", + m.created_at AS createdAt +FROM manifest m +WHERE m.company_id = @companyId + AND m."type" = @type +ORDER BY m."date" DESC, m.id DESC +LIMIT @limit OFFSET @offset +`; + +export const GET_MANIFEST_LINES_BY_MANIFEST_IDS = ` +SELECT + ma.id, + ma.manifest_id AS manifestId, + ma.sku, + ma.name, + ma.pu, + ma.amount, + ma.net, + ma.rebate, + ma.taxrate, + ma.comment +FROM manifestarticle ma +WHERE ma.manifest_id IN (SELECT value FROM json_each(@manifestIdsJson)) +ORDER BY ma.id +`; \ No newline at end of file diff --git a/src/store/order/list.js b/src/store/order/list.js index a20b760..89a4f8e 100644 --- a/src/store/order/list.js +++ b/src/store/order/list.js @@ -1,42 +1,126 @@ import { + COUNT_MANIFESTS as MSSQL_COUNT, + GET_MANIFEST_LINES_BY_MANIFEST_IDS as MSSQL_LINES_BY_MANIFEST_IDS, LIST_MANIFESTS as MSSQL_LIST, + LIST_MANIFESTS_PAGED as MSSQL_LIST_PAGED, ORDER_TYPE as MSSQL_ORDER_TYPE, } from '../../providers/mssql/queries/manifestDoc.js'; import { + COUNT_MANIFESTS as SQLITE_COUNT, + GET_MANIFEST_LINES_BY_MANIFEST_IDS as SQLITE_LINES_BY_MANIFEST_IDS, LIST_MANIFESTS as SQLITE_LIST, + LIST_MANIFESTS_PAGED as SQLITE_LIST_PAGED, ORDER_TYPE as SQLITE_ORDER_TYPE, } from '../../providers/sqlite3/queries/manifestDoc.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import { shapeSummary } from '../manifestDoc/helpers.js'; +import { shapeLine } from '../manifestLines.js'; import makeRunner from '../runner.js'; export default function list(provider) { const access = resolveSelectedCompanyAccess(provider); const isSqlite = provider?.dialect === 'sqlite3'; const sql = isSqlite ? SQLITE_LIST : MSSQL_LIST; + const sqlCount = isSqlite ? SQLITE_COUNT : MSSQL_COUNT; + const sqlPaged = isSqlite ? SQLITE_LIST_PAGED : MSSQL_LIST_PAGED; + const sqlLinesByManifestIds = isSqlite + ? SQLITE_LINES_BY_MANIFEST_IDS + : MSSQL_LINES_BY_MANIFEST_IDS; const orderType = isSqlite ? SQLITE_ORDER_TYPE : MSSQL_ORDER_TYPE; /** * Lists the orders of the user's selected company. * * @param {number} googleAuthId - The id of the Google auth user. + * @param {object} [opts] - Optional pagination options. + * @param {number} [opts.offset=0] - Number of orders to skip. + * @param {number} [opts.limit=20] - Number of orders to return. * @param {object} [tx] - Optional transaction. - * @returns {Promise>} The orders. + * @returns {Promise<{orders: Array<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, createdAt: string|null, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>, total: number, offset: number, limit: number}|Array>} The orders. * * @example + * const result = await store.order.list(42, { offset: 0, limit: 20 }); + * // => { orders: [...], total: 100, offset: 0, limit: 20 } + * * const orders = await store.order.list(42); - * // => [{ id, number, type, entityId, externalNumber, date }] + * // => [{ id, number, type, entityId, externalNumber, date, createdAt, lines: [...] }] */ - return async (googleAuthId, tx) => { - const runner = makeRunner(provider, tx); + return async (googleAuthId, opts, tx) => { + let offset = null; + let limit = null; + let isPaged = false; + let resolvedTx = tx; + + if (opts != null && typeof opts === 'object' && !opts._isTx) { + if (opts.offset !== undefined || opts.limit !== undefined) { + isPaged = true; + const parsedOffset = Number(opts.offset ?? 0); + const parsedLimit = Number(opts.limit ?? 20); + if (!Number.isInteger(parsedOffset) || parsedOffset < 0) { + throw new Error('Invalid offset'); + } + if (!Number.isInteger(parsedLimit) || parsedLimit < 1) { + throw new Error('Invalid limit'); + } + offset = parsedOffset; + limit = parsedLimit; + } + } else if (opts != null) { + resolvedTx = opts; + } + + const runner = makeRunner(provider, resolvedTx); const { companyId } = await access(googleAuthId, runner); - const result = await runner.query(sql, { - companyId, - type: orderType, - }); + let manifestRows; + let total = 0; - return result.rows.map((row) => shapeSummary(orderType, row)); + if (isPaged) { + const [countResult, listResult] = await Promise.all([ + runner.query(sqlCount, { companyId, type: orderType }), + runner.query(sqlPaged, { companyId, type: orderType, offset, limit }), + ]); + total = Number(countResult.rows[0]?.total ?? 0); + manifestRows = listResult.rows; + } else { + const result = await runner.query(sql, { companyId, type: orderType }); + manifestRows = result.rows; + total = manifestRows.length; + } + + const manifestIds = manifestRows.map((r) => r.id); + const linesByManifestId = new Map(); + + if (manifestIds.length > 0) { + const linesResult = await runner.query(sqlLinesByManifestIds, { + manifestIdsJson: JSON.stringify(manifestIds), + }); + for (const row of linesResult.rows) { + let lines = linesByManifestId.get(row.manifestId); + if (!lines) { + lines = []; + linesByManifestId.set(row.manifestId, lines); + } + lines.push(shapeLine(row)); + } + } + + const orders = manifestRows.map((row) => ({ + ...shapeSummary(orderType, row), + lines: linesByManifestId.get(row.id) ?? [], + })); + + if (isPaged) { + return { + orders, + total, + offset, + limit, + }; + } + + return orders; }; } +