u
This commit is contained in:
@@ -1,37 +1,64 @@
|
||||
import { LIST_BY_ARTICLE_ID as MSSQL_QUERY, LIST_BY_ARTICLE_ID_LIMITED as MSSQL_QUERY_LIMITED } from '../../providers/mssql/queries/articlehistory.js';
|
||||
import { LIST_BY_ARTICLE_ID as SQLITE_QUERY, LIST_BY_ARTICLE_ID_LIMITED as SQLITE_QUERY_LIMITED } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function getHistoryBySku(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const sql = isSqlite ? SQLITE_QUERY : MSSQL_QUERY;
|
||||
const sqlLimited = isSqlite ? SQLITE_QUERY_LIMITED : MSSQL_QUERY_LIMITED;
|
||||
|
||||
/**
|
||||
* Returns the change history of an article by its SKU.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} sku - The article SKU.
|
||||
* @param {object} [opts] - Optional options.
|
||||
* @param {number} [opts.limit] - Maximum number of entries to return (newest first).
|
||||
* @param {object} [opts] - Optional pagination and filter options.
|
||||
* @param {number} [opts.offset=0] - Number of entries to skip.
|
||||
* @param {number} [opts.limit=50] - Number of entries to return.
|
||||
* @param {string} [opts.search] - Optional search filter for history text.
|
||||
* @param {number} [opts.seekMonths] - Negative integer cutoff months ago.
|
||||
* @param {boolean} [opts.paged] - Explicitly return paged result object.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>} The article history entries.
|
||||
* @returns {Promise<{history: Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>} The article history entries.
|
||||
*
|
||||
* @example
|
||||
* const result = await store.article.getHistoryBySku(42, 'SKU-1', { offset: 0, limit: 50, search: 'Tee' });
|
||||
* // => { history: [...], total, unfilteredTotal, offset, limit }
|
||||
*
|
||||
* const history = await store.article.getHistoryBySku(42, 'SKU-1');
|
||||
* // => [{ id, companyuserId, date, text, meta }]
|
||||
*
|
||||
* const recent = await store.article.getHistoryBySku(42, 'SKU-1', { limit: 5 });
|
||||
* // => up to 5 most recent entries
|
||||
*/
|
||||
return async (googleAuthId, sku, opts, tx) => {
|
||||
// Support legacy call signature: getHistoryBySku(googleAuthId, sku, tx)
|
||||
let limit = null;
|
||||
let offset = 0;
|
||||
let limit = 50;
|
||||
let isPaged = false;
|
||||
let resolvedTx = tx;
|
||||
|
||||
const hasFilter = opts != null && typeof opts === 'object' && !opts._isTx && (
|
||||
opts.offset !== undefined ||
|
||||
opts.paged === true ||
|
||||
(typeof opts.search === 'string' && opts.search.trim() !== '') ||
|
||||
opts.seekMonths !== undefined
|
||||
);
|
||||
|
||||
if (opts != null && typeof opts === 'object' && !opts._isTx) {
|
||||
limit = opts.limit != null ? Number(opts.limit) : null;
|
||||
if (hasFilter || opts.paged === true) {
|
||||
isPaged = true;
|
||||
if (opts.offset !== undefined) {
|
||||
const parsedOffset = Number(opts.offset);
|
||||
if (!Number.isInteger(parsedOffset) || parsedOffset < 0) {
|
||||
throw new Error('Invalid offset');
|
||||
}
|
||||
offset = parsedOffset;
|
||||
}
|
||||
if (opts.limit !== undefined) {
|
||||
const parsedLimit = Number(opts.limit);
|
||||
if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
|
||||
throw new Error('Invalid limit');
|
||||
}
|
||||
limit = parsedLimit;
|
||||
}
|
||||
} else if (opts.limit != null) {
|
||||
limit = Number(opts.limit);
|
||||
}
|
||||
} else if (opts != null) {
|
||||
resolvedTx = opts;
|
||||
}
|
||||
@@ -39,17 +66,124 @@ export default function getHistoryBySku(provider) {
|
||||
const runner = makeRunner(provider, resolvedTx);
|
||||
const { articleId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
let result;
|
||||
if (limit != null && Number.isInteger(limit) && limit > 0) {
|
||||
result = await runner.query(sqlLimited, { articleId, limit });
|
||||
} else {
|
||||
result = await runner.query(sql, { articleId });
|
||||
const conditions = ['ah.article_id = @articleId'];
|
||||
const params = {
|
||||
articleId,
|
||||
};
|
||||
|
||||
if (opts?.seekMonths !== undefined) {
|
||||
if (typeof opts.seekMonths !== 'number' || !Number.isInteger(opts.seekMonths)) {
|
||||
throw new Error('Invalid seekMonths');
|
||||
}
|
||||
const d = new Date();
|
||||
d.setMonth(d.getMonth() - Math.abs(opts.seekMonths));
|
||||
params.dateTo = d.toISOString();
|
||||
conditions.push(`ah.${isSqlite ? '"date"' : '[date]'} <= @dateTo`);
|
||||
}
|
||||
|
||||
if (typeof opts?.search === 'string' && opts.search.trim() !== '') {
|
||||
params.searchPattern = `%${opts.search.trim()}%`;
|
||||
conditions.push(`ah.${isSqlite ? '"text"' : '[text]'} LIKE @searchPattern`);
|
||||
}
|
||||
|
||||
const whereClause = conditions.join('\n AND ');
|
||||
|
||||
const sqlCount = `
|
||||
SELECT COUNT(*) AS total
|
||||
FROM articlehistory ah
|
||||
WHERE ${whereClause}
|
||||
`;
|
||||
|
||||
const sqlCountBase = `
|
||||
SELECT COUNT(*) AS total
|
||||
FROM articlehistory ah
|
||||
WHERE ah.article_id = @articleId
|
||||
`;
|
||||
|
||||
let sqlList;
|
||||
if (isSqlite) {
|
||||
sqlList = `
|
||||
SELECT
|
||||
ah.id,
|
||||
ah.companyuser_id AS companyuserId,
|
||||
ah."date",
|
||||
ah."text",
|
||||
ah.meta
|
||||
FROM articlehistory ah
|
||||
WHERE ${whereClause}
|
||||
ORDER BY ah."date" DESC, ah.id DESC
|
||||
${isPaged ? 'LIMIT @limit OFFSET @offset' : (limit != null && limit > 0 ? 'LIMIT @limit' : '')}
|
||||
`;
|
||||
} else {
|
||||
sqlList = `
|
||||
SELECT
|
||||
${!isPaged && limit != null && limit > 0 ? 'TOP (@limit)' : ''}
|
||||
ah.id,
|
||||
ah.companyuser_id AS companyuserId,
|
||||
ah.[date],
|
||||
ah.[text],
|
||||
ah.meta
|
||||
FROM articlehistory ah
|
||||
WHERE ${whereClause}
|
||||
ORDER BY ah.[date] DESC, ah.id DESC
|
||||
${isPaged ? 'OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY' : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
if (isPaged) {
|
||||
params.offset = offset;
|
||||
params.limit = limit;
|
||||
|
||||
const hasUserFilters = (
|
||||
opts?.seekMonths !== undefined ||
|
||||
(typeof opts?.search === 'string' && opts.search.trim() !== '')
|
||||
);
|
||||
|
||||
const [countResult, listResult, unfilteredCountResult] = await Promise.all([
|
||||
runner.query(sqlCount, params),
|
||||
runner.query(sqlList, params),
|
||||
hasUserFilters ? runner.query(sqlCountBase, { articleId }) : null,
|
||||
]);
|
||||
|
||||
const total = Number(countResult.rows[0]?.total ?? 0);
|
||||
const unfilteredTotal = hasUserFilters
|
||||
? Number(unfilteredCountResult?.rows[0]?.total ?? 0)
|
||||
: total;
|
||||
|
||||
return {
|
||||
history: listResult.rows.map((row) => ({
|
||||
id: row.id,
|
||||
companyuserId: row.companyuserId ?? null,
|
||||
date:
|
||||
row.date instanceof Date
|
||||
? row.date.toISOString()
|
||||
: row.date
|
||||
? new Date(row.date).toISOString()
|
||||
: null,
|
||||
text: row.text,
|
||||
meta: row.meta ?? null,
|
||||
})),
|
||||
total,
|
||||
unfilteredTotal,
|
||||
offset,
|
||||
limit,
|
||||
};
|
||||
}
|
||||
|
||||
if (limit != null && limit > 0) {
|
||||
params.limit = limit;
|
||||
}
|
||||
|
||||
const result = await runner.query(sqlList, params);
|
||||
return result.rows.map((row) => ({
|
||||
id: row.id,
|
||||
companyuserId: row.companyuserId ?? null,
|
||||
date: row.date,
|
||||
date:
|
||||
row.date instanceof Date
|
||||
? row.date.toISOString()
|
||||
: row.date
|
||||
? new Date(row.date).toISOString()
|
||||
: null,
|
||||
text: row.text,
|
||||
meta: row.meta ?? null,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user