u
This commit is contained in:
@@ -320,6 +320,29 @@ const articles = await store.article.listExport(42, null);
|
|||||||
// => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
|
// => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `article.listHistory`
|
||||||
|
|
||||||
|
*Source: `src/store/article/listHistory.js`*
|
||||||
|
|
||||||
|
Lists article history entries for the user's selected company, supporting pagination, date cutoff via seekMonths, and search.
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `provider` | `import('../../providers/provider.js').Provider` | |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`(googleAuthId: number, opts?: {offset?: number, limit?: number, search?: string, seekMonths?: number}, tx?: object) => Promise<{history: Array<{id: number, date: string, sku: string, name: string|null, historytext: string}>, total: number, unfilteredTotal: number, offset: number, limit: number}>`
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```js
|
||||||
|
const res = await store.article.listHistory(googleAuthId, { offset: 0, limit: 50, search: 'hjg', seekMonths: -13 });
|
||||||
|
// => { history: [...], total, unfilteredTotal, offset, limit }
|
||||||
|
```
|
||||||
|
|
||||||
## `article.listSearchIndex`
|
## `article.listSearchIndex`
|
||||||
|
|
||||||
*Source: `src/store/article/listSearchIndex.js`*
|
*Source: `src/store/article/listSearchIndex.js`*
|
||||||
|
|||||||
@@ -84,3 +84,26 @@ Lists ledger entries for a given article SKU, optionally filtered by transaction
|
|||||||
const entries = await store.ledger.listBySku(googleAuthId, 'SKU-001', 'DELIVERY');
|
const entries = await store.ledger.listBySku(googleAuthId, 'SKU-001', 'DELIVERY');
|
||||||
// => [{ id, manifestarticle_id, warehouse_id, sku, transaction_type, amount, transaction_text, hash_version, prev_hash, created_at }]
|
// => [{ id, manifestarticle_id, warehouse_id, sku, transaction_type, amount, transaction_text, hash_version, prev_hash, created_at }]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `ledger.listHistory`
|
||||||
|
|
||||||
|
*Source: `src/store/ledger/listHistory.js`*
|
||||||
|
|
||||||
|
Lists ledger entries for the user's selected company across all articles, supporting pagination, date cutoff via seekMonths, and search.
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `provider` | `import('../../providers/provider.js').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**
|
||||||
|
|
||||||
|
```js
|
||||||
|
const res = await store.ledger.listHistory(googleAuthId, { offset: 0, limit: 50, search: 'kjh', seekMonths: -1 });
|
||||||
|
// => { ledger: [...], total, unfilteredTotal, offset, limit }
|
||||||
|
```
|
||||||
|
|||||||
2
API.md
2
API.md
@@ -116,6 +116,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `importRows(input: {googleAuthId: number, rows: Array<object>}, tx?: object): Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>`
|
- `importRows(input: {googleAuthId: number, rows: Array<object>}, tx?: object): Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>`
|
||||||
- `listChildren(googleAuthId: number, parentSku: string, tx?: object): Promise<Array<{id: number, sku: string, parentsort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>`
|
- `listChildren(googleAuthId: number, parentSku: string, tx?: object): Promise<Array<{id: number, sku: string, parentsort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>`
|
||||||
- `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>`
|
- `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>`
|
||||||
|
- `listHistory(provider: import('../../providers/provider.js').Provider): (googleAuthId: number, opts?: {offset?: number, limit?: number, search?: string, seekMonths?: number}, tx?: object) => Promise<{history: Array<{id: number, date: string, sku: string, name: string|null, historytext: string}>, total: number, unfilteredTotal: number, offset: number, limit: number}>`
|
||||||
- `listSearchIndex(googleAuthId: number, tx?: object): Promise<Array<{id: number, sku: string, name: string, eans: Array<string>, categoryIds: Array<number>}>>`
|
- `listSearchIndex(googleAuthId: number, tx?: object): Promise<Array<{id: number, sku: string, name: string, eans: Array<string>, categoryIds: Array<number>}>>`
|
||||||
- `orderImages(googleAuthId: number, sku: string, input: {fullHashes: Array<string>}, tx?: object): Promise<{fullHashes: Array<string>}>`
|
- `orderImages(googleAuthId: number, sku: string, input: {fullHashes: Array<string>}, tx?: object): Promise<{fullHashes: Array<string>}>`
|
||||||
- `removeChild(googleAuthId: number, parentSku: string, childSku: string, options?: {deleteArticle?: boolean, cascade?: boolean}, tx?: object): Promise<{success: boolean, parentSku: string, childSku: string, deleted: boolean}>`
|
- `removeChild(googleAuthId: number, parentSku: string, childSku: string, options?: {deleteArticle?: boolean, cascade?: boolean}, tx?: object): Promise<{success: boolean, parentSku: string, childSku: string, deleted: boolean}>`
|
||||||
@@ -238,6 +239,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `add(googleAuthId: number, input: {manifestarticle_id: number, warehouse_id?: number|null, transaction_type: string, amount: number, transaction_text: string}, tx?: object): Promise<{id: number, manifestarticle_id: number, warehouse_id: number|null, transaction_type: string, amount: number, transaction_text: string, hash_version: number, prev_hash: string, created_at: string}>`
|
- `add(googleAuthId: number, input: {manifestarticle_id: number, warehouse_id?: number|null, transaction_type: string, amount: number, transaction_text: string}, tx?: object): Promise<{id: number, manifestarticle_id: number, warehouse_id: number|null, transaction_type: string, amount: number, transaction_text: string, hash_version: number, prev_hash: string, created_at: string}>`
|
||||||
- `listByManifestarticleId(googleAuthId: number, manifestarticleId: number, optionsOrType?: string|object, tx?: object): Promise<Array<{id: number, manifestarticle_id: number, warehouse_id: number|null, sku: string|null, transaction_type: string, amount: number, transaction_text: string, hash_version: number, prev_hash: string, created_at: string}>>`
|
- `listByManifestarticleId(googleAuthId: number, manifestarticleId: number, optionsOrType?: string|object, tx?: object): Promise<Array<{id: number, manifestarticle_id: number, warehouse_id: number|null, sku: string|null, transaction_type: string, amount: number, transaction_text: string, hash_version: number, prev_hash: string, created_at: string}>>`
|
||||||
- `listBySku(googleAuthId: number, sku: string, optionsOrType?: string|object, tx?: object): Promise<Array<{id: number, manifestarticle_id: number, warehouse_id: number|null, sku: string|null, transaction_type: string, amount: number, transaction_text: string, hash_version: number, prev_hash: string, created_at: string}>>`
|
- `listBySku(googleAuthId: number, sku: string, optionsOrType?: string|object, tx?: object): Promise<Array<{id: number, manifestarticle_id: number, warehouse_id: number|null, sku: string|null, transaction_type: string, amount: number, transaction_text: string, hash_version: number, prev_hash: string, created_at: string}>>`
|
||||||
|
- `listHistory(provider: import('../../providers/provider.js').Provider): (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}>`
|
||||||
- [warehouse](API.warehouse.md)
|
- [warehouse](API.warehouse.md)
|
||||||
- `create(googleAuthId: number, input: {name: string, sort?: number}, tx?: object): Promise<{id: number, name: string, sort: number}>`
|
- `create(googleAuthId: number, input: {name: string, sort?: number}, tx?: object): Promise<{id: number, name: string, sort: number}>`
|
||||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import articleAddImage from './store/article/addImage.js';
|
|||||||
import articleCreateUnderParent from './store/article/createUnderParent.js';
|
import articleCreateUnderParent from './store/article/createUnderParent.js';
|
||||||
import articleGetBySku from './store/article/getBySku.js';
|
import articleGetBySku from './store/article/getBySku.js';
|
||||||
import articleGetHistoryBySku from './store/article/getHistoryBySku.js';
|
import articleGetHistoryBySku from './store/article/getHistoryBySku.js';
|
||||||
|
import articleListHistory from './store/article/listHistory.js';
|
||||||
import articleListSearchIndex from './store/article/listSearchIndex.js';
|
import articleListSearchIndex from './store/article/listSearchIndex.js';
|
||||||
import articleListExport from './store/article/listExport.js';
|
import articleListExport from './store/article/listExport.js';
|
||||||
import articleUpdateBySku from './store/article/updateBySku.js';
|
import articleUpdateBySku from './store/article/updateBySku.js';
|
||||||
@@ -159,6 +160,7 @@ import manifestattachmentOrder from './store/manifestattachment/order.js';
|
|||||||
import ledgerAdd from './store/ledger/add.js';
|
import ledgerAdd from './store/ledger/add.js';
|
||||||
import ledgerListByManifestarticleId from './store/ledger/listByManifestarticleId.js';
|
import ledgerListByManifestarticleId from './store/ledger/listByManifestarticleId.js';
|
||||||
import ledgerListBySku from './store/ledger/listBySku.js';
|
import ledgerListBySku from './store/ledger/listBySku.js';
|
||||||
|
import ledgerListHistory from './store/ledger/listHistory.js';
|
||||||
import warehouseCreate from './store/warehouse/create.js';
|
import warehouseCreate from './store/warehouse/create.js';
|
||||||
import warehouseGetById from './store/warehouse/getById.js';
|
import warehouseGetById from './store/warehouse/getById.js';
|
||||||
import warehouseList from './store/warehouse/list.js';
|
import warehouseList from './store/warehouse/list.js';
|
||||||
@@ -320,6 +322,7 @@ export default class LibDashStore {
|
|||||||
createUnderParent: articleCreateUnderParent(provider),
|
createUnderParent: articleCreateUnderParent(provider),
|
||||||
getBySku: articleGetBySku(provider),
|
getBySku: articleGetBySku(provider),
|
||||||
getHistoryBySku: articleGetHistoryBySku(provider),
|
getHistoryBySku: articleGetHistoryBySku(provider),
|
||||||
|
listHistory: articleListHistory(provider),
|
||||||
listSearchIndex: articleListSearchIndex(provider),
|
listSearchIndex: articleListSearchIndex(provider),
|
||||||
listExport: articleListExport(provider),
|
listExport: articleListExport(provider),
|
||||||
updateBySku: articleUpdateBySku(provider),
|
updateBySku: articleUpdateBySku(provider),
|
||||||
@@ -555,6 +558,7 @@ export default class LibDashStore {
|
|||||||
add: ledgerAdd(provider),
|
add: ledgerAdd(provider),
|
||||||
listByManifestarticleId: ledgerListByManifestarticleId(provider),
|
listByManifestarticleId: ledgerListByManifestarticleId(provider),
|
||||||
listBySku: ledgerListBySku(provider),
|
listBySku: ledgerListBySku(provider),
|
||||||
|
listHistory: ledgerListHistory(provider),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.warehouse = {
|
this.warehouse = {
|
||||||
|
|||||||
170
src/store/article/listHistory.js
Normal file
170
src/store/article/listHistory.js
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists article history entries for the user's selected company,
|
||||||
|
* 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<{history: Array<{id: number, date: string, sku: string, name: string|null, historytext: string}>, total: number, unfilteredTotal: number, offset: number, limit: number}>}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const res = await store.article.listHistory(googleAuthId, { offset: 0, limit: 50, search: 'hjg', seekMonths: -13 });
|
||||||
|
* // => { history: [...], 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 = ['a.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(`ah.${isSqlite ? '"date"' : '[date]'} <= @dateTo`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free-text search
|
||||||
|
if (typeof opts?.search === 'string' && opts.search.trim() !== '') {
|
||||||
|
params.searchPattern = `%${opts.search.trim()}%`;
|
||||||
|
if (isSqlite) {
|
||||||
|
conditions.push(`(
|
||||||
|
a.sku LIKE @searchPattern
|
||||||
|
OR at.name LIKE @searchPattern
|
||||||
|
OR ah."text" LIKE @searchPattern
|
||||||
|
)`);
|
||||||
|
} else {
|
||||||
|
conditions.push(`(
|
||||||
|
a.sku LIKE @searchPattern
|
||||||
|
OR at.name LIKE @searchPattern
|
||||||
|
OR ah.[text] LIKE @searchPattern
|
||||||
|
)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereClause = conditions.join('\n AND ');
|
||||||
|
|
||||||
|
const sqlCount = `
|
||||||
|
SELECT COUNT(*) AS total
|
||||||
|
FROM articlehistory ah
|
||||||
|
INNER JOIN article a ON a.id = ah.article_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 articlehistory ah
|
||||||
|
INNER JOIN article a ON a.id = ah.article_id
|
||||||
|
WHERE a.company_id = @companyId
|
||||||
|
`;
|
||||||
|
|
||||||
|
let sqlList;
|
||||||
|
if (isSqlite) {
|
||||||
|
sqlList = `
|
||||||
|
SELECT
|
||||||
|
ah.id,
|
||||||
|
ah."date",
|
||||||
|
a.sku,
|
||||||
|
at.name,
|
||||||
|
ah."text" AS historytext
|
||||||
|
FROM articlehistory ah
|
||||||
|
INNER JOIN article a ON a.id = ah.article_id
|
||||||
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
||||||
|
WHERE ${whereClause}
|
||||||
|
ORDER BY ah."date" DESC, ah.id DESC
|
||||||
|
${isPaged ? 'LIMIT @limit OFFSET @offset' : ''}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
sqlList = `
|
||||||
|
SELECT
|
||||||
|
ah.id,
|
||||||
|
ah.[date],
|
||||||
|
a.sku,
|
||||||
|
at.name,
|
||||||
|
ah.[text] AS historytext
|
||||||
|
FROM articlehistory ah
|
||||||
|
INNER JOIN article a ON a.id = ah.article_id
|
||||||
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
||||||
|
WHERE ${whereClause}
|
||||||
|
ORDER BY ah.[date] DESC, ah.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 {
|
||||||
|
history: 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,
|
||||||
|
name: row.name ?? null,
|
||||||
|
historytext: row.historytext,
|
||||||
|
})),
|
||||||
|
total,
|
||||||
|
unfilteredTotal,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
180
src/store/ledger/listHistory.js
Normal file
180
src/store/ledger/listHistory.js
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user