181 lines
6.2 KiB
JavaScript
181 lines
6.2 KiB
JavaScript
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
|
import makeRunner from '../runner.js';
|
|
|
|
/**
|
|
* Lists ledger entries for the user's selected company across all articles,
|
|
* supporting pagination, date cutoff via seekMonths, and search.
|
|
*
|
|
* @param {import('../../providers/provider.js').Provider} provider
|
|
* @returns {(googleAuthId: number, opts?: {offset?: number, limit?: number, search?: string, seekMonths?: number}, tx?: object) => Promise<{ledger: Array<{id: number, date: string, sku: string|null, name: string|null, transaction_type: string, transaction_text: string, amount: number}>, total: number, unfilteredTotal: number, offset: number, limit: number}>}
|
|
*
|
|
* @example
|
|
* const res = await store.ledger.listHistory(googleAuthId, { offset: 0, limit: 50, search: 'kjh', seekMonths: -1 });
|
|
* // => { ledger: [...], total, unfilteredTotal, offset, limit }
|
|
*/
|
|
export default function listHistory(provider) {
|
|
const access = resolveSelectedCompanyAccess(provider);
|
|
const isSqlite = provider?.dialect === 'sqlite3';
|
|
|
|
return async (googleAuthId, opts, tx) => {
|
|
let offset = 0;
|
|
let limit = 50;
|
|
let isPaged = true;
|
|
let resolvedTx = tx;
|
|
|
|
if (opts != null && typeof opts === 'object' && !opts._isTx) {
|
|
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 != null) {
|
|
resolvedTx = opts;
|
|
}
|
|
|
|
const runner = makeRunner(provider, resolvedTx);
|
|
const { companyId, languageId } = await access(googleAuthId, runner);
|
|
|
|
const conditions = ['m.company_id = @companyId'];
|
|
const params = {
|
|
companyId,
|
|
languageId,
|
|
};
|
|
|
|
// Date cutoff via seekMonths (strictly integer)
|
|
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(`l.created_at <= @dateTo`);
|
|
}
|
|
|
|
// Free-text search
|
|
if (typeof opts?.search === 'string' && opts.search.trim() !== '') {
|
|
params.searchPattern = `%${opts.search.trim()}%`;
|
|
const numCol = isSqlite ? 'm."number"' : 'm.[number]';
|
|
conditions.push(`(
|
|
ma.sku LIKE @searchPattern
|
|
OR ma.name LIKE @searchPattern
|
|
OR at.name LIKE @searchPattern
|
|
OR ${numCol} LIKE @searchPattern
|
|
OR m.external_number LIKE @searchPattern
|
|
OR l.transaction_type LIKE @searchPattern
|
|
OR l.transaction_text LIKE @searchPattern
|
|
)`);
|
|
}
|
|
|
|
const whereClause = conditions.join('\n AND ');
|
|
|
|
const sqlCount = `
|
|
SELECT COUNT(*) AS total
|
|
FROM ledger l
|
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
LEFT JOIN article a ON a.sku = ma.sku AND a.company_id = m.company_id
|
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
|
WHERE ${whereClause}
|
|
`;
|
|
|
|
const sqlCountBase = `
|
|
SELECT COUNT(*) AS total
|
|
FROM ledger l
|
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
WHERE m.company_id = @companyId
|
|
`;
|
|
|
|
let sqlList;
|
|
if (isSqlite) {
|
|
sqlList = `
|
|
SELECT
|
|
l.id,
|
|
l.created_at AS "date",
|
|
ma.sku,
|
|
COALESCE(at.name, ma.name) AS name,
|
|
l.transaction_type,
|
|
l.transaction_text,
|
|
l.amount
|
|
FROM ledger l
|
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
LEFT JOIN article a ON a.sku = ma.sku AND a.company_id = m.company_id
|
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
|
WHERE ${whereClause}
|
|
ORDER BY l.created_at DESC, l.id DESC
|
|
${isPaged ? 'LIMIT @limit OFFSET @offset' : ''}
|
|
`;
|
|
} else {
|
|
sqlList = `
|
|
SELECT
|
|
l.id,
|
|
l.created_at AS [date],
|
|
ma.sku,
|
|
COALESCE(at.name, ma.name) AS name,
|
|
l.transaction_type,
|
|
l.transaction_text,
|
|
l.amount
|
|
FROM ledger l
|
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
|
LEFT JOIN article a ON a.sku = ma.sku AND a.company_id = m.company_id
|
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
|
WHERE ${whereClause}
|
|
ORDER BY l.created_at DESC, l.id DESC
|
|
${isPaged ? 'OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY' : ''}
|
|
`;
|
|
}
|
|
|
|
const hasUserFilters = (
|
|
opts?.seekMonths !== undefined ||
|
|
(typeof opts?.search === 'string' && opts.search.trim() !== '')
|
|
);
|
|
|
|
params.offset = offset;
|
|
params.limit = limit;
|
|
|
|
const [countResult, listResult, unfilteredCountResult] = await Promise.all([
|
|
runner.query(sqlCount, params),
|
|
runner.query(sqlList, params),
|
|
hasUserFilters ? runner.query(sqlCountBase, { companyId }) : null,
|
|
]);
|
|
|
|
const total = Number(countResult.rows[0]?.total ?? 0);
|
|
const unfilteredTotal = hasUserFilters
|
|
? Number(unfilteredCountResult?.rows[0]?.total ?? 0)
|
|
: total;
|
|
|
|
return {
|
|
ledger: listResult.rows.map((row) => ({
|
|
id: row.id,
|
|
date:
|
|
row.date instanceof Date
|
|
? row.date.toISOString()
|
|
: row.date
|
|
? new Date(row.date).toISOString()
|
|
: null,
|
|
sku: row.sku ?? null,
|
|
name: row.name ?? null,
|
|
transaction_type: row.transaction_type,
|
|
transaction_text: row.transaction_text,
|
|
amount: row.amount,
|
|
})),
|
|
total,
|
|
unfilteredTotal,
|
|
offset,
|
|
limit,
|
|
};
|
|
};
|
|
}
|