u
This commit is contained in:
@@ -128,7 +128,7 @@ Returns the full detail of an article by its SKU, including unit, brand, categor
|
|||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|
||||||
`Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {name: string, thumbW: number, thumbH: number, thumbHash: string}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null}}>` — The full article detail.
|
`Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null}}>` — The full article detail.
|
||||||
|
|
||||||
**Throws**
|
**Throws**
|
||||||
|
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ Lists the brands of the user's selected company.
|
|||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|
||||||
`Promise<Array<{id: number, name: string, gsprpinfo: string|null}>>` — The brands.
|
`Promise<Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>>` — The brands.
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const brands = await store.brand.list(42);
|
const brands = await store.brand.list(42);
|
||||||
// => [{ id, name, gsprpinfo }]
|
// => [{ id, name, gsprpinfo, pics: [{ sort, fullHash, thumbW, thumbH, thumbHash }] }]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `brand.upsert`
|
## `brand.upsert`
|
||||||
@@ -72,7 +72,7 @@ Creates or updates a brand for the user's selected company, keyed by name.
|
|||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|
||||||
`Promise<{id: number, name: string, gsprpinfo: string|null}>` — The upserted brand.
|
`Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>` — The upserted brand.
|
||||||
|
|
||||||
**Throws**
|
**Throws**
|
||||||
|
|
||||||
@@ -82,5 +82,5 @@ Creates or updates a brand for the user's selected company, keyed by name.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const brand = await store.brand.upsert(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
|
const brand = await store.brand.upsert(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
|
||||||
// => { id, name, gsprpinfo }
|
// => { id, name, gsprpinfo, pics: [] }
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -156,13 +156,13 @@ Returns the articles belonging to a single category.
|
|||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|
||||||
`Promise<Array<object>>` — The articles in the category.
|
`Promise<Array<{id: number, sku: string, name: string, slug: string, net: number|null, taxRate: number|null, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null}>>` — The articles in the category.
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const articles = await store.category.getCategoryArticles(42, 5);
|
const articles = await store.category.getCategoryArticles(42, 5);
|
||||||
// => [{ id, sku, name, ... }]
|
// => [{ id, sku, name, brand, ... }]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `category.getTreeForCompany`
|
## `category.getTreeForCompany`
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const setting = await store.companysetting.get(42, 'ui.theme');
|
|||||||
|
|
||||||
*Source: `src/store/companysetting/list.js`*
|
*Source: `src/store/companysetting/list.js`*
|
||||||
|
|
||||||
Lists the settings of the user's selected company, optionally only frontend-visible ones. Write-only settings (such as `keys.tavily`, `keys.openrouter`) have their value masked with `'***'` by default for usergui presentation.
|
Lists the settings of the user's selected company, optionally only frontend-visible ones. Write-only settings (such as `keys.tavily`, `keys.openrouter`, `keys.serper`) have their value masked with `'***'` by default for usergui presentation.
|
||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ Sets (upserts) a company setting by key for the user's selected company.
|
|||||||
| `input.key` | `string` | The setting key (must be in the allow-list). |
|
| `input.key` | `string` | The setting key (must be in the allow-list). |
|
||||||
| `input.value` | `string` | The setting value. |
|
| `input.value` | `string` | The setting value. |
|
||||||
| `[input.frontend]` | `boolean` | Whether the setting is frontend-visible. |
|
| `[input.frontend]` | `boolean` | Whether the setting is frontend-visible. |
|
||||||
| `[input.writeonly]` | `boolean` | Whether the setting is write-only. Defaults to true for `keys.tavily` and `keys.openrouter`, false otherwise. |
|
| `[input.writeonly]` | `boolean` | Whether the setting is write-only. Defaults to true for `keys.tavily`, `keys.openrouter`, and `keys.serper`, false otherwise. |
|
||||||
| `[tx]` | `object` | Optional transaction to run within. |
|
| `[tx]` | `object` | Optional transaction to run within. |
|
||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|||||||
8
API.md
8
API.md
@@ -70,7 +70,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `delete(input: {googleAuthId: number, id: number}, tx?: object): Promise<void>`
|
- `delete(input: {googleAuthId: number, id: number}, tx?: object): Promise<void>`
|
||||||
- `deleteImage(googleAuthId: number, categoryId: number, tx?: object): Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null}>`
|
- `deleteImage(googleAuthId: number, categoryId: number, tx?: object): Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null}>`
|
||||||
- `ensureByPath(input: {googleAuthId: number, parentId: number, path: string}, tx?: object): Promise<number>`
|
- `ensureByPath(input: {googleAuthId: number, parentId: number, path: string}, tx?: object): Promise<number>`
|
||||||
- `getCategoryArticles(googleAuthId: number, categoryId: number, tx?: object): Promise<Array<object>>`
|
- `getCategoryArticles(googleAuthId: number, categoryId: number, tx?: object): Promise<Array<{id: number, sku: string, name: string, slug: string, net: number|null, taxRate: number|null, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null}>>`
|
||||||
- `getTreeForCompany(googleAuthId: number, tx?: object): Promise<{languageId: number, tree: Array<object>, articles: Array<object>}>`
|
- `getTreeForCompany(googleAuthId: number, tx?: object): Promise<{languageId: number, tree: Array<object>, articles: Array<object>}>`
|
||||||
- `move(input: {googleAuthId: number, id: number, parentId: number}, tx?: object): Promise<{id: number, parentId: number, sort: number}>`
|
- `move(input: {googleAuthId: number, id: number, parentId: number}, tx?: object): Promise<{id: number, parentId: number, sort: number}>`
|
||||||
- `order(input: {googleAuthId: number, parentId: number, categoryIds: Array<number>}, tx?: object): Promise<{categoryIds: Array<number>}>`
|
- `order(input: {googleAuthId: number, parentId: number, categoryIds: Array<number>}, tx?: object): Promise<{categoryIds: Array<number>}>`
|
||||||
@@ -80,7 +80,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `createUnderParent(input: {googleAuthId: number, parentId: number, name: string}, tx?: object): Promise<{id: number, parentId: number, name: string, sku: string}>`
|
- `createUnderParent(input: {googleAuthId: number, parentId: number, name: string}, tx?: object): Promise<{id: number, parentId: number, name: string, sku: string}>`
|
||||||
- `delete(googleAuthId: number, sku: string, tx?: object): Promise<void>`
|
- `delete(googleAuthId: number, sku: string, tx?: object): Promise<void>`
|
||||||
- `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise<void>`
|
- `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise<void>`
|
||||||
- `getBySku(googleAuthId: number, sku: string, tx?: object): Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {name: string, thumbW: number, thumbH: number, thumbHash: string}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null}}>`
|
- `getBySku(googleAuthId: number, sku: string, tx?: object): Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null}}>`
|
||||||
- `getHistoryBySku(googleAuthId: number, sku: string, tx?: object): Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>`
|
- `getHistoryBySku(googleAuthId: number, sku: string, tx?: object): Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>`
|
||||||
- `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>}>`
|
||||||
- `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>`
|
- `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>`
|
||||||
@@ -114,8 +114,8 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `set(googleAuthId: number, input: {key: string, value: string, frontend?: boolean, writeonly?: boolean}, tx?: object): Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}>`
|
- `set(googleAuthId: number, input: {key: string, value: string, frontend?: boolean, writeonly?: boolean}, tx?: object): Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}>`
|
||||||
- [brand](API.brand.md)
|
- [brand](API.brand.md)
|
||||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, gsprpinfo: string|null}>>`
|
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>>`
|
||||||
- `upsert(googleAuthId: number, input: {name: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null}>`
|
- `upsert(googleAuthId: number, input: {name: string, gsprpinfo?: string}, tx?: object): Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>`
|
||||||
- [customergroup](API.customergroup.md)
|
- [customergroup](API.customergroup.md)
|
||||||
- `create(googleAuthId: number, input: {name: string}, tx?: object): Promise<{id: number, name: string, sort: number}>`
|
- `create(googleAuthId: number, input: {name: string}, 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>`
|
||||||
|
|||||||
@@ -168,8 +168,8 @@ Company-scoped key/value settings. Auth via selected company (`google_auth.selec
|
|||||||
- `frontend: true` → readable by frontend routes (`frontendOnly: true`). Default on `set` is `false` (backend-only).
|
- `frontend: true` → readable by frontend routes (`frontendOnly: true`). Default on `set` is `false` (backend-only).
|
||||||
- With `frontendOnly: true`, backend-only keys are omitted / `get` returns `undefined` (no leak via error type beyond missing).
|
- With `frontendOnly: true`, backend-only keys are omitted / `get` returns `undefined` (no leak via error type beyond missing).
|
||||||
- `writeonly: true` → usergui gets a `"***"` scrambled version when retrieving the key (in `list`, or in `get` when `maskWriteonly` is enabled / `frontendOnly: true`). The backend receives the full value.
|
- `writeonly: true` → usergui gets a `"***"` scrambled version when retrieving the key (in `list`, or in `get` when `maskWriteonly` is enabled / `frontendOnly: true`). The backend receives the full value.
|
||||||
- `keys.tavily` and `keys.openrouter` default to `writeonly: true` on `set`.
|
- `keys.tavily`, `keys.openrouter`, and `keys.serper` default to `writeonly: true` on `set`.
|
||||||
- `set` only allows a fixed allow-list of keys: `ui.theme`, `keys.tavily`, `keys.openrouter`, `labelprinter.addr`, `labelprinter.type`.
|
- `set` only allows a fixed allow-list of keys: `ui.theme`, `keys.tavily`, `keys.openrouter`, `keys.serper`, `labelprinter.addr`, `labelprinter.type`.
|
||||||
- No delete endpoint — settings are upsert / read only.
|
- No delete endpoint — settings are upsert / read only.
|
||||||
|
|
||||||
Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid setting`.
|
Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid setting`.
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ import companysettingSet from './store/companysetting/set.js';
|
|||||||
import brandList from './store/brand/list.js';
|
import brandList from './store/brand/list.js';
|
||||||
import brandUpsert from './store/brand/upsert.js';
|
import brandUpsert from './store/brand/upsert.js';
|
||||||
import brandDelete from './store/brand/delete.js';
|
import brandDelete from './store/brand/delete.js';
|
||||||
|
import brandAddImage from './store/brand/addImage.js';
|
||||||
|
import brandDeleteImage from './store/brand/deleteImage.js';
|
||||||
import customergroupList from './store/customergroup/list.js';
|
import customergroupList from './store/customergroup/list.js';
|
||||||
import customergroupCreate from './store/customergroup/create.js';
|
import customergroupCreate from './store/customergroup/create.js';
|
||||||
import customergroupUpdate from './store/customergroup/update.js';
|
import customergroupUpdate from './store/customergroup/update.js';
|
||||||
@@ -232,6 +234,8 @@ export default class LibDashStore {
|
|||||||
list: brandList(provider),
|
list: brandList(provider),
|
||||||
upsert: brandUpsert(provider),
|
upsert: brandUpsert(provider),
|
||||||
delete: brandDelete(provider),
|
delete: brandDelete(provider),
|
||||||
|
addImage: brandAddImage(provider),
|
||||||
|
deleteImage: brandDeleteImage(provider),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.customergroup = {
|
this.customergroup = {
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ OUTER APPLY (
|
|||||||
ORDER BY ap.sort ASC, ap.id ASC
|
ORDER BY ap.sort ASC, ap.id ASC
|
||||||
) first_pic
|
) first_pic
|
||||||
LEFT JOIN pic ON pic.id = first_pic.pic_id
|
LEFT JOIN pic ON pic.id = first_pic.pic_id
|
||||||
|
LEFT JOIN brand b ON b.id = a.brand_id
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 bp.pic_id
|
||||||
|
FROM brandpic bp
|
||||||
|
WHERE bp.brand_id = b.id
|
||||||
|
ORDER BY bp.sort ASC, bp.id ASC
|
||||||
|
) first_brand_pic
|
||||||
|
LEFT JOIN pic brand_pic ON brand_pic.id = first_brand_pic.pic_id
|
||||||
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -49,7 +57,13 @@ export const ARTICLE_LIST_COLUMNS = `
|
|||||||
pic.hash AS fullHash,
|
pic.hash AS fullHash,
|
||||||
pic.thumbw AS thumbW,
|
pic.thumbw AS thumbW,
|
||||||
pic.thumbh AS thumbH,
|
pic.thumbh AS thumbH,
|
||||||
pic.thumbhash AS thumbHash
|
pic.thumbhash AS thumbHash,
|
||||||
|
b.id AS brandId,
|
||||||
|
b.name AS brandName,
|
||||||
|
brand_pic.hash AS brandFullHash,
|
||||||
|
brand_pic.thumbw AS brandThumbW,
|
||||||
|
brand_pic.thumbh AS brandThumbH,
|
||||||
|
brand_pic.thumbhash AS brandThumbHash
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_CATEGORY_ID_BY_SKU = `
|
export const GET_CATEGORY_ID_BY_SKU = `
|
||||||
@@ -362,7 +376,9 @@ SELECT
|
|||||||
u.basesifactor AS unitBasesifactor,
|
u.basesifactor AS unitBasesifactor,
|
||||||
ut.symbol AS unitSymbol,
|
ut.symbol AS unitSymbol,
|
||||||
ut.name AS unitName,
|
ut.name AS unitName,
|
||||||
|
b.id AS brandId,
|
||||||
b.name AS brandName,
|
b.name AS brandName,
|
||||||
|
bp.hash AS brandFullHash,
|
||||||
bp.thumbw AS brandThumbW,
|
bp.thumbw AS brandThumbW,
|
||||||
bp.thumbh AS brandThumbH,
|
bp.thumbh AS brandThumbH,
|
||||||
bp.thumbhash AS brandThumbHash,
|
bp.thumbhash AS brandThumbHash,
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ export const ARTICLE_DETAIL_COLUMNS = `
|
|||||||
u.basesifactor AS unitBasesifactor,
|
u.basesifactor AS unitBasesifactor,
|
||||||
ut.symbol AS unitSymbol,
|
ut.symbol AS unitSymbol,
|
||||||
ut.name AS unitName,
|
ut.name AS unitName,
|
||||||
|
b.id AS brandId,
|
||||||
b.name AS brandName,
|
b.name AS brandName,
|
||||||
|
brand_pic.hash AS brandFullHash,
|
||||||
brand_pic.thumbw AS brandThumbW,
|
brand_pic.thumbw AS brandThumbW,
|
||||||
brand_pic.thumbh AS brandThumbH,
|
brand_pic.thumbh AS brandThumbH,
|
||||||
brand_pic.thumbhash AS brandThumbHash,
|
brand_pic.thumbhash AS brandThumbHash,
|
||||||
|
|||||||
@@ -1,21 +1,49 @@
|
|||||||
export const LIST_BY_COMPANY = `
|
export const LIST_BY_COMPANY = `
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
b.id,
|
||||||
name,
|
b.name,
|
||||||
gsprpinfo
|
b.gsprpinfo,
|
||||||
FROM brand
|
(
|
||||||
WHERE company_id = @companyId
|
SELECT
|
||||||
ORDER BY name
|
bp.id,
|
||||||
|
bp.sort,
|
||||||
|
p.hash AS fullHash,
|
||||||
|
p.thumbw AS thumbW,
|
||||||
|
p.thumbh AS thumbH,
|
||||||
|
p.thumbhash AS thumbHash
|
||||||
|
FROM brandpic bp
|
||||||
|
INNER JOIN pic p ON p.id = bp.pic_id
|
||||||
|
WHERE bp.brand_id = b.id
|
||||||
|
ORDER BY bp.sort ASC, bp.id ASC
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS picsJson
|
||||||
|
FROM brand b
|
||||||
|
WHERE b.company_id = @companyId
|
||||||
|
ORDER BY b.name
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_BY_COMPANY_AND_NAME = `
|
export const GET_BY_COMPANY_AND_NAME = `
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
b.id,
|
||||||
name,
|
b.name,
|
||||||
gsprpinfo
|
b.gsprpinfo,
|
||||||
FROM brand
|
(
|
||||||
WHERE company_id = @companyId
|
SELECT
|
||||||
AND name = @name
|
bp.id,
|
||||||
|
bp.sort,
|
||||||
|
p.hash AS fullHash,
|
||||||
|
p.thumbw AS thumbW,
|
||||||
|
p.thumbh AS thumbH,
|
||||||
|
p.thumbhash AS thumbHash
|
||||||
|
FROM brandpic bp
|
||||||
|
INNER JOIN pic p ON p.id = bp.pic_id
|
||||||
|
WHERE bp.brand_id = b.id
|
||||||
|
ORDER BY bp.sort ASC, bp.id ASC
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS picsJson
|
||||||
|
FROM brand b
|
||||||
|
WHERE b.company_id = @companyId
|
||||||
|
AND b.name = @name
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CREATE = `
|
export const CREATE = `
|
||||||
@@ -34,3 +62,13 @@ export const DELETE_BY_COMPANY_AND_ID = `
|
|||||||
DELETE FROM brand
|
DELETE FROM brand
|
||||||
WHERE id = @id AND company_id = @companyId
|
WHERE id = @id AND company_id = @companyId
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const GET_BY_ID_AND_COMPANY = `
|
||||||
|
SELECT
|
||||||
|
b.id,
|
||||||
|
b.company_id,
|
||||||
|
b.name,
|
||||||
|
b.gsprpinfo
|
||||||
|
FROM brand b
|
||||||
|
WHERE b.id = @id AND b.company_id = @companyId
|
||||||
|
`;
|
||||||
|
|||||||
23
src/providers/mssql/queries/brandpic.js
Normal file
23
src/providers/mssql/queries/brandpic.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export const GET_NEXT_SORT = `
|
||||||
|
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
|
||||||
|
FROM brandpic
|
||||||
|
WHERE brand_id = @brandId
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ADD = `
|
||||||
|
INSERT INTO brandpic (brand_id, pic_id, sort)
|
||||||
|
VALUES (@brandId, @picId, @sort)
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DELETE_BY_BRAND = `
|
||||||
|
DELETE FROM brandpic
|
||||||
|
WHERE brand_id = @brandId
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GET_FIRST_PIC_BY_BRAND = `
|
||||||
|
SELECT TOP 1 p.id AS picId, p.thumbw AS thumbW, p.thumbh AS thumbH, p.thumbhash AS thumbHash
|
||||||
|
FROM brandpic bp
|
||||||
|
INNER JOIN pic p ON p.id = bp.pic_id
|
||||||
|
WHERE bp.brand_id = @brandId
|
||||||
|
ORDER BY bp.sort ASC, bp.id ASC
|
||||||
|
`;
|
||||||
@@ -23,6 +23,15 @@ FROM (
|
|||||||
FROM pic p
|
FROM pic p
|
||||||
INNER JOIN categorypic cp ON cp.pic_id = p.id
|
INNER JOIN categorypic cp ON cp.pic_id = p.id
|
||||||
WHERE p.hash = @hash
|
WHERE p.hash = @hash
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT c.category_id AS categoryId
|
||||||
|
FROM pic p
|
||||||
|
INNER JOIN brandpic bp ON bp.pic_id = p.id
|
||||||
|
INNER JOIN brand b ON b.id = bp.brand_id
|
||||||
|
INNER JOIN company c ON c.id = b.company_id
|
||||||
|
WHERE p.hash = @hash
|
||||||
) AS combined
|
) AS combined
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ LEFT JOIN pic ON pic.id = (
|
|||||||
SELECT ap.pic_id FROM articlepic ap
|
SELECT ap.pic_id FROM articlepic ap
|
||||||
WHERE ap.article_id = a.id ORDER BY ap.sort, ap.id LIMIT 1
|
WHERE ap.article_id = a.id ORDER BY ap.sort, ap.id LIMIT 1
|
||||||
)
|
)
|
||||||
|
LEFT JOIN brand b ON b.id = a.brand_id
|
||||||
|
LEFT JOIN pic brand_pic ON brand_pic.id = (
|
||||||
|
SELECT bp.pic_id FROM brandpic bp
|
||||||
|
WHERE bp.brand_id = b.id ORDER BY bp.sort, bp.id LIMIT 1
|
||||||
|
)
|
||||||
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -119,7 +124,9 @@ SELECT auth.*,
|
|||||||
'unitamount', a.unitamount, 'weight', a.weight,
|
'unitamount', a.unitamount, 'weight', a.weight,
|
||||||
'unitSi', u.si, 'unitBasesi', u.basesi,
|
'unitSi', u.si, 'unitBasesi', u.basesi,
|
||||||
'unitBasesifactor', u.basesifactor, 'unitSymbol', ut.symbol,
|
'unitBasesifactor', u.basesifactor, 'unitSymbol', ut.symbol,
|
||||||
'unitName', ut.name, 'brandName', b.name,
|
'unitName', ut.name,
|
||||||
|
'brandId', b.id, 'brandName', b.name,
|
||||||
|
'brandFullHash', bp.hash,
|
||||||
'brandThumbW', bp.thumbw, 'brandThumbH', bp.thumbh,
|
'brandThumbW', bp.thumbw, 'brandThumbH', bp.thumbh,
|
||||||
'brandThumbHash', bp.thumbhash,
|
'brandThumbHash', bp.thumbhash,
|
||||||
'optionsCount', (SELECT COUNT(*) FROM optionslistarticle WHERE optionslist_id = a.optionslist_id),
|
'optionsCount', (SELECT COUNT(*) FROM optionslistarticle WHERE optionslist_id = a.optionslist_id),
|
||||||
|
|||||||
@@ -3,6 +3,59 @@ import { translateQueries } from './translate.js';
|
|||||||
|
|
||||||
const queries = translateQueries(source, 'brand');
|
const queries = translateQueries(source, 'brand');
|
||||||
export const {
|
export const {
|
||||||
CREATE, DELETE_BY_COMPANY_AND_ID, GET_BY_COMPANY_AND_NAME,
|
CREATE, DELETE_BY_COMPANY_AND_ID, UPDATE_BY_ID, GET_BY_ID_AND_COMPANY,
|
||||||
LIST_BY_COMPANY, UPDATE_BY_ID,
|
} = queries;
|
||||||
} = queries;
|
|
||||||
|
export const LIST_BY_COMPANY = `
|
||||||
|
SELECT
|
||||||
|
b.id,
|
||||||
|
b.name,
|
||||||
|
b.gsprpinfo,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(json_group_array(json_object(
|
||||||
|
'id', id,
|
||||||
|
'sort', sort,
|
||||||
|
'fullHash', hash,
|
||||||
|
'thumbW', thumbw,
|
||||||
|
'thumbH', thumbh,
|
||||||
|
'thumbHash', thumbhash
|
||||||
|
)), '[]')
|
||||||
|
FROM (
|
||||||
|
SELECT bp.id, bp.sort, p.hash, p.thumbw, p.thumbh, p.thumbhash
|
||||||
|
FROM brandpic bp
|
||||||
|
INNER JOIN pic p ON p.id = bp.pic_id
|
||||||
|
WHERE bp.brand_id = b.id
|
||||||
|
ORDER BY bp.sort, bp.id
|
||||||
|
)
|
||||||
|
) AS picsJson
|
||||||
|
FROM brand b
|
||||||
|
WHERE b.company_id = @companyId
|
||||||
|
ORDER BY b.name
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GET_BY_COMPANY_AND_NAME = `
|
||||||
|
SELECT
|
||||||
|
b.id,
|
||||||
|
b.name,
|
||||||
|
b.gsprpinfo,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(json_group_array(json_object(
|
||||||
|
'id', id,
|
||||||
|
'sort', sort,
|
||||||
|
'fullHash', hash,
|
||||||
|
'thumbW', thumbw,
|
||||||
|
'thumbH', thumbh,
|
||||||
|
'thumbHash', thumbhash
|
||||||
|
)), '[]')
|
||||||
|
FROM (
|
||||||
|
SELECT bp.id, bp.sort, p.hash, p.thumbw, p.thumbh, p.thumbhash
|
||||||
|
FROM brandpic bp
|
||||||
|
INNER JOIN pic p ON p.id = bp.pic_id
|
||||||
|
WHERE bp.brand_id = b.id
|
||||||
|
ORDER BY bp.sort, bp.id
|
||||||
|
)
|
||||||
|
) AS picsJson
|
||||||
|
FROM brand b
|
||||||
|
WHERE b.company_id = @companyId
|
||||||
|
AND b.name = @name
|
||||||
|
`;
|
||||||
5
src/providers/sqlite3/queries/brandpic.js
Normal file
5
src/providers/sqlite3/queries/brandpic.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import * as source from '../../mssql/queries/brandpic.js';
|
||||||
|
import { translateQueries } from './translate.js';
|
||||||
|
|
||||||
|
const queries = translateQueries(source, 'brandpic');
|
||||||
|
export const { ADD, DELETE_BY_BRAND, GET_FIRST_PIC_BY_BRAND, GET_NEXT_SORT } = queries;
|
||||||
@@ -18,7 +18,7 @@ export default function getBySku(provider) {
|
|||||||
* @param {number} googleAuthId - The id of the Google auth user.
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
* @param {string} sku - The article SKU.
|
* @param {string} sku - The article SKU.
|
||||||
* @param {object} [tx] - Optional transaction.
|
* @param {object} [tx] - Optional transaction.
|
||||||
* @returns {Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {name: string, thumbW: number, thumbH: number, thumbHash: string}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null}}>} The full article detail.
|
* @returns {Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null}}>} The full article detail.
|
||||||
* @throws {Error} If the article is not found or the user is not authorized.
|
* @throws {Error} If the article is not found or the user is not authorized.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
@@ -65,10 +65,12 @@ export default function getBySku(provider) {
|
|||||||
},
|
},
|
||||||
brand: a.brandName
|
brand: a.brandName
|
||||||
? {
|
? {
|
||||||
|
id: a.brandId ?? null,
|
||||||
name: a.brandName,
|
name: a.brandName,
|
||||||
thumbW: a.brandThumbW,
|
fullHash: a.brandFullHash ?? null,
|
||||||
thumbH: a.brandThumbH,
|
thumbW: a.brandThumbW ?? null,
|
||||||
thumbHash: a.brandThumbHash,
|
thumbH: a.brandThumbH ?? null,
|
||||||
|
thumbHash: a.brandThumbHash ?? null,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
categoryIds: parseJsonArray(a.categoryIdsJson).map((r) => r.category_id),
|
categoryIds: parseJsonArray(a.categoryIdsJson).map((r) => r.category_id),
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ function shapeArticleDetail(row, categoryIds, photos, eans, prices) {
|
|||||||
},
|
},
|
||||||
brand: row.brandName
|
brand: row.brandName
|
||||||
? {
|
? {
|
||||||
|
id: row.brandId ?? null,
|
||||||
name: row.brandName,
|
name: row.brandName,
|
||||||
thumbW: row.brandThumbW,
|
fullHash: row.brandFullHash ?? null,
|
||||||
thumbH: row.brandThumbH,
|
thumbW: row.brandThumbW ?? null,
|
||||||
thumbHash: row.brandThumbHash,
|
thumbH: row.brandThumbH ?? null,
|
||||||
|
thumbHash: row.brandThumbHash ?? null,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
categoryIds: categoryIds.map((r) => r.categoryId),
|
categoryIds: categoryIds.map((r) => r.categoryId),
|
||||||
|
|||||||
104
src/store/brand/addImage.js
Normal file
104
src/store/brand/addImage.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import {
|
||||||
|
ADD as MSSQL_ADD_BRANDPIC,
|
||||||
|
DELETE_BY_BRAND as MSSQL_DELETE_BRANDPIC,
|
||||||
|
} from '../../providers/mssql/queries/brandpic.js';
|
||||||
|
import {
|
||||||
|
ADD as SQLITE_ADD_BRANDPIC,
|
||||||
|
DELETE_BY_BRAND as SQLITE_DELETE_BRANDPIC,
|
||||||
|
} from '../../providers/sqlite3/queries/brandpic.js';
|
||||||
|
import { CREATE as MSSQL_CREATE_PIC } from '../../providers/mssql/queries/pic.js';
|
||||||
|
import { CREATE as SQLITE_CREATE_PIC } from '../../providers/sqlite3/queries/pic.js';
|
||||||
|
import processAvifImage from '../../util/processAvifImage.js';
|
||||||
|
import newFullHash from '../../util/newFullHash.js';
|
||||||
|
import resolveBrandAccess from './resolveBrandAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
export default function addImage(provider) {
|
||||||
|
const access = resolveBrandAccess(provider);
|
||||||
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
|
|
||||||
|
const deleteBrandPicSql = isSqlite ? SQLITE_DELETE_BRANDPIC : MSSQL_DELETE_BRANDPIC;
|
||||||
|
const createPicSql = isSqlite ? SQLITE_CREATE_PIC : MSSQL_CREATE_PIC;
|
||||||
|
const addBrandPicSql = isSqlite ? SQLITE_ADD_BRANDPIC : MSSQL_ADD_BRANDPIC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an image to a brand, processing it into AVIF full and thumb variants.
|
||||||
|
*
|
||||||
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
|
* @param {number} brandId - The id of the brand.
|
||||||
|
* @param {Buffer} image - The source image buffer.
|
||||||
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
|
* @returns {Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, pics: Array<object>}>}
|
||||||
|
*/
|
||||||
|
return async (googleAuthId, brandId, image, tx) => {
|
||||||
|
const runAdd = async (runner) => {
|
||||||
|
await access(googleAuthId, brandId, runner);
|
||||||
|
|
||||||
|
const processed = await processAvifImage(image);
|
||||||
|
|
||||||
|
// Clean existing brandpic so the brand has 1 primary logo
|
||||||
|
await runner.query(deleteBrandPicSql, { brandId });
|
||||||
|
|
||||||
|
const sort = 0;
|
||||||
|
const fullHash = newFullHash();
|
||||||
|
|
||||||
|
if (isSqlite) {
|
||||||
|
if (runner.writePicFull) {
|
||||||
|
await runner.writePicFull(fullHash, processed.full.buffer);
|
||||||
|
await runner.writePicThumb(fullHash, processed.thumb.buffer);
|
||||||
|
} else if (provider?.writePicFull) {
|
||||||
|
await provider.writePicFull(fullHash, processed.full.buffer);
|
||||||
|
await provider.writePicThumb(fullHash, processed.thumb.buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const picResult = await runner.query(createPicSql, {
|
||||||
|
w: processed.full.w,
|
||||||
|
h: processed.full.h,
|
||||||
|
size: processed.full.size,
|
||||||
|
hash: fullHash,
|
||||||
|
thumbw: processed.thumb.w,
|
||||||
|
thumbh: processed.thumb.h,
|
||||||
|
thumbhash: processed.thumb.hash,
|
||||||
|
source: '',
|
||||||
|
fullbinary: processed.full.buffer,
|
||||||
|
thumbbinary: processed.thumb.buffer,
|
||||||
|
});
|
||||||
|
|
||||||
|
const picId = picResult.rows[0]?.id ?? picResult.insertId;
|
||||||
|
|
||||||
|
const brandPicResult = await runner.query(addBrandPicSql, {
|
||||||
|
brandId,
|
||||||
|
picId,
|
||||||
|
sort,
|
||||||
|
});
|
||||||
|
const brandPicId = brandPicResult.rows[0]?.id ?? brandPicResult.insertId;
|
||||||
|
|
||||||
|
const picObj = {
|
||||||
|
id: brandPicId,
|
||||||
|
sort,
|
||||||
|
fullHash,
|
||||||
|
thumbW: processed.thumb.w,
|
||||||
|
thumbH: processed.thumb.h,
|
||||||
|
thumbHash: processed.thumb.hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: brandId,
|
||||||
|
fullHash,
|
||||||
|
thumbW: processed.thumb.w,
|
||||||
|
thumbH: processed.thumb.h,
|
||||||
|
thumbHash: processed.thumb.hash,
|
||||||
|
pics: [picObj],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
return runAdd(makeRunner(provider, tx));
|
||||||
|
}
|
||||||
|
if (typeof provider?.transaction === 'function') {
|
||||||
|
return provider.transaction(runAdd);
|
||||||
|
}
|
||||||
|
return runAdd(makeRunner(provider));
|
||||||
|
};
|
||||||
|
}
|
||||||
28
src/store/brand/deleteImage.js
Normal file
28
src/store/brand/deleteImage.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { DELETE_BY_BRAND as MSSQL_DELETE } from '../../providers/mssql/queries/brandpic.js';
|
||||||
|
import { DELETE_BY_BRAND as SQLITE_DELETE } from '../../providers/sqlite3/queries/brandpic.js';
|
||||||
|
import resolveBrandAccess from './resolveBrandAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
export default function deleteImage(provider) {
|
||||||
|
const access = resolveBrandAccess(provider);
|
||||||
|
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
|
||||||
|
|
||||||
|
return async (googleAuthId, brandId, tx) => {
|
||||||
|
const runner = makeRunner(provider, tx);
|
||||||
|
await access(googleAuthId, brandId, runner);
|
||||||
|
|
||||||
|
const result = await runner.query(sql, { brandId });
|
||||||
|
|
||||||
|
if (!result.rowsAffected) {
|
||||||
|
throw new Error('Image not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: brandId,
|
||||||
|
thumbW: null,
|
||||||
|
thumbH: null,
|
||||||
|
thumbHash: null,
|
||||||
|
pics: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,6 +3,12 @@ import { LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY } from '../../providers/sqlit
|
|||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
function parsePics(raw) {
|
||||||
|
if (!raw) return [];
|
||||||
|
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
}
|
||||||
|
|
||||||
export default function list(provider) {
|
export default function list(provider) {
|
||||||
const access = resolveSelectedCompanyAccess(provider);
|
const access = resolveSelectedCompanyAccess(provider);
|
||||||
const sql =
|
const sql =
|
||||||
@@ -15,17 +21,22 @@ export default function list(provider) {
|
|||||||
*
|
*
|
||||||
* @param {number} googleAuthId - The id of the Google auth user.
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
* @param {object} [tx] - Optional transaction to run within.
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
* @returns {Promise<Array<{id: number, name: string, gsprpinfo: string|null}>>} The brands.
|
* @returns {Promise<Array<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>>} The brands.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const brands = await store.brand.list(42);
|
* const brands = await store.brand.list(42);
|
||||||
* // => [{ id, name, gsprpinfo }]
|
* // => [{ id, name, gsprpinfo, pics: [{ sort, fullHash, thumbW, thumbH, thumbHash }] }]
|
||||||
*/
|
*/
|
||||||
return async (googleAuthId, tx) => {
|
return async (googleAuthId, tx) => {
|
||||||
const { companyId } = await access(googleAuthId, tx);
|
const { companyId } = await access(googleAuthId, tx);
|
||||||
|
|
||||||
const result = await makeRunner(provider, tx).query(sql, { companyId });
|
const result = await makeRunner(provider, tx).query(sql, { companyId });
|
||||||
|
|
||||||
return result.rows;
|
return result.rows.map((row) => ({
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
gsprpinfo: row.gsprpinfo,
|
||||||
|
pics: parsePics(row.picsJson),
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/store/brand/resolveBrandAccess.js
Normal file
22
src/store/brand/resolveBrandAccess.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { GET_BY_ID_AND_COMPANY as MSSQL_GET } from '../../providers/mssql/queries/brand.js';
|
||||||
|
import { GET_BY_ID_AND_COMPANY as SQLITE_GET } from '../../providers/sqlite3/queries/brand.js';
|
||||||
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
export default function resolveBrandAccess(provider) {
|
||||||
|
const access = resolveSelectedCompanyAccess(provider);
|
||||||
|
const sql = provider?.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||||
|
|
||||||
|
return async (googleAuthId, brandId, tx) => {
|
||||||
|
if (!Number.isFinite(brandId) || brandId <= 0) {
|
||||||
|
throw new Error('Brand not found');
|
||||||
|
}
|
||||||
|
const { companyId } = await access(googleAuthId, tx);
|
||||||
|
const runner = makeRunner(provider, tx);
|
||||||
|
const result = await runner.query(sql, { id: brandId, companyId });
|
||||||
|
if (!result.rows[0]) {
|
||||||
|
throw new Error('Brand not found');
|
||||||
|
}
|
||||||
|
return { companyId, brand: result.rows[0] };
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,6 +11,12 @@ import {
|
|||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
|
function parsePics(raw) {
|
||||||
|
if (!raw) return [];
|
||||||
|
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
}
|
||||||
|
|
||||||
export default function upsert(provider) {
|
export default function upsert(provider) {
|
||||||
const access = resolveSelectedCompanyAccess(provider);
|
const access = resolveSelectedCompanyAccess(provider);
|
||||||
const isSqlite = provider?.dialect === 'sqlite3';
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
@@ -28,12 +34,12 @@ export default function upsert(provider) {
|
|||||||
* @param {string} input.name - The brand name.
|
* @param {string} input.name - The brand name.
|
||||||
* @param {string} [input.gsprpinfo] - Optional GSPRP info.
|
* @param {string} [input.gsprpinfo] - Optional GSPRP info.
|
||||||
* @param {object} [tx] - Optional transaction to run within.
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
* @returns {Promise<{id: number, name: string, gsprpinfo: string|null}>} The upserted brand.
|
* @returns {Promise<{id: number, name: string, gsprpinfo: string|null, pics: Array<{id: number, sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>}>} The upserted brand.
|
||||||
* @throws {Error} If the brand is invalid.
|
* @throws {Error} If the brand is invalid.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const brand = await store.brand.upsert(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
|
* const brand = await store.brand.upsert(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
|
||||||
* // => { id, name, gsprpinfo }
|
* // => { id, name, gsprpinfo, pics: [] }
|
||||||
*/
|
*/
|
||||||
return async (googleAuthId, input = {}, tx) => {
|
return async (googleAuthId, input = {}, tx) => {
|
||||||
const name = input.name?.trim();
|
const name = input.name?.trim();
|
||||||
@@ -58,7 +64,12 @@ export default function upsert(provider) {
|
|||||||
name,
|
name,
|
||||||
gsprpinfo,
|
gsprpinfo,
|
||||||
});
|
});
|
||||||
return { id, name, gsprpinfo };
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
gsprpinfo,
|
||||||
|
pics: parsePics(existing.rows[0].picsJson),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const created = await runner.query(createSql, {
|
const created = await runner.query(createSql, {
|
||||||
@@ -73,6 +84,7 @@ export default function upsert(provider) {
|
|||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
gsprpinfo,
|
gsprpinfo,
|
||||||
|
pics: [],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ export default function getCategoryArticles(provider) {
|
|||||||
* @param {number} googleAuthId - The id of the Google auth user.
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
* @param {number} categoryId - The id of the category.
|
* @param {number} categoryId - The id of the category.
|
||||||
* @param {object} [tx] - Optional transaction to run within.
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
* @returns {Promise<Array<object>>} The articles in the category.
|
* @returns {Promise<Array<{id: number, sku: string, name: string, slug: string, net: number|null, taxRate: number|null, optionsCount: number, articleListCount: number, childCount: number, fatherArticleId: number|null, fatherSku: string|null, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null}>>} The articles in the category.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const articles = await store.category.getCategoryArticles(42, 5);
|
* const articles = await store.category.getCategoryArticles(42, 5);
|
||||||
* // => [{ id, sku, name, ... }]
|
* // => [{ id, sku, name, brand, ... }]
|
||||||
*/
|
*/
|
||||||
return async (googleAuthId, categoryId, tx) => {
|
return async (googleAuthId, categoryId, tx) => {
|
||||||
const { languageId, posCustomerGroupId } = await access(
|
const { languageId, posCustomerGroupId } = await access(
|
||||||
|
|||||||
@@ -12,6 +12,29 @@ export default function queryCategoryArticles(provider) {
|
|||||||
posCustomerGroupId,
|
posCustomerGroupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.rows;
|
return result.rows.map((row) => {
|
||||||
|
const {
|
||||||
|
brandId,
|
||||||
|
brandName,
|
||||||
|
brandFullHash,
|
||||||
|
brandThumbW,
|
||||||
|
brandThumbH,
|
||||||
|
brandThumbHash,
|
||||||
|
...rest
|
||||||
|
} = row;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
brand: brandName
|
||||||
|
? {
|
||||||
|
id: brandId ?? null,
|
||||||
|
name: brandName,
|
||||||
|
fullHash: brandFullHash ?? null,
|
||||||
|
thumbW: brandThumbW ?? null,
|
||||||
|
thumbH: brandThumbH ?? null,
|
||||||
|
thumbHash: brandThumbHash ?? null,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const ALLOWED_KEYS = new Set([
|
|||||||
'ui.theme',
|
'ui.theme',
|
||||||
'keys.tavily',
|
'keys.tavily',
|
||||||
'keys.openrouter',
|
'keys.openrouter',
|
||||||
|
'keys.serper',
|
||||||
'labelprinter.addr',
|
'labelprinter.addr',
|
||||||
'labelprinter.type',
|
'labelprinter.type',
|
||||||
]);
|
]);
|
||||||
@@ -21,6 +22,7 @@ const ALLOWED_KEYS = new Set([
|
|||||||
const WRITEONLY_KEYS = new Set([
|
const WRITEONLY_KEYS = new Set([
|
||||||
'keys.tavily',
|
'keys.tavily',
|
||||||
'keys.openrouter',
|
'keys.openrouter',
|
||||||
|
'keys.serper',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export default function set(provider) {
|
export default function set(provider) {
|
||||||
@@ -39,7 +41,7 @@ export default function set(provider) {
|
|||||||
* @param {string} input.key - The setting key (must be in the allow-list).
|
* @param {string} input.key - The setting key (must be in the allow-list).
|
||||||
* @param {string} input.value - The setting value.
|
* @param {string} input.value - The setting value.
|
||||||
* @param {boolean} [input.frontend] - Whether the setting is frontend-visible.
|
* @param {boolean} [input.frontend] - Whether the setting is frontend-visible.
|
||||||
* @param {boolean} [input.writeonly] - Whether the setting is write-only. Defaults to true for `keys.tavily` and `keys.openrouter`, false otherwise.
|
* @param {boolean} [input.writeonly] - Whether the setting is write-only. Defaults to true for `keys.tavily`, `keys.openrouter`, and `keys.serper`, false otherwise.
|
||||||
* @param {object} [tx] - Optional transaction to run within.
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
* @returns {Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}>} The upserted setting.
|
* @returns {Promise<{key: string, value: string, frontend: boolean, writeonly: boolean}>} The upserted setting.
|
||||||
* @throws {Error} If the input is invalid.
|
* @throws {Error} If the input is invalid.
|
||||||
|
|||||||
Reference in New Issue
Block a user