u
This commit is contained in:
@@ -2,6 +2,31 @@
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `article.addChild`
|
||||
|
||||
*Source: `src/store/article/addChild.js`*
|
||||
|
||||
Adds a child article to a father article.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `fatherSku` | `string` | The father article SKU. |
|
||||
| `options` | `object` | |
|
||||
| `[options.attributeValueId]` | `number` | Existing attribute value ID for father's attribute. |
|
||||
| `[options.value]` | `string` | String value to find or create under father's attribute. |
|
||||
| `[options.name]` | `string` | Custom name for child article (defaults to father name + value). |
|
||||
| `[options.unitamount]` | `number` | Optional unitamount override. |
|
||||
| `[options.weight]` | `number` | Optional weight override. |
|
||||
| `[options.track_stock]` | `boolean|number` | Optional track_stock override. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — The created child article detail.
|
||||
|
||||
## `article.addImage`
|
||||
|
||||
*Source: `src/store/article/addImage.js`*
|
||||
@@ -211,6 +236,24 @@ const result = await store.article.importRows({
|
||||
// => { imported: [...], skipped: [...], errors: [...] }
|
||||
```
|
||||
|
||||
## `article.listChildren`
|
||||
|
||||
*Source: `src/store/article/listChildren.js`*
|
||||
|
||||
Lists all children of a father article.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `fatherSku` | `string` | The father article SKU. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<Array<{id: number, sku: string, fathersort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>`
|
||||
|
||||
## `article.listExport`
|
||||
|
||||
*Source: `src/store/article/listExport.js`*
|
||||
@@ -295,6 +338,27 @@ const result = await store.article.orderImages(42, 'SKU-1', { fullHashes: ['a',
|
||||
// => { fullHashes: ['a', 'b'] }
|
||||
```
|
||||
|
||||
## `article.removeChild`
|
||||
|
||||
*Source: `src/store/article/removeChild.js`*
|
||||
|
||||
Removes a child article from a father article, either by unlinking or deleting it.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `fatherSku` | `string` | The father article SKU. |
|
||||
| `childSku` | `string` | The child article SKU. |
|
||||
| `[options]` | `object` | |
|
||||
| `[options.deleteArticle=false]` | `boolean` | Whether to delete the child article entity entirely. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>`
|
||||
|
||||
## `article.setBrandIdBySku`
|
||||
|
||||
*Source: `src/store/article/setBrandIdBySku.js`*
|
||||
@@ -505,6 +569,28 @@ const article = await store.article.setWeightBySku(42, 'SKU-1', 1.5);
|
||||
// => { id, sku, name, ... }
|
||||
```
|
||||
|
||||
## `article.toggleFather`
|
||||
|
||||
*Source: `src/store/article/toggleFather.js`*
|
||||
|
||||
Toggles an article into or out of father mode.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `sku` | `string` | The article SKU. |
|
||||
| `options` | `object` | |
|
||||
| `options.isFather` | `boolean` | Whether the article should be a father article. |
|
||||
| `[options.attributeNameId]` | `number` | The attribute name id required when enabling father mode. |
|
||||
| `[options.fatherAttributeNameId]` | `number` | Alias for attributeNameId. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — The updated article detail.
|
||||
|
||||
## `article.updateBySku`
|
||||
|
||||
*Source: `src/store/article/updateBySku.js`*
|
||||
|
||||
31
API.md
31
API.md
@@ -8,14 +8,18 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
|
||||
> **Convention**: `PK` primary key · `?` nullable · `->tbl` foreign key · `UQ` unique · `s(N)` string · `txt` text · `i` int · `bi` bigint · `f` float · `d` decimal · `b` bit · `dt` datetime · `bin` binary · `=val` default
|
||||
|
||||
- **article**: `id PK, sku?:s(100) UQ, unit_id->unit, unitamount:f, weight:f, track_stock:b=1, brand_id?->brand, articlelist_id?->articlelist, optionslist_id?->optionslist, is_father:b=0, fatherarticle_id?->article, fathersort:i=0, created_at:dt, updated_at:dt, UQ(fatherarticle_id, fathersort)`
|
||||
- **article**: `id PK, sku?:s(100) UQ, unit_id->unit, unitamount:f, weight:f, track_stock:b=1, brand_id?->brand, articlelist_id?->articlelist, optionslist_id?->optionslist, is_father:b=0, fatherarticle_id?->article, fatherattributename_id?->attributename, fathersort:i=0, created_at:dt, updated_at:dt, UQ(fatherarticle_id, fathersort)`
|
||||
- **articleattributevalue**: `id PK, article_id->article, attributevalue_id->attributevalue, sort:i=0, UQ(article_id, attributevalue_id)`
|
||||
- **articleean**: `id PK, article_id->article, ean:s(50) UQ`
|
||||
- **articlehistory**: `id PK, article_id->article, companyuser_id?->companyuser, date:dt, text:txt`
|
||||
- **articlelist**: `id PK`
|
||||
- **articlelistarticle**: `id PK, articlelist_id->articlelist, article_id->article, count:i=1, sort:i=0, UQ(articlelist_id, article_id), UQ(articlelist_id, sort)`
|
||||
- **articlepic**: `id PK, article_id->article, pic_id->pic, sort:i=0, UQ(article_id, pic_id), UQ(article_id, sort)`
|
||||
- **articleproperty**: `id PK, article_id->article, property_id->propertyname, value:txt, sort:i=0`
|
||||
- **articlesupplier**: `id PK, article_id->article, entity_id->entity, price_id?->price, sku?:s(100), stock?:f, pu:f=1, sort:i=0, UQ(article_id, entity_id), UQ(article_id, sort)`
|
||||
- **articletext**: `id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)`
|
||||
- **attributename**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)`
|
||||
- **attributevalue**: `id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)`
|
||||
- **brand**: `id PK, company_id->company, name:s(255), gsprpinfo?:txt, UQ(company_id, name)`
|
||||
- **brandpic**: `id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)`
|
||||
- **category**: `id PK, parent_id?->category, sort:i=0`
|
||||
@@ -32,6 +36,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- **entityhistory**: `id PK, entity_id->entity, companyuser_id?->companyuser, date:dt, text:txt`
|
||||
- **file**: `id PK, hash:s(64), mimetype:s(255), name:s(255), size:bi, created:dt, modified:dt, content:bin, thumbavif?:bin, thumbx?:i, thumby?:i, meta?:txt`
|
||||
- **google_auth**: `id PK, email:s(320) UQ, picture?:s(2048), picturebinary?:bin, sub?:s(255) UQ, created_at:dt, selected_company?->company`
|
||||
- **labeltemplate**: `id PK, company_id->company, name:s(255), type:s(50), width:f, height:f, data:txt, sort:i=0`
|
||||
- **language**: `id PK, iso639:s(10) UQ`
|
||||
- **ledger**: `id PK, manifestarticle_id->manifestarticle, warehouse_id?->warehouse, transaction_type:s(100), amount:f, transaction_text:txt, hash_version:i, prev_hash?:s(128) [append-only]`
|
||||
- **manifest**: `id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt, created_at:dt`
|
||||
@@ -45,6 +50,8 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- **posterminal**: `id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin:s(50)`
|
||||
- **posterminalcategory**: `id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)`
|
||||
- **price**: `id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f`
|
||||
- **printer**: `id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt`
|
||||
- **propertyname**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)`
|
||||
- **syncpos**: `id PK, company_id->company, paircode:s(50) UQ, token?:s(255) UQ`
|
||||
- **tax**: `id PK, rate:d(18,4) UQ`
|
||||
- **unit**: `id PK, company_id->company, si:s(50), basesi:s(50), basesifactor:f, UQ(company_id, si)`
|
||||
@@ -76,6 +83,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `order(input: {googleAuthId: number, parentId: number, categoryIds: Array<number>}, tx?: object): Promise<{categoryIds: Array<number>}>`
|
||||
- `rename(input: {googleAuthId: number, id: number, name: string}, tx?: object): Promise<{id: number, name: string, slug: string}>`
|
||||
- [article](API.article.md)
|
||||
- `addChild(googleAuthId: number, fatherSku: string, options: {attributeValueId?: number, value?: string, name?: string, unitamount?: number, weight?: number, track_stock?: boolean|number}, tx?: object): Promise<object>`
|
||||
- `addImage(googleAuthId: number, sku: string, image: Buffer, tx?: object): Promise<{picId: number, sort: number, full: {w: number, h: number, size: number}, thumb: {w: number, h: number}, fullHash: string, thumbHash: string}>`
|
||||
- `createUnderParent(input: {googleAuthId: number, parentId: number, name: string, track_stock?: boolean|number}, tx?: object): Promise<{id: number, parentId: number, name: string, sku: string, track_stock: boolean}>`
|
||||
- `delete(googleAuthId: number, sku: string, tx?: object): Promise<void>`
|
||||
@@ -83,9 +91,11 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `getBySku(googleAuthId: number, sku: string, tx?: object): Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, track_stock: boolean, 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}>>`
|
||||
- `importRows(input: {googleAuthId: number, rows: Array<object>}, tx?: object): Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>`
|
||||
- `listChildren(googleAuthId: number, fatherSku: string, tx?: object): Promise<Array<{id: number, sku: string, fathersort: 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>>`
|
||||
- `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>}>`
|
||||
- `removeChild(googleAuthId: number, fatherSku: string, childSku: string, options?: {deleteArticle?: boolean}, tx?: object): Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>`
|
||||
- `setBrandIdBySku(googleAuthId: number, sku: string, brandId: number|null, tx?: object): Promise<object>`
|
||||
- `setCategoryIdsBySku(googleAuthId: number, sku: string, categoryIds: Array<number>, tx?: object): Promise<object>`
|
||||
- `setEansBySku(googleAuthId: number, sku: string, eans: Array<string>, tx?: object): Promise<object>`
|
||||
@@ -93,6 +103,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `setPricesBySku(googleAuthId: number, sku: string, prices: Array<{price: number, tax: number, minmultiplier?: number}>, tx?: object): Promise<object>`
|
||||
- `setTrackStockBySku(googleAuthId: number, sku: string, trackStock: boolean|number, tx?: object): Promise<object>`
|
||||
- `setWeightBySku(googleAuthId: number, sku: string, weight: number, tx?: object): Promise<object>`
|
||||
- `toggleFather(googleAuthId: number, sku: string, options: {isFather: boolean, attributeNameId?: number, fatherAttributeNameId?: number}, tx?: object): Promise<object>`
|
||||
- `updateBySku(googleAuthId: number, sku: string, patch: {name?: string, unitSi?: string, unitamount?: number, track_stock?: boolean|number}, tx?: object): Promise<object>`
|
||||
- [lang](API.lang.md)
|
||||
- `create(iso639: string, tx?: object): Promise<number>`
|
||||
@@ -217,9 +228,12 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- [numbers](API.numbers.md)
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, type: string, prefix: string, padding: number, next: number}>>`
|
||||
- `update(googleAuthId: number, id: number, input: {prefix?: string, padding?: number, next?: number}, tx?: object): Promise<{id: number, type: string, prefix: string, padding: number, next: number}>`
|
||||
- [articleattributevalue](API.articleattributevalue.md)
|
||||
- `listByArticleSku(googleAuthId: number, sku: string, tx?: object): Promise<Array<{id: number, articleId: number, attributeId: number, attributeName: string, exclusive: boolean, attributeValueId: number, value: string, sort: number}>>`
|
||||
- `setByArticleSku(googleAuthId: number, sku: string, values: Array<number|{attributeValueId: number, sort?: number}>, tx?: object): Promise<Array<{id: number, articleId: number, attributeId: number, attributeName: string, exclusive: boolean, attributeValueId: number, value: string, sort: number}>>`
|
||||
- [propertyname](API.propertyname.md)
|
||||
- `create(googleAuthId: number, input: {name: string, sort?: number, exclusive?: boolean}, tx?: object): Promise<{id: number, name: string, sort: number, exclusive: boolean}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, sort: number, exclusive: boolean}>>`
|
||||
- `order(googleAuthId: number, input: {propertynameIds: Array<number>}, tx?: object): Promise<{propertynameIds: Array<number>}>`
|
||||
- `update(googleAuthId: number, id: number, input: {name?: string, sort?: number, exclusive?: boolean}, tx?: object): Promise<{id: number, name: string, sort: number, exclusive: boolean}>`
|
||||
- [articleproperty](API.articleproperty.md)
|
||||
- `delete(googleAuthId: number, sku: string, propertyId: number, tx?: object): Promise<void>`
|
||||
- `listByArticleSku(googleAuthId: number, sku: string, tx?: object): Promise<Array<{id: number, articleId: number, propertyId: number, name: string, exclusive: boolean, value: string, sort: number}>>`
|
||||
@@ -236,17 +250,14 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `listByAttributeId(googleAuthId: number, attributeId: number, tx?: object): Promise<Array<{id: number, attributeId: number, value: string, sort: number}>>`
|
||||
- `order(googleAuthId: number, attributeId: number, input: {attributevalueIds: Array<number>}, tx?: object): Promise<{attributevalueIds: Array<number>}>`
|
||||
- `update(googleAuthId: number, id: number, input: {value?: string, sort?: number}, tx?: object): Promise<{id: number, attributeId: number, value: string, sort: number}>`
|
||||
- [articleattributevalue](API.articleattributevalue.md)
|
||||
- `listByArticleSku(googleAuthId: number, sku: string, tx?: object): Promise<Array<{id: number, articleId: number, attributeId: number, attributeName: string, exclusive: boolean, attributeValueId: number, value: string, sort: number}>>`
|
||||
- `setByArticleSku(googleAuthId: number, sku: string, values: Array<number|{attributeValueId: number, sort?: number}>, tx?: object): Promise<Array<{id: number, articleId: number, attributeId: number, attributeName: string, exclusive: boolean, attributeValueId: number, value: string, sort: number}>>`
|
||||
- [labeltemplate](API.labeltemplate.md)
|
||||
- `create(googleAuthId: number, input: {name: string, width: number, height: number, dpi: number, type: string, content: object, thumb?: Buffer|null}, tx?: object): Promise<{id: number, company_id: number, name: string, width: number, height: number, dpi: number, type: string, content: object, thumb: Buffer|null}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `getById(googleAuthId: number, id: number, tx?: object): Promise<{id: number, company_id: number, name: string, width: number, height: number, dpi: number, type: string, content: object, thumb: Buffer|null}|undefined>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, company_id: number, name: string, width: number, height: number, dpi: number, type: string, content: object, thumb: Buffer|null}>>`
|
||||
- `update(googleAuthId: number, id: number, input: {name?: string, width?: number, height?: number, dpi?: number, type?: string, content?: object, thumb?: Buffer|null}, tx?: object): Promise<{id: number, company_id: number, name: string, width: number, height: number, dpi: number, type: string, content: object, thumb: Buffer|null}>`
|
||||
- [propertyname](API.propertyname.md)
|
||||
- `create(googleAuthId: number, input: {name: string, sort?: number, exclusive?: boolean}, tx?: object): Promise<{id: number, name: string, sort: number, exclusive: boolean}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, sort: number, exclusive: boolean}>>`
|
||||
- `order(googleAuthId: number, input: {propertynameIds: Array<number>}, tx?: object): Promise<{propertynameIds: Array<number>}>`
|
||||
- `update(googleAuthId: number, id: number, input: {name?: string, sort?: number, exclusive?: boolean}, tx?: object): Promise<{id: number, name: string, sort: number, exclusive: boolean}>`
|
||||
- [runner.js](API.runner.js.md)
|
||||
- `runner(provider: import('../providers/provider.js').DatabaseProvider, tx?: object): ((sql: string, params?: object) => Promise<import('../providers/provider.js').QueryResult>) & { query: Function, dialect?: string }`
|
||||
|
||||
@@ -36,6 +36,12 @@ const MODULE_ORDER = [
|
||||
'google_auth',
|
||||
'pic',
|
||||
'numbers',
|
||||
'propertyname',
|
||||
'articleproperty',
|
||||
'attributename',
|
||||
'attributevalue',
|
||||
'articleattributevalue',
|
||||
'labeltemplate',
|
||||
];
|
||||
|
||||
// Files that are internal helpers and should not appear as public API entries.
|
||||
@@ -313,14 +319,18 @@ function render() {
|
||||
}
|
||||
|
||||
const SCHEMA_TABLES = [
|
||||
['article', 'id PK, sku?:s(100) UQ, unit_id->unit, unitamount:f, weight:f, track_stock:b=1, brand_id?->brand, articlelist_id?->articlelist, optionslist_id?->optionslist, is_father:b=0, fatherarticle_id?->article, fathersort:i=0, created_at:dt, updated_at:dt, UQ(fatherarticle_id, fathersort)'],
|
||||
['article', 'id PK, sku?:s(100) UQ, unit_id->unit, unitamount:f, weight:f, track_stock:b=1, brand_id?->brand, articlelist_id?->articlelist, optionslist_id?->optionslist, is_father:b=0, fatherarticle_id?->article, fatherattributename_id?->attributename, fathersort:i=0, created_at:dt, updated_at:dt, UQ(fatherarticle_id, fathersort)'],
|
||||
['articleattributevalue', 'id PK, article_id->article, attributevalue_id->attributevalue, sort:i=0, UQ(article_id, attributevalue_id)'],
|
||||
['articleean', 'id PK, article_id->article, ean:s(50) UQ'],
|
||||
['articlehistory', 'id PK, article_id->article, companyuser_id?->companyuser, date:dt, text:txt'],
|
||||
['articlelist', 'id PK'],
|
||||
['articlelistarticle', 'id PK, articlelist_id->articlelist, article_id->article, count:i=1, sort:i=0, UQ(articlelist_id, article_id), UQ(articlelist_id, sort)'],
|
||||
['articlepic', 'id PK, article_id->article, pic_id->pic, sort:i=0, UQ(article_id, pic_id), UQ(article_id, sort)'],
|
||||
['articleproperty', 'id PK, article_id->article, property_id->propertyname, value:txt, sort:i=0'],
|
||||
['articlesupplier', 'id PK, article_id->article, entity_id->entity, price_id?->price, sku?:s(100), stock?:f, pu:f=1, sort:i=0, UQ(article_id, entity_id), UQ(article_id, sort)'],
|
||||
['articletext', 'id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)'],
|
||||
['attributename', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'],
|
||||
['attributevalue', 'id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)'],
|
||||
['brand', 'id PK, company_id->company, name:s(255), gsprpinfo?:txt, UQ(company_id, name)'],
|
||||
['brandpic', 'id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)'],
|
||||
['category', 'id PK, parent_id?->category, sort:i=0'],
|
||||
@@ -337,6 +347,7 @@ const SCHEMA_TABLES = [
|
||||
['entityhistory', 'id PK, entity_id->entity, companyuser_id?->companyuser, date:dt, text:txt'],
|
||||
['file', 'id PK, hash:s(64), mimetype:s(255), name:s(255), size:bi, created:dt, modified:dt, content:bin, thumbavif?:bin, thumbx?:i, thumby?:i, meta?:txt'],
|
||||
['google_auth', 'id PK, email:s(320) UQ, picture?:s(2048), picturebinary?:bin, sub?:s(255) UQ, created_at:dt, selected_company?->company'],
|
||||
['labeltemplate', 'id PK, company_id->company, name:s(255), type:s(50), width:f, height:f, data:txt, sort:i=0'],
|
||||
['language', 'id PK, iso639:s(10) UQ'],
|
||||
['ledger', 'id PK, manifestarticle_id->manifestarticle, warehouse_id?->warehouse, transaction_type:s(100), amount:f, transaction_text:txt, hash_version:i, prev_hash?:s(128) [append-only]'],
|
||||
['manifest', 'id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt, created_at:dt'],
|
||||
@@ -350,6 +361,8 @@ const SCHEMA_TABLES = [
|
||||
['posterminal', 'id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin:s(50)'],
|
||||
['posterminalcategory', 'id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)'],
|
||||
['price', 'id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f'],
|
||||
['printer', 'id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt'],
|
||||
['propertyname', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'],
|
||||
['syncpos', 'id PK, company_id->company, paircode:s(50) UQ, token?:s(255) UQ'],
|
||||
['tax', 'id PK, rate:d(18,4) UQ'],
|
||||
['unit', 'id PK, company_id->company, si:s(50), basesi:s(50), basesifactor:f, UQ(company_id, si)'],
|
||||
|
||||
@@ -31,6 +31,10 @@ import articleSetWeightBySku from './store/article/setWeightBySku.js';
|
||||
import articleSetTrackStockBySku from './store/article/setTrackStockBySku.js';
|
||||
import articleSetCategoryIdsBySku from './store/article/setCategoryIdsBySku.js';
|
||||
import articleSetFatherBySku from './store/article/setFatherBySku.js';
|
||||
import articleToggleFather from './store/article/toggleFather.js';
|
||||
import articleAddChild from './store/article/addChild.js';
|
||||
import articleRemoveChild from './store/article/removeChild.js';
|
||||
import articleListChildren from './store/article/listChildren.js';
|
||||
import articleImportRows from './store/article/importRows.js';
|
||||
import categoryEnsureByPath from './store/category/ensureByPath.js';
|
||||
import unitList from './store/unit/list.js';
|
||||
@@ -226,6 +230,10 @@ export default class LibDashStore {
|
||||
setTrackStockBySku: articleSetTrackStockBySku(provider),
|
||||
setCategoryIdsBySku: articleSetCategoryIdsBySku(provider),
|
||||
setFatherBySku: articleSetFatherBySku(provider),
|
||||
toggleFather: articleToggleFather(provider),
|
||||
addChild: articleAddChild(provider),
|
||||
removeChild: articleRemoveChild(provider),
|
||||
listChildren: articleListChildren(provider),
|
||||
importRows: articleImportRows(provider),
|
||||
addImage: articleAddImage(provider),
|
||||
deleteImage: articleDeleteImage(provider),
|
||||
|
||||
@@ -102,6 +102,50 @@ SET is_father = 1
|
||||
WHERE id = @articleId
|
||||
`;
|
||||
|
||||
export const TOGGLE_FATHER = `
|
||||
UPDATE article
|
||||
SET is_father = @isFather,
|
||||
fatherattributename_id = @fatherAttributeNameId
|
||||
WHERE id = @articleId
|
||||
`;
|
||||
|
||||
export const CREATE_CHILD = `
|
||||
INSERT INTO article (
|
||||
company_id, sku, unit_id, unitamount, weight, track_stock, brand_id, is_father, fatherarticle_id, fathersort
|
||||
)
|
||||
OUTPUT INSERTED.id
|
||||
VALUES (
|
||||
@companyId, @sku, @unitId, @unitamount, @weight, @trackStock, @brandId, 0, @fatherArticleId, @fathersort
|
||||
)
|
||||
`;
|
||||
|
||||
export const UNLINK_CHILD = `
|
||||
UPDATE article
|
||||
SET fatherarticle_id = NULL,
|
||||
fathersort = 0
|
||||
WHERE id = @articleId AND fatherarticle_id = @fatherArticleId
|
||||
`;
|
||||
|
||||
export const LIST_CHILDREN_BY_FATHER_ID = `
|
||||
SELECT
|
||||
child.id,
|
||||
child.sku,
|
||||
child.fathersort,
|
||||
child.track_stock AS trackStock,
|
||||
child.unitamount,
|
||||
child.weight,
|
||||
at_child.name,
|
||||
at_child.slug,
|
||||
av.id AS attributeValueId,
|
||||
av.value AS attributeValue
|
||||
FROM article child
|
||||
LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = @languageId
|
||||
LEFT JOIN articleattributevalue aav ON aav.article_id = child.id
|
||||
LEFT JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = @attributeId
|
||||
WHERE child.fatherarticle_id = @fatherArticleId
|
||||
ORDER BY child.fathersort ASC, child.id ASC
|
||||
`;
|
||||
|
||||
export const SET_FATHER_SORT = `
|
||||
UPDATE article
|
||||
SET fathersort = @fathersort
|
||||
@@ -406,8 +450,28 @@ SELECT
|
||||
FROM article child
|
||||
WHERE child.fatherarticle_id = a.id
|
||||
) AS childCount,
|
||||
a.is_father AS isFather,
|
||||
a.fatherattributename_id AS fatherAttributeNameId,
|
||||
fan.name AS fatherAttributeName,
|
||||
fan.exclusive AS fatherAttributeExclusive,
|
||||
a.fatherarticle_id AS fatherArticleId,
|
||||
fa.sku AS fatherSku,
|
||||
(
|
||||
SELECT
|
||||
child.id,
|
||||
child.sku,
|
||||
child.fathersort,
|
||||
at_child.name,
|
||||
av.id AS attributeValueId,
|
||||
av.value AS attributeValue
|
||||
FROM article child
|
||||
LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = auth.languageId
|
||||
LEFT JOIN articleattributevalue aav ON aav.article_id = child.id
|
||||
LEFT JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.fatherattributename_id
|
||||
WHERE child.fatherarticle_id = a.id
|
||||
ORDER BY child.fathersort ASC, child.id ASC
|
||||
FOR JSON PATH
|
||||
) AS childrenJson,
|
||||
(
|
||||
SELECT ca2.category_id
|
||||
FROM categoryarticle ca2
|
||||
@@ -510,6 +574,7 @@ SELECT
|
||||
) first_brand_pic
|
||||
LEFT JOIN pic bp ON bp.id = first_brand_pic.pic_id
|
||||
LEFT JOIN article fa ON fa.id = a.fatherarticle_id
|
||||
LEFT JOIN attributename fan ON fan.id = a.fatherattributename_id
|
||||
WHERE a.id = auth.articleId
|
||||
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
|
||||
) AS articleJson
|
||||
|
||||
@@ -323,6 +323,7 @@ BEGIN
|
||||
optionslist_id INT NULL,
|
||||
is_father BIT NOT NULL CONSTRAINT DF_article_is_father DEFAULT 0,
|
||||
fatherarticle_id INT NULL,
|
||||
fatherattributename_id INT NULL,
|
||||
fathersort INT NOT NULL CONSTRAINT DF_article_fathersort DEFAULT 0,
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_article_created_at DEFAULT SYSUTCDATETIME(),
|
||||
updated_at DATETIME2 NOT NULL CONSTRAINT DF_article_updated_at DEFAULT SYSUTCDATETIME(),
|
||||
@@ -332,6 +333,7 @@ BEGIN
|
||||
CONSTRAINT FK_article_articlelist FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE SET NULL,
|
||||
CONSTRAINT FK_article_optionslist FOREIGN KEY (optionslist_id) REFERENCES optionslist(id) ON DELETE SET NULL,
|
||||
CONSTRAINT FK_article_fatherarticle FOREIGN KEY (fatherarticle_id) REFERENCES article(id) ON DELETE NO ACTION,
|
||||
CONSTRAINT FK_article_fatherattributename FOREIGN KEY (fatherattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION,
|
||||
CONSTRAINT UQ_article_company_sku UNIQUE (company_id, sku)
|
||||
);
|
||||
|
||||
@@ -341,6 +343,14 @@ BEGIN
|
||||
ON article (fatherarticle_id, fathersort)
|
||||
WHERE fatherarticle_id IS NOT NULL;
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('article') AND name = 'fatherattributename_id')
|
||||
BEGIN
|
||||
ALTER TABLE article ADD fatherattributename_id INT NULL;
|
||||
ALTER TABLE article ADD CONSTRAINT FK_article_fatherattributename FOREIGN KEY (fatherattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION;
|
||||
END
|
||||
END
|
||||
ELSE IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('article') AND name = 'track_stock')
|
||||
BEGIN
|
||||
ALTER TABLE article ADD track_stock BIT NOT NULL CONSTRAINT DF_article_track_stock DEFAULT 1;
|
||||
@@ -988,6 +998,9 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_OPTIONSLIST,
|
||||
INIT_ARTICLELIST,
|
||||
INIT_BRAND,
|
||||
INIT_PROPERTYNAME,
|
||||
INIT_ATTRIBUTENAME,
|
||||
INIT_ATTRIBUTEVALUE,
|
||||
INIT_ARTICLE,
|
||||
INIT_ARTICLEEAN,
|
||||
INIT_ARTICLEPIC,
|
||||
@@ -1000,10 +1013,7 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_ARTICLENAME,
|
||||
INIT_PRICE,
|
||||
INIT_ARTICLE_SUPPLIER,
|
||||
INIT_PROPERTYNAME,
|
||||
INIT_ARTICLEPROPERTY,
|
||||
INIT_ATTRIBUTENAME,
|
||||
INIT_ATTRIBUTEVALUE,
|
||||
INIT_ARTICLEATTRIBUTEVALUE,
|
||||
INIT_MANIFEST,
|
||||
INIT_MANIFESTARTICLE,
|
||||
|
||||
@@ -406,6 +406,11 @@ export class BetterSqlite3Provider extends BetterSqlite3Executor {
|
||||
`ALTER TABLE article ADD COLUMN track_stock INTEGER NOT NULL DEFAULT 1`,
|
||||
);
|
||||
}
|
||||
if (articleCols.length > 0 && !articleCols.includes('fatherattributename_id')) {
|
||||
this.db.exec(
|
||||
`ALTER TABLE article ADD COLUMN fatherattributename_id INTEGER REFERENCES attributename(id) ON DELETE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
const warehouseCols = this.db.pragma('table_info("warehouse")').map((c) => c.name);
|
||||
if (warehouseCols.length > 0 && !warehouseCols.includes('sort')) {
|
||||
|
||||
@@ -3,12 +3,13 @@ import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'article');
|
||||
export const {
|
||||
ADD_CATEGORY, ADD_EAN, ARTICLE_LIST_COLUMNS, CREATE,
|
||||
ADD_CATEGORY, ADD_EAN, ARTICLE_LIST_COLUMNS, CREATE, CREATE_CHILD,
|
||||
DELETE_CATEGORIES_BY_ARTICLE, DELETE_EANS_BY_ARTICLE, EAN_EXISTS,
|
||||
GET_BRAND_BY_COMPANY_AND_ID,
|
||||
GET_CATEGORY_ID_BY_SKU, GET_CATEGORY_TREE_FLAT, GET_ID_BY_SKU,
|
||||
LIST_CHILDREN_BY_FATHER_ID,
|
||||
SET_FATHER_SORT,
|
||||
SET_IS_FATHER, UPDATE_BRAND, UPDATE_FATHER_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT,
|
||||
SET_IS_FATHER, TOGGLE_FATHER, UNLINK_CHILD, UPDATE_BRAND, UPDATE_FATHER_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT,
|
||||
UPDATE_UNITAMOUNT, UPDATE_UNIT_AND_AMOUNT, UPDATE_WEIGHT,
|
||||
} = queries;
|
||||
|
||||
@@ -133,7 +134,24 @@ SELECT auth.*,
|
||||
'optionsCount', (SELECT COUNT(*) FROM optionslistarticle WHERE optionslist_id = a.optionslist_id),
|
||||
'articleListCount', (SELECT COUNT(*) FROM articlelistarticle WHERE articlelist_id = a.articlelist_id),
|
||||
'childCount', (SELECT COUNT(*) FROM article WHERE fatherarticle_id = a.id),
|
||||
'isFather', a.is_father,
|
||||
'fatherAttributeNameId', a.fatherattributename_id,
|
||||
'fatherAttributeName', fan.name,
|
||||
'fatherAttributeExclusive', fan.exclusive,
|
||||
'fatherArticleId', a.fatherarticle_id, 'fatherSku', father.sku,
|
||||
'childrenJson', json((SELECT COALESCE(json_group_array(json_object(
|
||||
'id', child.id,
|
||||
'sku', child.sku,
|
||||
'fathersort', child.fathersort,
|
||||
'name', at_child.name,
|
||||
'attributeValueId', av.id,
|
||||
'attributeValue', av.value
|
||||
)), '[]') FROM article child
|
||||
LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = auth.languageId
|
||||
LEFT JOIN articleattributevalue aav ON aav.article_id = child.id
|
||||
LEFT JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.fatherattributename_id
|
||||
WHERE child.fatherarticle_id = a.id
|
||||
ORDER BY child.fathersort ASC, child.id ASC)),
|
||||
'categoryIdsJson', json(${JSON_CATEGORIES}),
|
||||
'photosJson', json((SELECT COALESCE(json_group_array(json_object(
|
||||
'sort', sort, 'fullHash', hash, 'fullW', w, 'fullH', h,
|
||||
@@ -197,4 +215,5 @@ LEFT JOIN pic bp ON bp.id = (
|
||||
SELECT pic_id FROM brandpic WHERE brand_id = b.id ORDER BY sort, id LIMIT 1
|
||||
)
|
||||
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
||||
LEFT JOIN attributename fan ON fan.id = a.fatherattributename_id
|
||||
`;
|
||||
@@ -223,6 +223,7 @@ CREATE TABLE IF NOT EXISTS article (
|
||||
is_father INTEGER NOT NULL DEFAULT 0,
|
||||
fatherarticle_id INTEGER,
|
||||
fathersort INTEGER NOT NULL DEFAULT 0,
|
||||
fatherattributename_id INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
updated_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
UNIQUE (company_id, sku),
|
||||
@@ -231,7 +232,8 @@ CREATE TABLE IF NOT EXISTS article (
|
||||
FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (optionslist_id) REFERENCES optionslist(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (fatherarticle_id) REFERENCES article(id) ON DELETE NO ACTION
|
||||
FOREIGN KEY (fatherarticle_id) REFERENCES article(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (fatherattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_article_fatherarticle_fathersort
|
||||
ON article(fatherarticle_id, fathersort)
|
||||
@@ -699,6 +701,9 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_OPTIONSLIST,
|
||||
INIT_ARTICLELIST,
|
||||
INIT_BRAND,
|
||||
INIT_PROPERTYNAME,
|
||||
INIT_ATTRIBUTENAME,
|
||||
INIT_ATTRIBUTEVALUE,
|
||||
INIT_ARTICLE,
|
||||
INIT_ARTICLEEAN,
|
||||
INIT_ARTICLEPIC,
|
||||
@@ -711,10 +716,7 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_ARTICLENAME,
|
||||
INIT_PRICE,
|
||||
INIT_ARTICLE_SUPPLIER,
|
||||
INIT_PROPERTYNAME,
|
||||
INIT_ARTICLEPROPERTY,
|
||||
INIT_ATTRIBUTENAME,
|
||||
INIT_ATTRIBUTEVALUE,
|
||||
INIT_ARTICLEATTRIBUTEVALUE,
|
||||
INIT_MANIFEST,
|
||||
INIT_MANIFESTARTICLE,
|
||||
|
||||
197
src/store/article/addChild.js
Normal file
197
src/store/article/addChild.js
Normal file
@@ -0,0 +1,197 @@
|
||||
import * as mssqlArticle from '../../providers/mssql/queries/article.js';
|
||||
import * as sqliteArticle from '../../providers/sqlite3/queries/article.js';
|
||||
import { GET_CHILD_COUNT as MSSQL_GET_CHILD_COUNT } from '../../providers/mssql/queries/articleDelete.js';
|
||||
import { GET_CHILD_COUNT as SQLITE_GET_CHILD_COUNT } from '../../providers/sqlite3/queries/articleDelete.js';
|
||||
import * as mssqlAttrVal from '../../providers/mssql/queries/attributevalue.js';
|
||||
import * as sqliteAttrVal from '../../providers/sqlite3/queries/attributevalue.js';
|
||||
import * as mssqlArticleAttrVal from '../../providers/mssql/queries/articleattributevalue.js';
|
||||
import * as sqliteArticleAttrVal from '../../providers/sqlite3/queries/articleattributevalue.js';
|
||||
import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/categoryarticle.js';
|
||||
import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js';
|
||||
import { CREATE as MSSQL_CREATE_ARTICLETEXT } from '../../providers/mssql/queries/articletext.js';
|
||||
import { CREATE as SQLITE_CREATE_ARTICLETEXT } from '../../providers/sqlite3/queries/articletext.js';
|
||||
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import getBySku from './getBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import slugify from '../slugify.js';
|
||||
|
||||
export default function addChild(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const load = getBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT;
|
||||
const attrValQueries = isSqlite ? sqliteAttrVal : mssqlAttrVal;
|
||||
const articleAttrValQueries = isSqlite ? sqliteArticleAttrVal : mssqlArticleAttrVal;
|
||||
const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE;
|
||||
const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT;
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Adds a child article to a father article.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {object} options
|
||||
* @param {number} [options.attributeValueId] - Existing attribute value ID for father's attribute.
|
||||
* @param {string} [options.value] - String value to find or create under father's attribute.
|
||||
* @param {string} [options.name] - Custom name for child article (defaults to father name + value).
|
||||
* @param {number} [options.unitamount] - Optional unitamount override.
|
||||
* @param {number} [options.weight] - Optional weight override.
|
||||
* @param {boolean|number} [options.track_stock] - Optional track_stock override.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The created child article detail.
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, options = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, companyId, languageId, companyuserId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
runner,
|
||||
);
|
||||
|
||||
// Fetch father article fields
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT id, sku, company_id, unit_id, unitamount, weight, track_stock, brand_id, is_father, fatherarticle_id, fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
);
|
||||
const father = fatherRes.rows[0];
|
||||
if (!father) {
|
||||
throw new Error('Father article not found');
|
||||
}
|
||||
if (!father.is_father || !father.fatherattributename_id) {
|
||||
throw new Error('Article is not a father article');
|
||||
}
|
||||
|
||||
// Fetch father's text / name
|
||||
const fatherTextRes = await runner.query(
|
||||
'SELECT name FROM articletext WHERE article_id = @fatherId AND language_id = @languageId',
|
||||
{ fatherId, languageId },
|
||||
);
|
||||
const fatherName = fatherTextRes.rows[0]?.name || father.sku;
|
||||
|
||||
// Determine attribute value
|
||||
let finalValueId = null;
|
||||
let finalValue = null;
|
||||
|
||||
if (options.attributeValueId != null) {
|
||||
const valRes = await runner.query(attrValQueries.GET_BY_ID_AND_COMPANY, {
|
||||
id: Number(options.attributeValueId),
|
||||
companyId,
|
||||
});
|
||||
const row = valRes.rows[0];
|
||||
if (!row || row.attributeId !== father.fatherattributename_id) {
|
||||
throw new Error('Attribute value not found or does not match father attribute');
|
||||
}
|
||||
finalValueId = row.id;
|
||||
finalValue = row.value;
|
||||
} else if (typeof options.value === 'string' && options.value.trim()) {
|
||||
const valStr = options.value.trim();
|
||||
const existingValRes = await runner.query(attrValQueries.GET_BY_ATTRIBUTE_AND_VALUE, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
value: valStr,
|
||||
});
|
||||
if (existingValRes.rows[0]) {
|
||||
finalValueId = existingValRes.rows[0].id;
|
||||
finalValue = existingValRes.rows[0].value;
|
||||
} else {
|
||||
const sortRes = await runner.query(attrValQueries.GET_NEXT_SORT, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
});
|
||||
const sort = sortRes.rows[0]?.sort ?? 0;
|
||||
const createdValRes = await runner.query(attrValQueries.CREATE, {
|
||||
attributeId: father.fatherattributename_id,
|
||||
value: valStr,
|
||||
sort,
|
||||
});
|
||||
finalValueId = createdValRes.rows[0]?.id ?? createdValRes.insertId;
|
||||
finalValue = valStr;
|
||||
}
|
||||
} else {
|
||||
throw new Error('Attribute value is required for child article');
|
||||
}
|
||||
|
||||
// Compute child SKU: father.sku + '-' + finalValueId
|
||||
const childSku = `${father.sku}-${finalValueId}`;
|
||||
|
||||
// Check if child SKU already exists
|
||||
const existingChildRes = await runner.query(
|
||||
'SELECT id FROM article WHERE sku = @childSku AND company_id = @companyId',
|
||||
{ childSku, companyId },
|
||||
);
|
||||
if (existingChildRes.rows[0]) {
|
||||
throw new Error(`Child article "${childSku}" already exists`);
|
||||
}
|
||||
|
||||
// Next fathersort
|
||||
const countRes = await runner.query(getChildCountSql, { articleId: fatherId });
|
||||
const fathersort = countRes.rows[0]?.count ?? 0;
|
||||
|
||||
// Create child article
|
||||
const rawTrack = options.track_stock ?? options.trackStock;
|
||||
const finalTrackStock = rawTrack == null ? father.track_stock : (rawTrack ? 1 : 0);
|
||||
|
||||
const childRes = await runner.query(queries.CREATE_CHILD, {
|
||||
companyId,
|
||||
sku: childSku,
|
||||
unitId: father.unit_id,
|
||||
unitamount: options.unitamount ?? father.unitamount,
|
||||
weight: options.weight ?? father.weight,
|
||||
trackStock: finalTrackStock,
|
||||
brandId: father.brand_id,
|
||||
fatherArticleId: fatherId,
|
||||
fathersort,
|
||||
});
|
||||
const childId = childRes.rows[0]?.id ?? childRes.insertId;
|
||||
|
||||
// Link articleattributevalue
|
||||
await runner.query(articleAttrValQueries.INSERT_ROW, {
|
||||
articleId: childId,
|
||||
attributeValueId: finalValueId,
|
||||
sort: 0,
|
||||
});
|
||||
|
||||
// Copy father's categories
|
||||
const catRes = await runner.query(
|
||||
'SELECT category_id FROM categoryarticle WHERE article_id = @fatherId',
|
||||
{ fatherId },
|
||||
);
|
||||
for (const catRow of catRes.rows) {
|
||||
await runner.query(addCategoryArticleSql, {
|
||||
categoryId: catRow.category_id,
|
||||
articleId: childId,
|
||||
});
|
||||
}
|
||||
|
||||
// Create article text
|
||||
const childName = options.name?.trim() || `${fatherName} - ${finalValue}`;
|
||||
const childSlug = slugify(childName);
|
||||
await runner.query(createArticleTextSql, {
|
||||
articleId: childId,
|
||||
languageId,
|
||||
name: childName,
|
||||
slug: childSlug,
|
||||
});
|
||||
|
||||
// History
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: childId,
|
||||
companyuserId,
|
||||
text: `Artikel "${childName}" (${childSku}) als Variante von "${father.sku}" angelegt`,
|
||||
});
|
||||
|
||||
return load(googleAuthId, childSku, runner);
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
@@ -99,8 +99,20 @@ export default function getBySku(provider) {
|
||||
optionsCount: a.optionsCount,
|
||||
articleListCount: a.articleListCount,
|
||||
childCount: a.childCount,
|
||||
isFather: Boolean(a.isFather),
|
||||
fatherAttributeNameId: a.fatherAttributeNameId ?? null,
|
||||
fatherAttributeName: a.fatherAttributeName ?? null,
|
||||
fatherAttributeExclusive: a.fatherAttributeExclusive != null ? Boolean(a.fatherAttributeExclusive) : null,
|
||||
fatherArticleId: a.fatherArticleId,
|
||||
fatherSku: a.fatherSku,
|
||||
children: parseJsonArray(a.childrenJson).map((c) => ({
|
||||
id: c.id,
|
||||
sku: c.sku,
|
||||
fathersort: c.fathersort,
|
||||
name: c.name ?? null,
|
||||
attributeValueId: c.attributeValueId ?? null,
|
||||
attributeValue: c.attributeValue ?? null,
|
||||
})),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
64
src/store/article/listChildren.js
Normal file
64
src/store/article/listChildren.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as mssqlArticle from '../../providers/mssql/queries/article.js';
|
||||
import * as sqliteArticle from '../../providers/sqlite3/queries/article.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function listChildren(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
|
||||
/**
|
||||
* Lists all children of a father article.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<Array<{id: number, sku: string, fathersort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>}
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, languageId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
runner,
|
||||
);
|
||||
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT is_father, fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
);
|
||||
const father = fatherRes.rows[0];
|
||||
if (!father || !father.is_father) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await runner.query(queries.LIST_CHILDREN_BY_FATHER_ID, {
|
||||
fatherArticleId: fatherId,
|
||||
languageId,
|
||||
attributeId: father.fatherattributename_id,
|
||||
});
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
id: row.id,
|
||||
sku: row.sku,
|
||||
fathersort: Number(row.fathersort ?? 0),
|
||||
trackStock: Boolean(row.trackStock ?? 1),
|
||||
unitamount: Number(row.unitamount ?? 1),
|
||||
weight: Number(row.weight ?? 0),
|
||||
name: row.name ?? null,
|
||||
slug: row.slug ?? null,
|
||||
attributeValueId: row.attributeValueId ?? null,
|
||||
attributeValue: row.attributeValue ?? null,
|
||||
}));
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
105
src/store/article/removeChild.js
Normal file
105
src/store/article/removeChild.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import * as mssqlArticle from '../../providers/mssql/queries/article.js';
|
||||
import * as sqliteArticle from '../../providers/sqlite3/queries/article.js';
|
||||
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import articleDelete from './delete.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function removeChild(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const del = articleDelete(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||
|
||||
/**
|
||||
* Removes a child article from a father article, either by unlinking or deleting it.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} fatherSku - The father article SKU.
|
||||
* @param {string} childSku - The child article SKU.
|
||||
* @param {object} [options]
|
||||
* @param {boolean} [options.deleteArticle=false] - Whether to delete the child article entity entirely.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>}
|
||||
*/
|
||||
return async (googleAuthId, fatherSku, childSku, options = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId: fatherId, companyuserId } = await resolve(
|
||||
googleAuthId,
|
||||
fatherSku,
|
||||
runner,
|
||||
);
|
||||
const { articleId: childId } = await resolve(googleAuthId, childSku, runner);
|
||||
|
||||
// Verify child relationship
|
||||
const childRes = await runner.query(
|
||||
'SELECT fatherarticle_id FROM article WHERE id = @childId',
|
||||
{ childId },
|
||||
);
|
||||
const child = childRes.rows[0];
|
||||
if (!child || child.fatherarticle_id !== fatherId) {
|
||||
throw new Error('Child article does not belong to this father');
|
||||
}
|
||||
|
||||
if (options.deleteArticle) {
|
||||
await del(googleAuthId, childSku, runner);
|
||||
return {
|
||||
success: true,
|
||||
fatherSku,
|
||||
childSku,
|
||||
deleted: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch father's attribute name id
|
||||
const fatherRes = await runner.query(
|
||||
'SELECT fatherattributename_id FROM article WHERE id = @fatherId',
|
||||
{ fatherId },
|
||||
);
|
||||
const fatherAttributeNameId = fatherRes.rows[0]?.fatherattributename_id;
|
||||
|
||||
// Unlink child from father
|
||||
await runner.query(queries.UNLINK_CHILD, {
|
||||
articleId: childId,
|
||||
fatherArticleId: fatherId,
|
||||
});
|
||||
|
||||
// Remove the child's attribute value link for the father's attribute
|
||||
if (fatherAttributeNameId) {
|
||||
await runner.query(
|
||||
`DELETE FROM articleattributevalue
|
||||
WHERE article_id = @childId
|
||||
AND attributevalue_id IN (
|
||||
SELECT id FROM attributevalue WHERE attribute_id = @fatherAttributeNameId
|
||||
)`,
|
||||
{ childId, fatherAttributeNameId },
|
||||
);
|
||||
}
|
||||
|
||||
// Add history
|
||||
await runner.query(insertHistorySql, {
|
||||
articleId: childId,
|
||||
companyuserId,
|
||||
text: `Artikel "${childSku}" von Vaterartikel "${fatherSku}" getrennt`,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
fatherSku,
|
||||
childSku,
|
||||
deleted: false,
|
||||
};
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
93
src/store/article/toggleFather.js
Normal file
93
src/store/article/toggleFather.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import * as mssqlArticle from '../../providers/mssql/queries/article.js';
|
||||
import * as sqliteArticle from '../../providers/sqlite3/queries/article.js';
|
||||
import { GET_CHILD_COUNT as MSSQL_GET_CHILD_COUNT } from '../../providers/mssql/queries/articleDelete.js';
|
||||
import { GET_CHILD_COUNT as SQLITE_GET_CHILD_COUNT } from '../../providers/sqlite3/queries/articleDelete.js';
|
||||
import * as mssqlAttr from '../../providers/mssql/queries/attributename.js';
|
||||
import * as sqliteAttr from '../../providers/sqlite3/queries/attributename.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
import getBySku from './getBySku.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function toggleFather(provider) {
|
||||
const resolve = resolveArticleBySku(provider);
|
||||
const load = getBySku(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
|
||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||
const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT;
|
||||
const attrQueries = isSqlite ? sqliteAttr : mssqlAttr;
|
||||
|
||||
/**
|
||||
* Toggles an article into or out of father mode.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {string} sku - The article SKU.
|
||||
* @param {object} options
|
||||
* @param {boolean} options.isFather - Whether the article should be a father article.
|
||||
* @param {number} [options.attributeNameId] - The attribute name id required when enabling father mode.
|
||||
* @param {number} [options.fatherAttributeNameId] - Alias for attributeNameId.
|
||||
* @param {object} [tx] - Optional transaction.
|
||||
* @returns {Promise<object>} The updated article detail.
|
||||
*/
|
||||
return async (googleAuthId, sku, { isFather, attributeNameId, fatherAttributeNameId } = {}, tx) => {
|
||||
const run = async (runner) => {
|
||||
const { articleId, companyId } = await resolve(googleAuthId, sku, runner);
|
||||
|
||||
const targetIsFather = Boolean(isFather);
|
||||
const targetAttrId = attributeNameId ?? fatherAttributeNameId;
|
||||
|
||||
if (targetIsFather) {
|
||||
if (!targetAttrId || Number(targetAttrId) <= 0) {
|
||||
throw new Error('attributeNameId is required to become a father article');
|
||||
}
|
||||
|
||||
// Verify article is not a child of another article
|
||||
const currentRes = await runner.query(
|
||||
'SELECT fatherarticle_id FROM article WHERE id = @articleId',
|
||||
{ articleId },
|
||||
);
|
||||
if (currentRes.rows[0]?.fatherarticle_id != null) {
|
||||
throw new Error('A child article cannot become a father');
|
||||
}
|
||||
|
||||
// Verify attribute exists and belongs to company
|
||||
const attrRes = await runner.query(attrQueries.GET_BY_COMPANY_AND_ID, {
|
||||
id: Number(targetAttrId),
|
||||
companyId,
|
||||
});
|
||||
if (!attrRes.rows[0]) {
|
||||
throw new Error('Attribute name not found');
|
||||
}
|
||||
|
||||
await runner.query(queries.TOGGLE_FATHER, {
|
||||
articleId,
|
||||
isFather: 1,
|
||||
fatherAttributeNameId: Number(targetAttrId),
|
||||
});
|
||||
} else {
|
||||
// Disabling father: verify zero children
|
||||
const childCountRes = await runner.query(getChildCountSql, { articleId });
|
||||
const childCount = childCountRes.rows[0]?.count ?? 0;
|
||||
if (childCount > 0) {
|
||||
throw new Error('Cannot disable father mode on an article that still has children');
|
||||
}
|
||||
|
||||
await runner.query(queries.TOGGLE_FATHER, {
|
||||
articleId,
|
||||
isFather: 0,
|
||||
fatherAttributeNameId: null,
|
||||
});
|
||||
}
|
||||
|
||||
return load(googleAuthId, sku, runner);
|
||||
};
|
||||
|
||||
if (tx) {
|
||||
return run(makeRunner(provider, tx));
|
||||
}
|
||||
if (typeof provider?.transaction === 'function') {
|
||||
return provider.transaction(run);
|
||||
}
|
||||
return run(makeRunner(provider));
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user