Add article.listSearchIndex for client-side search preload
Returns all company articles with id, sku, name, and eans as a flat array. Walks the category tree to find reachable articles. Single query using recursive CTE + FOR JSON for eans.
This commit is contained in:
@@ -263,6 +263,7 @@ Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
|||||||
|
|
||||||
| Method | Signature | Returns |
|
| Method | Signature | Returns |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
| `listSearchIndex` | `(googleAuthId, companyId)` | `[{ id, sku, name, eans }]` |
|
||||||
| `createUnderParent` | `({ googleAuthId, parentId, name })` | `{ id, parentId, name, sku }` |
|
| `createUnderParent` | `({ googleAuthId, parentId, name })` | `{ id, parentId, name, sku }` |
|
||||||
| `getBySku` | `(googleAuthId, sku)` | detail object (see below) |
|
| `getBySku` | `(googleAuthId, sku)` | detail object (see below) |
|
||||||
| `updateBySku` | `(googleAuthId, sku, { name?, unitamount?, unitSi? })` | same shape as `getBySku` |
|
| `updateBySku` | `(googleAuthId, sku, { name?, unitamount?, unitSi? })` | same shape as `getBySku` |
|
||||||
@@ -310,6 +311,8 @@ Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
|||||||
|
|
||||||
`getCategoryArticles` / `getTreeForCompany().articles[]` keep the flat list shape (`net`, `taxRate`, single `fullHash`/`thumbW`/`thumbH`/`thumbHash`).
|
`getCategoryArticles` / `getTreeForCompany().articles[]` keep the flat list shape (`net`, `taxRate`, single `fullHash`/`thumbW`/`thumbH`/`thumbHash`).
|
||||||
|
|
||||||
|
`listSearchIndex` returns every article in the company (reachable via the category tree) with `id`, `sku`, `name`, and `eans` (all EANs as a flat string array). Designed for client-side search / autocomplete preload. Auth via `resolveCompanyAccess`. Errors: `Company not found`, `Not authorized`.
|
||||||
|
|
||||||
`parentId` is the category id. Creates `article` + `articletext` (company default language) + `categoryarticle` in one transaction. Allocates `sku` via `getnextnumberwithprefixandpadding` (`type` `sku`; creates `numbers` row with empty prefix if missing). Default unit `pc` is ensured if missing (`unitamount` 1, `weight` 0). Auth via `resolveCategoryAccess(googleAuthId, parentId)` — `Parent not found` / `Not authorized`.
|
`parentId` is the category id. Creates `article` + `articletext` (company default language) + `categoryarticle` in one transaction. Allocates `sku` via `getnextnumberwithprefixandpadding` (`type` `sku`; creates `numbers` row with empty prefix if missing). Default unit `pc` is ensured if missing (`unitamount` 1, `weight` 0). Auth via `resolveCategoryAccess(googleAuthId, parentId)` — `Parent not found` / `Not authorized`.
|
||||||
|
|
||||||
Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Errors: `Article not found`, `Not authorized`.
|
Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Errors: `Article not found`, `Not authorized`.
|
||||||
@@ -324,7 +327,7 @@ Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Err
|
|||||||
|
|
||||||
`setWeightBySku` sets the article’s `weight`. Must be finite and `>= 0` (else `Invalid weight`). Errors: `Article not found`, `Not authorized`, `Invalid weight`.
|
`setWeightBySku` sets the article’s `weight`. Must be finite and `>= 0` (else `Invalid weight`). Errors: `Article not found`, `Not authorized`, `Invalid weight`.
|
||||||
|
|
||||||
`setCategoryIdsBySku` replaces the article’s category list (`categoryarticle`) in one transaction. Each category must belong to the article’s company and ids must be unique (else `Invalid categoryIds`); empty array removes the article from all categories. Returns the same object as `getBySku` (whose `categoryIds` reflects the new list). Errors: `Article not found`, `Not authorized`, `Invalid categoryIds`.
|
`setCategoryIdsBySku` replaces the article’s category list (`categoryarticle`) in one transaction. Each category must belong to the article’s company and ids must be unique (else `Invalid categoryIds`); empty array is rejected — an article must have at least one category (else `Article must have at least one category`). Returns the same object as `getBySku` (whose `categoryIds` reflects the new list). Errors: `Article not found`, `Not authorized`, `Invalid categoryIds`, `Article must have at least one category`.
|
||||||
|
|
||||||
`deleteImage` removes `articlepic` and the `pic` row for this article (`fullHash`). Errors: `Article not found`, `Image not found`, `Not authorized`.
|
`deleteImage` removes `articlepic` and the `pic` row for this article (`fullHash`). Errors: `Article not found`, `Image not found`, `Not authorized`.
|
||||||
|
|
||||||
@@ -394,6 +397,7 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
|||||||
| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, companyId, id)` |
|
| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, companyId, id)` |
|
||||||
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
||||||
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
||||||
|
| `GET /api/articles/search-index` | `article.listSearchIndex(googleAuthId, companyId)` |
|
||||||
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
|
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
|
||||||
| `PUT /api/article/:sku/prices` | `article.setPricesBySku(googleAuthId, sku, [{ price, tax, minmultiplier? }])` |
|
| `PUT /api/article/:sku/prices` | `article.setPricesBySku(googleAuthId, sku, [{ price, tax, minmultiplier? }])` |
|
||||||
| `PUT /api/article/:sku/eans` | `article.setEansBySku(googleAuthId, sku, string[])` |
|
| `PUT /api/article/:sku/eans` | `article.setEansBySku(googleAuthId, sku, string[])` |
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import articleOrderImages from './store/article/orderImages.js';
|
|||||||
import articleAddImage from './store/article/addImage.js';
|
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 articleListSearchIndex from './store/article/listSearchIndex.js';
|
||||||
import articleUpdateBySku from './store/article/updateBySku.js';
|
import articleUpdateBySku from './store/article/updateBySku.js';
|
||||||
import articleSetPricesBySku from './store/article/setPricesBySku.js';
|
import articleSetPricesBySku from './store/article/setPricesBySku.js';
|
||||||
import articleSetEansBySku from './store/article/setEansBySku.js';
|
import articleSetEansBySku from './store/article/setEansBySku.js';
|
||||||
@@ -95,6 +96,7 @@ export default class LibDashStore {
|
|||||||
this.article = {
|
this.article = {
|
||||||
createUnderParent: articleCreateUnderParent(pool),
|
createUnderParent: articleCreateUnderParent(pool),
|
||||||
getBySku: articleGetBySku(pool),
|
getBySku: articleGetBySku(pool),
|
||||||
|
listSearchIndex: articleListSearchIndex(pool),
|
||||||
updateBySku: articleUpdateBySku(pool),
|
updateBySku: articleUpdateBySku(pool),
|
||||||
setPricesBySku: articleSetPricesBySku(pool),
|
setPricesBySku: articleSetPricesBySku(pool),
|
||||||
setEansBySku: articleSetEansBySku(pool),
|
setEansBySku: articleSetEansBySku(pool),
|
||||||
|
|||||||
@@ -160,6 +160,46 @@ FROM brand
|
|||||||
WHERE id = @brandId AND company_id = @companyId
|
WHERE id = @brandId AND company_id = @companyId
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Company-wide article search index.
|
||||||
|
* Returns one row per article with id, sku, name, and all eans as a JSON array.
|
||||||
|
* Used by the client for local search / autocomplete.
|
||||||
|
*/
|
||||||
|
export const SEARCH_ARTICLES = `
|
||||||
|
;WITH category_tree AS (
|
||||||
|
SELECT id
|
||||||
|
FROM category
|
||||||
|
WHERE id = @rootCategoryId
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT c.id
|
||||||
|
FROM category c
|
||||||
|
INNER JOIN category_tree ct ON c.parent_id = ct.id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
a.id,
|
||||||
|
a.sku,
|
||||||
|
at.name,
|
||||||
|
(
|
||||||
|
SELECT ae.ean
|
||||||
|
FROM articleean ae
|
||||||
|
WHERE ae.article_id = a.id
|
||||||
|
ORDER BY ae.ean
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS eansJson
|
||||||
|
FROM article a
|
||||||
|
LEFT JOIN articletext at
|
||||||
|
ON at.article_id = a.id AND at.language_id = @languageId
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM categoryarticle ca
|
||||||
|
INNER JOIN category_tree ct ON ct.id = ca.category_id
|
||||||
|
WHERE ca.article_id = a.id
|
||||||
|
)
|
||||||
|
ORDER BY a.id
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single-query replacement for resolveArticleBySku + queryBySku.
|
* Single-query replacement for resolveArticleBySku + queryBySku.
|
||||||
* Resolves SKU → article + company + auth, then loads full article detail
|
* Resolves SKU → article + company + auth, then loads full article detail
|
||||||
|
|||||||
23
src/store/article/listSearchIndex.js
Normal file
23
src/store/article/listSearchIndex.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { SEARCH_ARTICLES } from '../../sql/article.js';
|
||||||
|
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||||
|
|
||||||
|
export default function listSearchIndex(pool) {
|
||||||
|
const access = resolveCompanyAccess(pool);
|
||||||
|
|
||||||
|
return async (googleAuthId, companyId) => {
|
||||||
|
const { rootCategoryId, languageId } = await access(googleAuthId, companyId);
|
||||||
|
|
||||||
|
const result = await pool
|
||||||
|
.request()
|
||||||
|
.input('rootCategoryId', rootCategoryId)
|
||||||
|
.input('languageId', languageId)
|
||||||
|
.query(SEARCH_ARTICLES);
|
||||||
|
|
||||||
|
return result.recordset.map((row) => ({
|
||||||
|
id: row.id,
|
||||||
|
sku: row.sku,
|
||||||
|
name: row.name,
|
||||||
|
eans: row.eansJson ? JSON.parse(row.eansJson).map((r) => r.ean) : [],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user