From 4718cb57268673cea915a0cb64da80435d222069 Mon Sep 17 00:00:00 2001 From: seb Date: Sun, 4 Oct 2026 21:22:05 +0200 Subject: [PATCH] u --- API.article.md | 68 ++--- API.category.md | 2 +- API.md | 18 +- BENCHMARK_REPORT.md | 6 +- FEATURE_PLAN_EXPORT.md | 4 +- README.md | 8 +- generate-api-doc.js | 2 +- schema-graph.dot | 12 +- schema-graph.svg | 256 +++++++++--------- scripts/benchmark/workload.js | 18 +- scripts/test-history-and-attrvalue.js | 40 +-- scripts/test-tombstone.js | 26 +- scripts/test-unique-slugs.js | 38 +-- scripts/test-variant-flow.js | 82 +++--- src/index.js | 8 +- src/providers/mssql/queries/article.js | 80 +++--- src/providers/mssql/queries/articleDelete.js | 4 +- src/providers/mssql/queries/articleDetail.js | 10 +- src/providers/mssql/queries/category.js | 2 +- src/providers/mssql/queries/manifestDoc.js | 4 +- src/providers/mssql/queries/posterminal.js | 12 +- src/providers/mssql/schema.js | 26 +- src/providers/sqlite3/index.js | 4 +- src/providers/sqlite3/queries/article.js | 32 +-- .../sqlite3/queries/articleDetail.js | 2 +- src/providers/sqlite3/queries/category.js | 2 +- src/providers/sqlite3/queries/manifestDoc.js | 2 +- src/providers/sqlite3/queries/posterminal.js | 12 +- src/providers/sqlite3/schema.js | 18 +- src/store/article/addChild.js | 122 ++++----- src/store/article/attachChild.js | 144 +++++----- src/store/article/delete.js | 20 +- src/store/article/getBySku.js | 16 +- src/store/article/importRows.js | 60 ++-- src/store/article/listChildren.js | 30 +- src/store/article/queryBySku.js | 4 +- src/store/article/removeChild.js | 72 ++--- src/store/article/setParentBySku.js | 90 +++--- src/store/article/setPricesBySku.js | 6 +- src/store/article/toggleParent.js | 70 ++--- .../articleattributevalue/setByArticleSku.js | 40 +-- src/store/articlelist/setItemsByArticleSku.js | 6 +- src/store/articlesupplier/create.js | 6 +- src/store/category/getCategoryArticles.js | 2 +- src/store/category/queryCategoryArticles.js | 4 +- src/store/manifestLines.js | 14 +- .../optionslist/setOptionsByArticleSku.js | 6 +- src/store/stock/adjust.js | 6 +- 48 files changed, 758 insertions(+), 758 deletions(-) diff --git a/API.article.md b/API.article.md index 45e6a63..c360cb7 100644 --- a/API.article.md +++ b/API.article.md @@ -6,19 +6,19 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin *Source: `src/store/article/addChild.js`* -Adds a child article to a father article (creates and attaches with auto-generated SKU). +Adds a child article to a parent article (creates and attaches with auto-generated SKU). **Parameters** | Name | Type | Description | | --- | --- | --- | | `googleAuthId` | `number` | The id of the Google auth user. | -| `fatherSku` | `string` | The father article SKU. | +| `parentSku` | `string` | The parent article SKU. | | `options` | `object` | | | `[options.attach=false]` | `boolean` | If true, attaches an existing article instead of creating. | -| `[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.attributeValueId]` | `number` | Existing attribute value ID for parent's attribute. | +| `[options.value]` | `string` | String value to find or create under parent's attribute. | +| `[options.name]` | `string` | Custom name for child article (defaults to parent name + value). | | `[options.unitamount]` | `number` | Optional unitamount override. | | `[options.weight]` | `number` | Optional weight override. | | `[options.track_stock]` | `boolean|number` | Optional track_stock override. | @@ -58,20 +58,20 @@ const result = await store.article.addImage(42, 'SKU-1', imageBuffer); *Source: `src/store/article/attachChild.js`* -Attaches an existing article as a child to a father article. +Attaches an existing article as a child to a parent article. **Parameters** | Name | Type | Description | | --- | --- | --- | | `googleAuthId` | `number` | The id of the Google auth user. | -| `fatherSku` | `string` | The father article SKU. | +| `parentSku` | `string` | The parent article SKU. | | `options` | `object` | | | `options.sku` | `string` | The child article SKU to attach. | | `[options.childSku]` | `string` | Alias for options.sku. | -| `[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.reparent=false]` | `boolean` | Whether to allow re-parenting if child belongs to another father. | +| `[options.attributeValueId]` | `number` | Existing attribute value ID for parent's attribute. | +| `[options.value]` | `string` | String value to find or create under parent's attribute. | +| `[options.reparent=false]` | `boolean` | Whether to allow re-parenting if child belongs to another parent. | | `[options.unitamount]` | `number` | Optional unitamount override. | | `[options.weight]` | `number` | Optional weight override. | | `[options.track_stock]` | `boolean|number` | Optional track_stock override. | @@ -184,7 +184,7 @@ Returns the full detail of an article by its SKU, including unit, brand, categor **Returns** -`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}}>` — The full article detail. +`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, parentArticleId: number|null, parentSku: string|null}}>` — The full article detail. **Throws** @@ -231,7 +231,7 @@ const recent = await store.article.getHistoryBySku(42, 'SKU-1', { limit: 5 }); *Source: `src/store/article/importRows.js`* -Imports article rows, creating articles by their SKU and returning per-row import, skip and error outcomes. The import runs in **two passes** so a `fatherSku` that references a row appearing later in the file still resolves. A row is **skipped** when its SKU already exists in the company (upsert mode is not applied), and an EAN is skipped when already used by another row. +Imports article rows, creating articles by their SKU and returning per-row import, skip and error outcomes. The import runs in **two passes** so a `parentSku` that references a row appearing later in the file still resolves. A row is **skipped** when its SKU already exists in the company (upsert mode is not applied), and an EAN is skipped when already used by another row. **Parameters** @@ -242,7 +242,7 @@ Imports article rows, creating articles by their SKU and returning per-row impor | `input.rows` | `Array` | The article rows to import. | | `input.rows[].sku` | `string` | The article SKU. | | `input.rows[].name` | `string` | The article name. | -| `[input.rows[].vk]` | `string` | `'V'` (father/compound) or `'K'` (variant) marker. | +| `[input.rows[].vk]` | `string` | `'V'` (parent/compound) or `'K'` (variant) marker. | | `[input.rows[].parentId]` | `number|string` | The id of the parent category. | | `[input.rows[].parentCategory]` | `string` | The category breadcrumb path, e.g. `'Flowers | CBN'`. | | `[input.rows[].brand]` | `string` | The brand name to upsert. | @@ -250,7 +250,7 @@ Imports article rows, creating articles by their SKU and returning per-row impor | `[input.rows[].tax]` | `number|string` | The tax rate in percent (e.g. `19`). | | `[input.rows[].eans]` | `Array|string` | The EANs to set. | | `[input.rows[].track_stock=true]` | `boolean|number` | Whether inventory stock is tracked. | -| `[input.rows[].fatherSku]` | `string` | The father article SKU for a V/K compound link. | +| `[input.rows[].parentSku]` | `string` | The parent article SKU for a V/K compound link. | | `[input.rows[].deposit]` | `number|string` | The deposit (Pfand) value; currently ignored. | | `[tx]` | `object` | Optional transaction. | @@ -264,7 +264,7 @@ Imports article rows, creating articles by their SKU and returning per-row impor const result = await store.article.importRows({ googleAuthId: 42, rows: [ - { sku: '42001190-1', name: '420Strainz 1g', vk: 'K', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '11,00', tax: 19, eans: ['4262557690455'], fatherSku: '42001190' }, + { sku: '42001190-1', name: '420Strainz 1g', vk: 'K', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '11,00', tax: 19, eans: ['4262557690455'], parentSku: '42001190' }, { sku: '42001190', name: '420Strainz', vk: 'V', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '0,00', tax: 19 }, ], }); @@ -275,19 +275,19 @@ const result = await store.article.importRows({ *Source: `src/store/article/listChildren.js`* -Lists all children of a father article. +Lists all children of a parent article. **Parameters** | Name | Type | Description | | --- | --- | --- | | `googleAuthId` | `number` | The id of the Google auth user. | -| `fatherSku` | `string` | The father article SKU. | +| `parentSku` | `string` | The parent article SKU. | | `[tx]` | `object` | Optional transaction. | **Returns** -`Promise>` +`Promise>` ## `article.listExport` @@ -377,14 +377,14 @@ const result = await store.article.orderImages(42, 'SKU-1', { fullHashes: ['a', *Source: `src/store/article/removeChild.js`* -Removes a child article from a father article, either by unlinking or deleting it. +Removes a child article from a parent 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. | +| `parentSku` | `string` | The parent article SKU. | | `childSku` | `string` | The child article SKU. | | `[options]` | `object` | | | `[options.deleteArticle=false]` | `boolean` | Whether to delete the child article entity entirely. | @@ -393,7 +393,7 @@ Removes a child article from a father article, either by unlinking or deleting i **Returns** -`Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>` +`Promise<{success: boolean, parentSku: string, childSku: string, deleted: boolean}>` ## `article.resolveUniqueSlug` @@ -506,11 +506,11 @@ const article = await store.article.setEansBySku(42, 'SKU-1', ['4001234567890']) // => { id, sku, name, ... } ``` -## `article.setFatherBySku` +## `article.setParentBySku` -*Source: `src/store/article/setFatherBySku.js`* +*Source: `src/store/article/setParentBySku.js`* -Links an article to a father article (V/K compound) by their SKUs. +Links an article to a parent article (V/K compound) by their SKUs. **Parameters** @@ -518,7 +518,7 @@ Links an article to a father article (V/K compound) by their SKUs. | --- | --- | --- | | `googleAuthId` | `number` | The id of the Google auth user. | | `sku` | `string` | The child article SKU. | -| `fatherSku` | `string|null` | The father article SKU, or `null` to unlink. | +| `parentSku` | `string|null` | The parent article SKU, or `null` to unlink. | | `[tx]` | `object` | Optional transaction to run within. | **Returns** @@ -527,13 +527,13 @@ Links an article to a father article (V/K compound) by their SKUs. **Throws** -`Error` — If the article or father is not found, or the link is invalid. +`Error` — If the article or parent is not found, or the link is invalid. **Example** ```js -const article = await store.article.setFatherBySku(42, '42001190-1', '42001190'); -// => { id, sku, name, fatherSku: '42001190', ... } +const article = await store.article.setParentBySku(42, '42001190-1', '42001190'); +// => { id, sku, name, parentSku: '42001190', ... } ``` ## `article.setPricesBySku` @@ -626,11 +626,11 @@ const article = await store.article.setWeightBySku(42, 'SKU-1', 1.5); // => { id, sku, name, ... } ``` -## `article.toggleFather` +## `article.toggleParent` -*Source: `src/store/article/toggleFather.js`* +*Source: `src/store/article/toggleParent.js`* -Toggles an article into or out of father mode. +Toggles an article into or out of parent mode. **Parameters** @@ -639,9 +639,9 @@ Toggles an article into or out of father mode. | `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. | +| `options.isParent` | `boolean` | Whether the article should be a parent article. | +| `[options.attributeNameId]` | `number` | The attribute name id required when enabling parent mode. | +| `[options.parentAttributeNameId]` | `number` | Alias for attributeNameId. | | `[tx]` | `object` | Optional transaction. | **Returns** diff --git a/API.category.md b/API.category.md index b65a48e..9ff2c68 100644 --- a/API.category.md +++ b/API.category.md @@ -156,7 +156,7 @@ Returns the articles belonging to a single category. **Returns** -`Promise>` — The articles in the category. +`Promise>` — The articles in the category. **Example** diff --git a/API.md b/API.md index 95eb1c1..1c0d7db 100644 --- a/API.md +++ b/API.md @@ -8,7 +8,7 @@ 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, fatherattributename_id?->attributename, 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_parent:b=0, parentarticle_id?->article, parentattributename_id?->attributename, parentsort:i=0, created_at:dt, updated_at:dt, UQ(parentarticle_id, parentsort)` - **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` @@ -80,35 +80,35 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - `delete(input: {googleAuthId: number, id: number}, tx?: object): Promise` - `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` - - `getCategoryArticles(googleAuthId: number, categoryId: number, tx?: object): Promise>` + - `getCategoryArticles(googleAuthId: number, categoryId: number, tx?: object): Promise>` - `getTreeForCompany(googleAuthId: number, tx?: object): Promise<{languageId: number, tree: Array, articles: Array}>` - `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}, tx?: object): Promise<{categoryIds: Array}>` - `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: {attach?: boolean, attributeValueId?: number, value?: string, name?: string, unitamount?: number, weight?: number, track_stock?: boolean|number}, tx?: object): Promise` + - `addChild(googleAuthId: number, parentSku: string, options: {attach?: boolean, attributeValueId?: number, value?: string, name?: string, unitamount?: number, weight?: number, track_stock?: boolean|number}, tx?: object): Promise` - `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}>` - - `attachChild(googleAuthId: number, fatherSku: string, options: {sku: string, childSku?: string, attributeValueId?: number, value?: string, reparent?: boolean, unitamount?: number, weight?: number, track_stock?: boolean|number}, tx?: object): Promise<{article: object, children: Array}>` + - `attachChild(googleAuthId: number, parentSku: string, options: {sku: string, childSku?: string, attributeValueId?: number, value?: string, reparent?: boolean, unitamount?: number, weight?: number, track_stock?: boolean|number}, tx?: object): Promise<{article: object, children: Array}>` - `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, optionsOrTx?: object|*, maybeTx?: object): Promise` - `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise` - - `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}}>` + - `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, parentArticleId: number|null, parentSku: string|null}}>` - `getHistoryBySku(googleAuthId: number, sku: string, opts?: {limit?: number}, tx?: object): Promise>` - `importRows(input: {googleAuthId: number, rows: Array}, tx?: object): Promise<{imported: Array, skipped: Array, errors: Array}>` - - `listChildren(googleAuthId: number, fatherSku: string, tx?: object): Promise>` + - `listChildren(googleAuthId: number, parentSku: string, tx?: object): Promise>` - `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise>` - `listSearchIndex(googleAuthId: number, tx?: object): Promise, categoryIds: Array}>>` - `orderImages(googleAuthId: number, sku: string, input: {fullHashes: Array}, tx?: object): Promise<{fullHashes: Array}>` - - `removeChild(googleAuthId: number, fatherSku: string, childSku: string, options?: {deleteArticle?: boolean, cascade?: boolean}, tx?: object): Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>` + - `removeChild(googleAuthId: number, parentSku: string, childSku: string, options?: {deleteArticle?: boolean, cascade?: boolean}, tx?: object): Promise<{success: boolean, parentSku: string, childSku: string, deleted: boolean}>` - `resolveUniqueSlug(runner: object, params: {companyId: number, languageId: number, name: string, excludeArticleId?: number}): Promise` - `setBrandIdBySku(googleAuthId: number, sku: string, brandId: number|null, tx?: object): Promise` - `setCategoryIdsBySku(googleAuthId: number, sku: string, categoryIds: Array, tx?: object): Promise` - `setEansBySku(googleAuthId: number, sku: string, eans: Array, tx?: object): Promise` - - `setFatherBySku(googleAuthId: number, sku: string, fatherSku: string|null, tx?: object): Promise` + - `setParentBySku(googleAuthId: number, sku: string, parentSku: string|null, tx?: object): Promise` - `setPricesBySku(googleAuthId: number, sku: string, prices: Array<{price: number, tax: number, minmultiplier?: number}>, tx?: object): Promise` - `setTrackStockBySku(googleAuthId: number, sku: string, trackStock: boolean|number, tx?: object): Promise` - `setWeightBySku(googleAuthId: number, sku: string, weight: number, tx?: object): Promise` - - `toggleFather(googleAuthId: number, sku: string, options: {isFather: boolean, attributeNameId?: number, fatherAttributeNameId?: number}, tx?: object): Promise` + - `toggleParent(googleAuthId: number, sku: string, options: {isParent: boolean, attributeNameId?: number, parentAttributeNameId?: number}, tx?: object): Promise` - `updateBySku(googleAuthId: number, sku: string, patch: {name?: string, unitSi?: string, unitamount?: number, track_stock?: boolean|number}, tx?: object): Promise` - [lang](API.lang.md) - `create(iso639: string, tx?: object): Promise` diff --git a/BENCHMARK_REPORT.md b/BENCHMARK_REPORT.md index 00241b3..02b52cb 100644 --- a/BENCHMARK_REPORT.md +++ b/BENCHMARK_REPORT.md @@ -28,7 +28,7 @@ | `article.setBrandIdBySku` | 18 | 15.50 | 24.98 | 0.57 | 0.81 | 27.21x | | `article.setCategoryIdsBySku` | 18 | 15.08 | 33.45 | 0.74 | 1.01 | 20.24x | | `article.setEansBySku` | 18 | 11.86 | 19.23 | 0.56 | 0.85 | 21.15x | -| `article.setFatherBySku` | 13 | 14.79 | 25.32 | 0.66 | 0.92 | 22.52x | +| `article.setParentBySku` | 13 | 14.79 | 25.32 | 0.66 | 0.92 | 22.52x | | `article.setPricesBySku` | 18 | 14.16 | 29.51 | 0.69 | 1.12 | 20.48x | | `article.setWeightBySku` | 18 | 14.18 | 36.45 | 0.52 | 0.68 | 27.02x | | `article.updateBySku` | 18 | 18.71 | 84.26 | 0.83 | 1.65 | 22.52x | @@ -153,7 +153,7 @@ | `article.setBrandIdBySku` | 18 | 15.50 | 9.85 | 24.98 | 14.23 | 24.98 | 0 | | `article.setCategoryIdsBySku` | 18 | 15.08 | 9.51 | 33.45 | 13.12 | 33.45 | 0 | | `article.setEansBySku` | 18 | 11.86 | 8.23 | 19.23 | 11.67 | 19.23 | 0 | -| `article.setFatherBySku` | 13 | 14.79 | 8.86 | 25.32 | 14.06 | 25.32 | 0 | +| `article.setParentBySku` | 13 | 14.79 | 8.86 | 25.32 | 14.06 | 25.32 | 0 | | `article.setPricesBySku` | 18 | 14.16 | 8.82 | 29.51 | 12.62 | 29.51 | 0 | | `article.setWeightBySku` | 18 | 14.18 | 9.15 | 36.45 | 12.90 | 36.45 | 0 | | `article.updateBySku` | 18 | 18.71 | 8.88 | 84.26 | 13.70 | 84.26 | 0 | @@ -278,7 +278,7 @@ | `article.setBrandIdBySku` | 18 | 0.57 | 0.53 | 0.81 | 0.55 | 0.81 | 0 | | `article.setCategoryIdsBySku` | 18 | 0.74 | 0.69 | 1.01 | 0.73 | 1.01 | 0 | | `article.setEansBySku` | 18 | 0.56 | 0.51 | 0.85 | 0.54 | 0.85 | 0 | -| `article.setFatherBySku` | 13 | 0.66 | 0.60 | 0.92 | 0.63 | 0.92 | 0 | +| `article.setParentBySku` | 13 | 0.66 | 0.60 | 0.92 | 0.63 | 0.92 | 0 | | `article.setPricesBySku` | 18 | 0.69 | 0.63 | 1.12 | 0.65 | 1.12 | 0 | | `article.setWeightBySku` | 18 | 0.52 | 0.49 | 0.68 | 0.51 | 0.68 | 0 | | `article.updateBySku` | 18 | 0.83 | 0.73 | 1.65 | 0.78 | 1.65 | 0 | diff --git a/FEATURE_PLAN_EXPORT.md b/FEATURE_PLAN_EXPORT.md index a51fafa..85d91f8 100644 --- a/FEATURE_PLAN_EXPORT.md +++ b/FEATURE_PLAN_EXPORT.md @@ -25,7 +25,7 @@ Nicht-Ziele (Phase 1): inkrementelle Backups, Replikation, Live-Sync, Verschlüs | Zeitstempel: MSSQL `DATETIME2`, SQLite `TEXT` ISO-8601 (`%Y-%m-%dT%H:%M:%fZ`, ms-Präzision). | Schemas | Kanonisches Format: ISO-8601 UTC, `Z`, **genau 3 Nachkommastellen**. | | Die **Ledger-Hashkette** (`prev_hash`) ist SHA-512 über `JSON.stringify({id, manifestarticle_id, warehouse_id, transaction_type, amount, transaction_text, hash_version, prev_hash, created_at})`. Sie hängt von `id` und `created_at` (ms-genau) ab. | `src/store/ledger/add.js` | IDs und `created_at` müssen 1:1 erhalten bleiben. Import in aufsteigender `id`-Reihenfolge, Kette wird nach dem Import verifiziert. | | MSSQL-Ledger ist `APPEND_ONLY`; `purge` hinterlässt `MSSQL_DroppedLedgerTable_*`. | `mssql/schema.js`, README | Import in MSSQL nur in eine **frisch initialisierte** Ledger-Tabelle. Kein Merge in vorhandene Ledger-Daten. | -| Zirkuläre FKs: `company.poscustomergroup_id → customergroup` und `customergroup.company_id → company`. Selbstreferenzen: `category.parent_id`, `article.fatherarticle_id`. Außerdem `company.root_category_id → category`. | Schemas | Die Importreihenfolge ist nicht rein topologisch; die FK-Prüfung muss zeitweise aufgeschoben werden (dialektspezifisch). | +| Zirkuläre FKs: `company.poscustomergroup_id → customergroup` und `customergroup.company_id → company`. Selbstreferenzen: `category.parent_id`, `article.parentarticle_id`. Außerdem `company.root_category_id → category`. | Schemas | Die Importreihenfolge ist nicht rein topologisch; die FK-Prüfung muss zeitweise aufgeschoben werden (dialektspezifisch). | | SQLite hat `entity.company_id` (denormalisiert, per Trigger gepflegt). MSSQL löst das über die View `v_entity_company_number`. | `sqlite3/schema.js`, `mssql/schema.js` | `entity.company_id` ist eine **abgeleitete Spalte**: nicht exportieren, das Ziel setzt sie selbst. | | SQLite-`initialize` seedet `tax` (0, 7, 19) und `meta.version`. | `sqlite3/schema.js` | Import-Modus „replace“ muss Seed-Zeilen entfernen oder überschreiben, sonst PK-/UQ-Konflikte. | | Collation: SQLite-Spalten teils `COLLATE NOCASE`, MSSQL-Default ist case-insensitive. Nicht jede SQLite-UQ ist `NOCASE` (z. B. `attributename(company_id, name)`). | Schemas | SQLite → MSSQL kann an einer UQ scheitern (`Foo` vs. `foo`). Pre-Flight-Check (§8). | @@ -113,7 +113,7 @@ Abgeleitet aus den Schemata (Eltern vor Kindern). Die Registry speichert sie exp 8. `manifest`, `manifestarticle`, `file`, `manifestattachment`, `warehouse`, `ledger` 9. `numbers`, `companyuser`, `articlehistory`, `entityhistory`, `syncpos`, `companysetting`, `posterminal`, `posterminalcategory`, `printer`, `labeltemplate` -Selbstreferenzen (`category.parent_id`, `article.fatherarticle_id`): Der Export sortiert nach `id`. Beim Import wird die FK-Prüfung zurückgestellt (§6.3), ein Zwei-Pass-Update ist nicht nötig. +Selbstreferenzen (`category.parent_id`, `article.parentarticle_id`): Der Export sortiert nach `id`. Beim Import wird die FK-Prüfung zurückgestellt (§6.3), ein Zwei-Pass-Update ist nicht nötig. ## 5. Öffentliche API (`store.transfer`) diff --git a/README.md b/README.md index 67cd79e..3b56bff 100644 --- a/README.md +++ b/README.md @@ -501,7 +501,7 @@ Company-scoped POS terminals. Auth via selected company (`google_auth.selected_c | `order` | `({ googleAuthId, parentId, categoryIds })` | `{ categoryIds }` | | `delete` | `({ googleAuthId, id })` | `void` — `Category has children` if child categories remain; FK `categoryarticle` also blocks deletion | | `getTreeForCompany` | `(googleAuthId)` | `{ languageId, tree, articles }` — tree rows carry `name` + `slug` | -| `getCategoryArticles` | `(googleAuthId, categoryId)` | `{ id, sku, name, slug, net, taxRate, optionsCount, articleListCount, childCount, fatherArticleId, fatherSku, fullHash, thumbW, thumbH, thumbHash }[]` | +| `getCategoryArticles` | `(googleAuthId, categoryId)` | `{ id, sku, name, slug, net, taxRate, optionsCount, articleListCount, childCount, parentArticleId, parentSku, fullHash, thumbW, thumbH, thumbHash }[]` | | `addImage` | `(googleAuthId, categoryId, imageBuffer)` | `{ id, thumbW, thumbH, thumbHash }` | | `deleteImage` | `(googleAuthId, categoryId)` | `{ id, thumbW: null, thumbH: null, thumbHash: null }` | @@ -643,8 +643,8 @@ type ArticleDetail = { optionsCount: number, // 0 when no optionslist articleListCount: number, // 0 when no articlelist childCount: number, // 0 when no children - fatherArticleId: number | null, // null when root article - fatherSku: string | null, // null when root article + parentArticleId: number | null, // null when root article + parentSku: string | null, // null when root article } type PriceEntry = { @@ -681,7 +681,7 @@ Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Err `orderImages` sets `articlepic.sort` to `0..n-1` in the given order. `fullHashes` must list every image of the article exactly once. Errors: `Article not found`, `Invalid image order`, `Not authorized`. -`delete` removes the article when it has no child articles (`fatherarticle_id` FK is `ON DELETE NO ACTION`). Cascades: `articlehistory`, `articleean`, `categoryarticle`, `price`, `articlesupplier`, `articletext`, `articlelistarticle`, `optionslistarticle`, `articlepic`; deletes linked `pic` rows and owned `articlelist` / `optionslist`. Errors: `Article not found`, `Article has children`, `Not authorized`. +`delete` removes the article when it has no child articles (`parentarticle_id` FK is `ON DELETE NO ACTION`). Cascades: `articlehistory`, `articleean`, `categoryarticle`, `price`, `articlesupplier`, `articletext`, `articlelistarticle`, `optionslistarticle`, `articlepic`; deletes linked `pic` rows and owned `articlelist` / `optionslist`. Errors: `Article not found`, `Article has children`, `Not authorized`. --- diff --git a/generate-api-doc.js b/generate-api-doc.js index 93ce7bd..9367493 100644 --- a/generate-api-doc.js +++ b/generate-api-doc.js @@ -319,7 +319,7 @@ 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, fatherattributename_id?->attributename, 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_parent:b=0, parentarticle_id?->article, parentattributename_id?->attributename, parentsort:i=0, created_at:dt, updated_at:dt, UQ(parentarticle_id, parentsort)'], ['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'], diff --git a/schema-graph.dot b/schema-graph.dot index 6926180..74cb587 100644 --- a/schema-graph.dot +++ b/schema-graph.dot @@ -42,7 +42,7 @@ digraph schema { category [label=<
category
idINT PK
parent_id?INT FK
sortINT
updated_atDATETIME2
>]; articlehistory [label=<
articlehistory
idINT PK
article_idINT FK
companyuser_id?INT FK
dateDATETIME2
textNVARCHAR(MAX)
>]; articleean [label=<
articleean
idINT PK
article_idINT FK
eanNVARCHAR(50)
>]; - article [label=<
article
idINT PK
fatherattributename_id?INT FK
brand_id?INT FK
fatherarticle_id?INT FK
articlelist_id?INT FK
optionslist_id?INT FK
unit_idINT FK
company_idINT FK
sku?NVARCHAR(100)
unitamountFLOAT
weightFLOAT
track_stockBIT
is_fatherBIT
fathersortINT
created_atDATETIME2
updated_atDATETIME2
>]; + article [label=<
article
idINT PK
parentattributename_id?INT FK
brand_id?INT FK
parentarticle_id?INT FK
articlelist_id?INT FK
optionslist_id?INT FK
unit_idINT FK
company_idINT FK
sku?NVARCHAR(100)
unitamountFLOAT
weightFLOAT
track_stockBIT
is_parentBIT
parentsortINT
created_atDATETIME2
updated_atDATETIME2
>]; articlesupplier [label=<
articlesupplier
idINT PK
article_idINT FK
price_id?INT FK
entity_idINT FK
sku?NVARCHAR(100)
stock?FLOAT
puFLOAT
sortINT
>]; articlelist [label=<
articlelist
idINT PK
>]; articlelistarticle [label=<
articlelistarticle
idINT PK
article_idINT FK
articlelist_idINT FK
countINT
sortINT
>]; @@ -137,9 +137,9 @@ digraph schema { article:brand_id:e -> brand:in0:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="FK_article_brand", constraint=false, weight=1]; article:articlelist_id:e -> articlelist:in0:n [color="#56d68a", fontcolor="#56d68a", tooltip="FK_article_articlelist", constraint=false, weight=1]; article:optionslist_id:e -> optionslist:in0:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="FK_article_optionslist", constraint=false, weight=1]; - article:fatherarticle_id:e -> article:id:e [color="#c79bff", fontcolor="#c79bff", tooltip="FK_article_fatherarticle", constraint=false]; - article:fatherattributename_id:sw -> attributename:in2:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_article_fatherattributename", constraint=false, weight=1]; - article:fatherattributename_id:sw -> attributename:in2:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_article_fatherattributename", constraint=false, weight=1]; + article:parentarticle_id:e -> article:id:e [color="#c79bff", fontcolor="#c79bff", tooltip="FK_article_parentarticle", constraint=false]; + article:parentattributename_id:sw -> attributename:in2:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_article_parentattributename", constraint=false, weight=1]; + article:parentattributename_id:sw -> attributename:in2:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_article_parentattributename", constraint=false, weight=1]; articlehistory:article_id:se -> article:in3:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_articlehistory_article", weight=3]; articlehistory:companyuser_id:w -> companyuser:in3:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_articlehistory_companyuser", constraint=false, weight=1]; companyhistory:company_id:sw -> company:in10:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_companyhistory_company", weight=3]; @@ -151,7 +151,7 @@ digraph schema { brandpic:brand_id:w -> brand:in1:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_brandpic_brand", weight=3]; brandpic:pic_id:e -> pic:in0:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_brandpic_pic", constraint=false, weight=1]; articlelistarticle:articlelist_id:sw -> articlelist:in1:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="FK_articlelistarticle_articlelist", weight=3]; - articlelistarticle:article_id:sw -> article:in8:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_articlelistarticle_article", constraint=false, weight=1]; + articlelistarticle:article_id:sw -> article:in7:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_articlelistarticle_article", constraint=false, weight=1]; optionslistarticle:optionslist_id:se -> optionslist:in1:n [color="#ff6f6f", fontcolor="#ff6f6f", tooltip="FK_optionslistarticle_optionslist", weight=3]; optionslistarticle:article_id:e -> article:in11:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="FK_optionslistarticle_article", constraint=false, weight=1]; categoryarticle:category_id:ne -> category:in5:n [color="#5aa7e6", fontcolor="#5aa7e6", tooltip="FK_categoryarticle_category", weight=3]; @@ -163,7 +163,7 @@ digraph schema { articletext:article_id:e -> article:in9:n [color="#56d68a", fontcolor="#56d68a", tooltip="FK_articletext_article", weight=3]; articletext:language_id:w -> language:in1:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="FK_articletext_language", constraint=false, weight=1]; price:tax_id:w -> tax:in0:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_price_tax", constraint=false, weight=1]; - price:article_id:e -> article:in7:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_price_article", weight=3]; + price:article_id:e -> article:in8:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_price_article", weight=3]; price:customergroup_id:e -> customergroup:in0:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_price_customergroup", constraint=false, weight=1]; articlesupplier:article_id:sw -> article:in10:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_articlesupplier_article", weight=3]; articlesupplier:entity_id:e -> entity:in2:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_articlesupplier_entity", constraint=false, weight=1]; diff --git a/schema-graph.svg b/schema-graph.svg index e7713c1..f57eed2 100644 --- a/schema-graph.svg +++ b/schema-graph.svg @@ -879,105 +879,105 @@ article - - - - - - - - - - - - - - -article - - -id -INT -PK - - -fatherattributename_id -? -INT -FK - - -brand_id -? -INT -FK - - -fatherarticle_id -? -INT -FK - - -articlelist_id -? -INT -FK - - -optionslist_id -? -INT -FK - - -unit_id -INT -FK - - -company_id -INT -FK - - -sku? -NVARCHAR(100) - - -unitamount -FLOAT - - -weight -FLOAT - - -track_stock -BIT - - -is_father -BIT - - -fathersort -INT - - -created_at -DATETIME2 - - -updated_at -DATETIME2 + + + + + + + + + + + + + + +article + + +id +INT +PK + + +parentattributename_id +? +INT +FK + + +brand_id +? +INT +FK + + +parentarticle_id +? +INT +FK + + +articlelist_id +? +INT +FK + + +optionslist_id +? +INT +FK + + +unit_id +INT +FK + + +company_id +INT +FK + + +sku? +NVARCHAR(100) + + +unitamount +FLOAT + + +weight +FLOAT + + +track_stock +BIT + + +is_parent +BIT + + +parentsort +INT + + +created_at +DATETIME2 + + +updated_at +DATETIME2 articletext:e->article:n - - + + @@ -1181,8 +1181,8 @@ categoryarticle:w->article:n - - + + @@ -1239,8 +1239,8 @@ articlehistory:se->article:n - - + + @@ -1269,8 +1269,8 @@ articleean:se->article:n - - + + @@ -1278,7 +1278,7 @@ article:e->company:n - + @@ -1287,7 +1287,7 @@ article:e->brand:n - + @@ -1295,9 +1295,9 @@ article:e->article:e - - - + + + @@ -1319,7 +1319,7 @@ article:e->articlelist:n - + @@ -1342,7 +1342,7 @@ article:e->optionslist:n - + @@ -1351,7 +1351,7 @@ article:w->unit:n - + @@ -1391,8 +1391,8 @@ article:sw->attributename:n - - + + @@ -1400,8 +1400,8 @@ article:sw->attributename:n - - + + @@ -1463,8 +1463,8 @@ articlesupplier:sw->article:n - - + + @@ -1547,8 +1547,8 @@ articlelistarticle:sw->article:n - - + + @@ -1599,8 +1599,8 @@ optionslistarticle:e->article:n - - + + @@ -1786,8 +1786,8 @@ articlepic:se->article:n - - + + @@ -2136,8 +2136,8 @@ price:e->article:n - - + + @@ -2340,8 +2340,8 @@ posterminalcategory:w->category:n - - + + @@ -2700,8 +2700,8 @@ articleproperty:w->article:n - - + + @@ -2744,8 +2744,8 @@ articleattributevalue:w->article:n - - + + diff --git a/scripts/benchmark/workload.js b/scripts/benchmark/workload.js index 6f5355a..cd768ee 100644 --- a/scripts/benchmark/workload.js +++ b/scripts/benchmark/workload.js @@ -52,7 +52,7 @@ export async function fillDatabase(store, options = {}) { secondaryCompany: null, categories: [], articles: [], - fatherArticles: [], + parentArticles: [], childArticles: [], customergroups: [], entities: [], @@ -326,7 +326,7 @@ export async function fillDatabase(store, options = {}) { const numArticles = counts.articles; for (let i = 1; i <= numArticles; i++) { const targetCat = ctx.categories[i % ctx.categories.length]; - const isFather = i % 4 === 1; + const isParent = i % 4 === 1; const art = await store.article.createUnderParent({ googleAuthId: ctx.googleAuthId, parentId: targetCat.id, @@ -365,12 +365,12 @@ export async function fillDatabase(store, options = {}) { ctx.categories[(i + 1) % ctx.categories.length].id, ]); - // V/K Father-Child hierarchy - if (isFather) { - ctx.fatherArticles.push(art); - } else if (ctx.fatherArticles.length > 0) { - const parentFather = ctx.fatherArticles[ctx.fatherArticles.length - 1]; - await store.article.setFatherBySku(ctx.googleAuthId, sku, parentFather.sku); + // V/K Parent-Child hierarchy + if (isParent) { + ctx.parentArticles.push(art); + } else if (ctx.parentArticles.length > 0) { + const parentParent = ctx.parentArticles[ctx.parentArticles.length - 1]; + await store.article.setParentBySku(ctx.googleAuthId, sku, parentParent.sku); ctx.childArticles.push(art); } @@ -426,7 +426,7 @@ export async function fillDatabase(store, options = {}) { brand: 'BenchBrand Pro', net: '45,50', tax: 19, - fatherSku: `IMP-${Date.now()}-1`, + parentSku: `IMP-${Date.now()}-1`, }, ]; await store.article.importRows({ diff --git a/scripts/test-history-and-attrvalue.js b/scripts/test-history-and-attrvalue.js index 04bfe7e..fe760f8 100644 --- a/scripts/test-history-and-attrvalue.js +++ b/scripts/test-history-and-attrvalue.js @@ -6,7 +6,7 @@ import LibDashStore from '../src/index.js'; import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; async function run() { - console.log('--- Testing Father History & AttributeValue Shapes ---'); + console.log('--- Testing Parent History & AttributeValue Shapes ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-hist-attr-')); const dbFile = join(tempDir, 'test.db'); @@ -31,11 +31,11 @@ async function run() { }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); - console.log('1. Testing setFatherBySku history logging...'); - const father = await store.article.createUnderParent({ + console.log('1. Testing setParentBySku history logging...'); + const parent = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, - name: 'Father Shirt', + name: 'Parent Shirt', }); const child = await store.article.createUnderParent({ googleAuthId, @@ -44,19 +44,19 @@ async function run() { }); // Check history before linking - let fatherHist = await store.article.getHistoryBySku(googleAuthId, father.sku); - assert.equal(fatherHist.length, 1); - assert.match(fatherHist[0].text, /angelegt/); + let parentHist = await store.article.getHistoryBySku(googleAuthId, parent.sku); + assert.equal(parentHist.length, 1); + assert.match(parentHist[0].text, /angelegt/); - // Link child to father - await store.article.setFatherBySku(googleAuthId, child.sku, father.sku); + // Link child to parent + await store.article.setParentBySku(googleAuthId, child.sku, parent.sku); - // Father history must now contain the conversion entry and the child entry! - fatherHist = await store.article.getHistoryBySku(googleAuthId, father.sku); - const hasConversion = fatherHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert')); - const hasVariantAdded = fatherHist.some((h) => h.text.includes('hinzugefügt')); - assert.ok(hasConversion, `Father history missing conversion row: ${JSON.stringify(fatherHist)}`); - assert.ok(hasVariantAdded, `Father history missing variant added row: ${JSON.stringify(fatherHist)}`); + // Parent history must now contain the conversion entry and the child entry! + parentHist = await store.article.getHistoryBySku(googleAuthId, parent.sku); + const hasConversion = parentHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert')); + const hasVariantAdded = parentHist.some((h) => h.text.includes('hinzugefügt')); + assert.ok(hasConversion, `Parent history missing conversion row: ${JSON.stringify(parentHist)}`); + assert.ok(hasVariantAdded, `Parent history missing variant added row: ${JSON.stringify(parentHist)}`); // Child history must contain the assignment entry const childHist = await store.article.getHistoryBySku(googleAuthId, child.sku); @@ -67,15 +67,15 @@ async function run() { const importRes = await store.article.importRows({ googleAuthId, rows: [ - { sku: 'IMP-FATHER', name: 'Imported Father', parentId: company.rootCategoryId }, - { sku: 'IMP-CHILD', name: 'Imported Child', parentId: company.rootCategoryId, fatherSku: 'IMP-FATHER' }, + { sku: 'IMP-PARENT', name: 'Imported Parent', parentId: company.rootCategoryId }, + { sku: 'IMP-CHILD', name: 'Imported Child', parentId: company.rootCategoryId, parentSku: 'IMP-PARENT' }, ], }); assert.equal(importRes.errors.length, 0); - const impFatherHist = await store.article.getHistoryBySku(googleAuthId, 'IMP-FATHER'); - const impHasConversion = impFatherHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert')); - assert.ok(impHasConversion, `Imported father missing conversion row: ${JSON.stringify(impFatherHist)}`); + const impParentHist = await store.article.getHistoryBySku(googleAuthId, 'IMP-PARENT'); + const impHasConversion = impParentHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert')); + assert.ok(impHasConversion, `Imported parent missing conversion row: ${JSON.stringify(impParentHist)}`); console.log('3. Testing flexible attributevalue request shapes in setByArticleSku...'); const attrName = await store.attributename.create(googleAuthId, { name: 'Farbe' }); diff --git a/scripts/test-tombstone.js b/scripts/test-tombstone.js index 8b0b6b5..232592c 100644 --- a/scripts/test-tombstone.js +++ b/scripts/test-tombstone.js @@ -119,31 +119,31 @@ async function run() { assert.equal(tomb.taxrate, 19); assert.ok(tomb.deleted_at); - // 5. Test cascading deletion with father and variants - console.log('5. Testing father with variant child deletion...'); - const father = await store.article.createUnderParent({ + // 5. Test cascading deletion with parent and variants + console.log('5. Testing parent with variant child deletion...'); + const parent = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Pants', }); const attrName = await store.attributename.create(googleAuthId, { name: 'Größe' }); - await store.article.toggleFather(googleAuthId, father.sku, { - isFather: true, + await store.article.toggleParent(googleAuthId, parent.sku, { + isParent: true, attributeNameId: attrName.id, }); - const child = await store.article.addChild(googleAuthId, father.sku, { value: 'M' }); + const child = await store.article.addChild(googleAuthId, parent.sku, { value: 'M' }); await store.article.setPricesBySku(googleAuthId, child.article.sku, [ { price: 49.99, tax: 19, minmultiplier: 1 }, ]); // Delete child via removeChild with deleteArticle console.log('Deleting child via removeChild({ deleteArticle: true })...'); - await store.article.removeChild(googleAuthId, father.sku, child.article.sku, { deleteArticle: true }); + await store.article.removeChild(googleAuthId, parent.sku, child.article.sku, { deleteArticle: true }); - // Add another child and delete father with cascade: true - const child2 = await store.article.addChild(googleAuthId, father.sku, { value: 'L' }); - console.log('Deleting father with cascade: true...'); - await store.article.delete(googleAuthId, father.sku, { cascade: true }); + // Add another child and delete parent with cascade: true + const child2 = await store.article.addChild(googleAuthId, parent.sku, { value: 'L' }); + console.log('Deleting parent with cascade: true...'); + await store.article.delete(googleAuthId, parent.sku, { cascade: true }); const allTombstones = provider.db.prepare('SELECT * FROM articletombstone ORDER BY id ASC').all(); assert.equal(allTombstones.length, 4); @@ -164,9 +164,9 @@ async function run() { assert.equal(allTombstones[2].name, 'Pants - L'); assert.equal(allTombstones[2].sku, child2.article.sku); - // 4th tombstone: Pants (father deleted in cascade) + // 4th tombstone: Pants (parent deleted in cascade) assert.equal(allTombstones[3].name, 'Pants'); - assert.equal(allTombstones[3].sku, father.sku); + assert.equal(allTombstones[3].sku, parent.sku); // 6. Test posterminal maxknowntombstoneid column and updates console.log('6. Testing posterminal maxknowntombstoneid column...'); diff --git a/scripts/test-unique-slugs.js b/scripts/test-unique-slugs.js index 1fc65f9..6629366 100644 --- a/scripts/test-unique-slugs.js +++ b/scripts/test-unique-slugs.js @@ -35,65 +35,65 @@ async function run() { const art1 = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, - name: 'Root Father Shirt', + name: 'Root Parent Shirt', }); const art1Res = await store.article.getBySku(googleAuthId, art1.sku); - assert.equal(art1Res.article.slug, 'root-father-shirt', `Expected slug 'root-father-shirt', got '${art1Res.article.slug}'`); + assert.equal(art1Res.article.slug, 'root-parent-shirt', `Expected slug 'root-parent-shirt', got '${art1Res.article.slug}'`); const art2 = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, - name: 'Root Father Shirt', + name: 'Root Parent Shirt', }); const art2Res = await store.article.getBySku(googleAuthId, art2.sku); - assert.equal(art2Res.article.slug, 'root-father-shirt-2', `Expected slug 'root-father-shirt-2', got '${art2Res.article.slug}'`); + assert.equal(art2Res.article.slug, 'root-parent-shirt-2', `Expected slug 'root-parent-shirt-2', got '${art2Res.article.slug}'`); const art3 = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, - name: 'Root Father Shirt', + name: 'Root Parent Shirt', }); const art3Res = await store.article.getBySku(googleAuthId, art3.sku); - assert.equal(art3Res.article.slug, 'root-father-shirt-3', `Expected slug 'root-father-shirt-3', got '${art3Res.article.slug}'`); + assert.equal(art3Res.article.slug, 'root-parent-shirt-3', `Expected slug 'root-parent-shirt-3', got '${art3Res.article.slug}'`); console.log('2. Testing updateBySku self-collision and renaming...'); // Update art2 without changing name await store.article.updateBySku(googleAuthId, art2.sku, { unitamount: 2 }); let updated2 = await store.article.getBySku(googleAuthId, art2.sku); - assert.equal(updated2.article.slug, 'root-father-shirt-2', `Expected unchanged slug, got '${updated2.article.slug}'`); + assert.equal(updated2.article.slug, 'root-parent-shirt-2', `Expected unchanged slug, got '${updated2.article.slug}'`); // Update art2 with same name - await store.article.updateBySku(googleAuthId, art2.sku, { name: 'Root Father Shirt' }); + await store.article.updateBySku(googleAuthId, art2.sku, { name: 'Root Parent Shirt' }); updated2 = await store.article.getBySku(googleAuthId, art2.sku); - assert.equal(updated2.article.slug, 'root-father-shirt-2', `Expected slug to remain 'root-father-shirt-2', got '${updated2.article.slug}'`); + assert.equal(updated2.article.slug, 'root-parent-shirt-2', `Expected slug to remain 'root-parent-shirt-2', got '${updated2.article.slug}'`); // Rename art2 to something else await store.article.updateBySku(googleAuthId, art2.sku, { name: 'Unique Shirt' }); updated2 = await store.article.getBySku(googleAuthId, art2.sku); assert.equal(updated2.article.slug, 'unique-shirt', `Expected slug 'unique-shirt', got '${updated2.article.slug}'`); - // Rename art3 to 'Root Father Shirt' again: should take root-father-shirt-2 now that it is free - await store.article.updateBySku(googleAuthId, art3.sku, { name: 'Root Father Shirt' }); + // Rename art3 to 'Root Parent Shirt' again: should take root-parent-shirt-2 now that it is free + await store.article.updateBySku(googleAuthId, art3.sku, { name: 'Root Parent Shirt' }); const updated3 = await store.article.getBySku(googleAuthId, art3.sku); - assert.equal(updated3.article.slug, 'root-father-shirt-2', `Expected slug to fill gap 'root-father-shirt-2', got '${updated3.article.slug}'`); + assert.equal(updated3.article.slug, 'root-parent-shirt-2', `Expected slug to fill gap 'root-parent-shirt-2', got '${updated3.article.slug}'`); console.log('3. Testing addChild duplicate slugs...'); const attrName = await store.attributename.create(googleAuthId, { name: 'Größe' }); - await store.article.toggleFather(googleAuthId, art1.sku, { - isFather: true, + await store.article.toggleParent(googleAuthId, art1.sku, { + isParent: true, attributeNameId: attrName.id, }); const child1 = await store.article.addChild(googleAuthId, art1.sku, { value: 'M', }); - assert.equal(child1.article.slug, 'root-father-shirt-m', `Expected child slug 'root-father-shirt-m', got '${child1.article.slug}'`); + assert.equal(child1.article.slug, 'root-parent-shirt-m', `Expected child slug 'root-parent-shirt-m', got '${child1.article.slug}'`); // Add another child with name that yields same slug const child2 = await store.article.addChild(googleAuthId, art1.sku, { value: 'L', - name: 'Root Father Shirt - M', + name: 'Root Parent Shirt - M', }); - assert.equal(child2.article.slug, 'root-father-shirt-m-2', `Expected child slug 'root-father-shirt-m-2', got '${child2.article.slug}'`); + assert.equal(child2.article.slug, 'root-parent-shirt-m-2', `Expected child slug 'root-parent-shirt-m-2', got '${child2.article.slug}'`); console.log('4. Testing importRows duplicate slugs in same batch...'); const importRes = await store.article.importRows({ @@ -128,10 +128,10 @@ async function run() { const artCompany2 = await store.article.createUnderParent({ googleAuthId, parentId: company2.rootCategoryId, - name: 'Root Father Shirt', + name: 'Root Parent Shirt', }); const artCompany2Detail = await store.article.getBySku(googleAuthId, artCompany2.sku); - assert.equal(artCompany2Detail.article.slug, 'root-father-shirt', `Expected 'root-father-shirt' in company 2, got '${artCompany2Detail.article.slug}'`); + assert.equal(artCompany2Detail.article.slug, 'root-parent-shirt', `Expected 'root-parent-shirt' in company 2, got '${artCompany2Detail.article.slug}'`); console.log('✅ ALL SLUG TESTS PASSED'); } finally { diff --git a/scripts/test-variant-flow.js b/scripts/test-variant-flow.js index c69774e..cdb053e 100644 --- a/scripts/test-variant-flow.js +++ b/scripts/test-variant-flow.js @@ -6,7 +6,7 @@ import LibDashStore from '../src/index.js'; import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; async function run() { - console.log('--- Testing Variant Flow: Next-Free SKU, Fathersort Reclaim, and Duplicate Protection ---'); + console.log('--- Testing Variant Flow: Next-Free SKU, Parentsort Reclaim, and Duplicate Protection ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-variants-')); const dbFile = join(tempDir, 'test.db'); @@ -31,35 +31,35 @@ async function run() { }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); - // Create father article - const father = await store.article.createUnderParent({ + // Create parent article + const parent = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'T-Shirt', }); const attrName = await store.attributename.create(googleAuthId, { name: 'Farbe' }); - await store.article.toggleFather(googleAuthId, father.sku, { - isFather: true, + await store.article.toggleParent(googleAuthId, parent.sku, { + isParent: true, attributeNameId: attrName.id, }); console.log('1. Add initial children (Rot, Blau, Grün)...'); - const child1 = await store.article.addChild(googleAuthId, father.sku, { value: 'Rot' }); - const child2 = await store.article.addChild(googleAuthId, father.sku, { value: 'Blau' }); - const child3 = await store.article.addChild(googleAuthId, father.sku, { value: 'Grün' }); + const child1 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Rot' }); + const child2 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Blau' }); + const child3 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Grün' }); - assert.equal(child1.article.sku, `${father.sku}-1`); - assert.equal(child2.article.sku, `${father.sku}-2`); - assert.equal(child3.article.sku, `${father.sku}-3`); + assert.equal(child1.article.sku, `${parent.sku}-1`); + assert.equal(child2.article.sku, `${parent.sku}-2`); + assert.equal(child3.article.sku, `${parent.sku}-3`); - let list = await store.article.listChildren(googleAuthId, father.sku); + let list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 3); - assert.deepEqual(list.map((c) => c.fathersort), [0, 1, 2]); + assert.deepEqual(list.map((c) => c.parentsort), [0, 1, 2]); console.log('2. Testing Duplicate Variant Protection (Defect 3)...'); await assert.rejects( async () => { - await store.article.addChild(googleAuthId, father.sku, { value: 'Rot' }); + await store.article.addChild(googleAuthId, parent.sku, { value: 'Rot' }); }, (err) => { assert.match(err.message, /Variant with this attribute value already exists/); @@ -69,57 +69,57 @@ async function run() { ); console.log('3. Unlink middle child (child2 / SKU-2) -> leaves orphan holding SKU-2 (Bug A & B)...'); - await store.article.removeChild(googleAuthId, father.sku, child2.article.sku, { + await store.article.removeChild(googleAuthId, parent.sku, child2.article.sku, { deleteArticle: false, }); - // Check that remaining children's fathersort was recompacted - list = await store.article.listChildren(googleAuthId, father.sku); + // Check that remaining children's parentsort was recompacted + list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 2); assert.deepEqual(list.map((c) => c.sku), [child1.article.sku, child3.article.sku]); - assert.deepEqual(list.map((c) => c.fathersort), [0, 1]); + assert.deepEqual(list.map((c) => c.parentsort), [0, 1]); // Check orphan still exists in DB const orphan = await store.article.getBySku(googleAuthId, child2.article.sku); - assert.equal(orphan.article.fatherArticleId, null); - assert.equal(orphan.article.sku, `${father.sku}-2`); + assert.equal(orphan.article.parentArticleId, null); + assert.equal(orphan.article.sku, `${parent.sku}-2`); console.log('4. Add new child (Gelb) -> SKU must NOT collide with orphan (Bug A: next free index)...'); - const child4 = await store.article.addChild(googleAuthId, father.sku, { value: 'Gelb' }); + const child4 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Gelb' }); // Since SKU-1, SKU-2 (orphan), SKU-3 exist, next free index is 4 - assert.equal(child4.article.sku, `${father.sku}-4`); + assert.equal(child4.article.sku, `${parent.sku}-4`); - list = await store.article.listChildren(googleAuthId, father.sku); + list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 3); - assert.deepEqual(list.map((c) => c.fathersort), [0, 1, 2]); + assert.deepEqual(list.map((c) => c.parentsort), [0, 1, 2]); - console.log('5. Testing gaps in fathersort (Evidence: father with child at fathersort: 1)...'); + console.log('5. Testing gaps in parentsort (Evidence: parent with child at parentsort: 1)...'); // Unlink child3 and child4 so only child1 remains - await store.article.removeChild(googleAuthId, father.sku, child3.article.sku, { deleteArticle: true }); - await store.article.removeChild(googleAuthId, father.sku, child4.article.sku, { deleteArticle: true }); + await store.article.removeChild(googleAuthId, parent.sku, child3.article.sku, { deleteArticle: true }); + await store.article.removeChild(googleAuthId, parent.sku, child4.article.sku, { deleteArticle: true }); - // Manually force child1's fathersort: 1 to simulate pre-existing gap state (like father 94) + // Manually force child1's parentsort: 1 to simulate pre-existing gap state (like parent 94) await provider.query( - `UPDATE article SET fathersort = 1 WHERE sku = @sku`, + `UPDATE article SET parentsort = 1 WHERE sku = @sku`, { sku: child1.article.sku } ); - list = await store.article.listChildren(googleAuthId, father.sku); + list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 1); - assert.equal(list[0].fathersort, 1); // exactly like father 94 + assert.equal(list[0].parentsort, 1); // exactly like parent 94 // Now adding a variant MUST NOT hit UNIQUE constraint failed (409) - console.log('Adding variant to father with child at fathersort: 1...'); - const child5 = await store.article.addChild(googleAuthId, father.sku, { value: 'Schwarz' }); + console.log('Adding variant to parent with child at parentsort: 1...'); + const child5 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Schwarz' }); assert.ok(child5); - list = await store.article.listChildren(googleAuthId, father.sku); + list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 2); - // Fathersort should have picked 0 (next free non-negative integer) - assert.deepEqual(list.map((c) => c.fathersort), [0, 1]); + // Parentsort should have picked 0 (next free non-negative integer) + assert.deepEqual(list.map((c) => c.parentsort), [0, 1]); console.log('6. Testing rejecting sku when creating via addChild...'); await assert.rejects( async () => { - await store.article.addChild(googleAuthId, father.sku, { + await store.article.addChild(googleAuthId, parent.sku, { sku: 'SOME-CUSTOM-SKU', value: 'Lila', }); @@ -132,15 +132,15 @@ async function run() { ); console.log('7. Testing attaching existing orphan article (SKU-2) via attachChild...'); - const reconnected = await store.article.attachChild(googleAuthId, father.sku, { + const reconnected = await store.article.attachChild(googleAuthId, parent.sku, { sku: child2.article.sku, value: 'Blau', }); assert.equal(reconnected.article.sku, child2.article.sku); - assert.equal(reconnected.article.fatherArticleId, father.id); + assert.equal(reconnected.article.parentArticleId, parent.id); - console.log('8. Testing idempotent attachChild on same father...'); - const idempotentResult = await store.article.attachChild(googleAuthId, father.sku, { + console.log('8. Testing idempotent attachChild on same parent...'); + const idempotentResult = await store.article.attachChild(googleAuthId, parent.sku, { sku: child2.article.sku, value: 'Blau', }); diff --git a/src/index.js b/src/index.js index 601146e..a006355 100644 --- a/src/index.js +++ b/src/index.js @@ -32,8 +32,8 @@ import articleSetBrandIdBySku from './store/article/setBrandIdBySku.js'; 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 articleSetParentBySku from './store/article/setParentBySku.js'; +import articleToggleParent from './store/article/toggleParent.js'; import articleAddChild from './store/article/addChild.js'; import articleAttachChild from './store/article/attachChild.js'; import articleRemoveChild from './store/article/removeChild.js'; @@ -267,8 +267,8 @@ export default class LibDashStore { setWeightBySku: articleSetWeightBySku(provider), setTrackStockBySku: articleSetTrackStockBySku(provider), setCategoryIdsBySku: articleSetCategoryIdsBySku(provider), - setFatherBySku: articleSetFatherBySku(provider), - toggleFather: articleToggleFather(provider), + setParentBySku: articleSetParentBySku(provider), + toggleParent: articleToggleParent(provider), addChild: articleAddChild(provider), attachChild: articleAttachChild(provider), removeChild: articleRemoveChild(provider), diff --git a/src/providers/mssql/queries/article.js b/src/providers/mssql/queries/article.js index 963fe68..b50ef18 100644 --- a/src/providers/mssql/queries/article.js +++ b/src/providers/mssql/queries/article.js @@ -21,7 +21,7 @@ OUTER APPLY ( 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 parent ON parent.id = a.parentarticle_id `; export const ARTICLE_LIST_COLUMNS = ` @@ -32,7 +32,7 @@ export const ARTICLE_LIST_COLUMNS = ` pr.net, t.rate AS taxRate, a.track_stock, - a.is_father AS isFather, + a.is_parent AS isParent, CASE WHEN a.optionslist_id IS NOT NULL THEN ( SELECT COUNT(*) @@ -52,10 +52,10 @@ export const ARTICLE_LIST_COLUMNS = ` ( SELECT COUNT(*) FROM article child - WHERE child.fatherarticle_id = a.id + WHERE child.parentarticle_id = a.id ) AS childCount, - a.fatherarticle_id AS fatherArticleId, - father.sku AS fatherSku, + a.parentarticle_id AS parentArticleId, + parent.sku AS parentSku, pic.hash AS fullHash, pic.thumbw AS thumbW, pic.thumbh AS thumbH, @@ -75,7 +75,7 @@ INNER JOIN categoryarticle ca ON ca.article_id = a.id WHERE a.sku = @sku AND (@companyId IS NULL OR a.company_id = @companyId) `; -/** Resolve an article id by sku (used to wire father/child compound links). */ +/** Resolve an article id by sku (used to wire parent/child compound links). */ export const GET_ID_BY_SKU = ` SELECT TOP 1 id AS articleId FROM article @@ -89,49 +89,49 @@ FROM articleean WHERE ean = @ean `; -/** Link an article to its father article and mark the father. */ -export const UPDATE_FATHER_BY_SKU = ` +/** Link an article to its parent article and mark the parent. */ +export const UPDATE_PARENT_BY_SKU = ` UPDATE article -SET fatherarticle_id = @fatherArticleId, - fathersort = COALESCE(@fathersort, 0) +SET parentarticle_id = @parentArticleId, + parentsort = COALESCE(@parentsort, 0) WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId) `; -export const SET_IS_FATHER = ` +export const SET_IS_PARENT = ` UPDATE article -SET is_father = 1 +SET is_parent = 1 WHERE id = @articleId `; -export const TOGGLE_FATHER = ` +export const TOGGLE_PARENT = ` UPDATE article -SET is_father = @isFather, - fatherattributename_id = @fatherAttributeNameId +SET is_parent = @isParent, + parentattributename_id = @parentAttributeNameId 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 + company_id, sku, unit_id, unitamount, weight, track_stock, brand_id, is_parent, parentarticle_id, parentsort ) OUTPUT INSERTED.id VALUES ( - @companyId, @sku, @unitId, @unitamount, @weight, @trackStock, @brandId, 0, @fatherArticleId, @fathersort + @companyId, @sku, @unitId, @unitamount, @weight, @trackStock, @brandId, 0, @parentArticleId, @parentsort ) `; export const UNLINK_CHILD = ` UPDATE article -SET fatherarticle_id = NULL, - fathersort = 0 -WHERE id = @articleId AND fatherarticle_id = @fatherArticleId +SET parentarticle_id = NULL, + parentsort = 0 +WHERE id = @articleId AND parentarticle_id = @parentArticleId `; -export const LIST_CHILDREN_BY_FATHER_ID = ` +export const LIST_CHILDREN_BY_PARENT_ID = ` SELECT child.id, child.sku, - child.fathersort, + child.parentsort, child.track_stock AS trackStock, child.unitamount, child.weight, @@ -145,14 +145,14 @@ LEFT JOIN ( articleattributevalue aav INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = @attributeId ) ON aav.article_id = child.id -WHERE child.fatherarticle_id = @fatherArticleId -ORDER BY child.fathersort ASC, child.id ASC +WHERE child.parentarticle_id = @parentArticleId +ORDER BY child.parentsort ASC, child.id ASC `; -export const SET_FATHER_SORT = ` +export const SET_PARENT_SORT = ` UPDATE article -SET fathersort = @fathersort -WHERE id = @articleId AND fatherarticle_id = @fatherArticleId +SET parentsort = @parentsort +WHERE id = @articleId AND parentarticle_id = @parentArticleId `; /** SKU → article/category + company auth in one round-trip. */ @@ -484,19 +484,19 @@ SELECT ( SELECT COUNT(*) FROM article child - WHERE child.fatherarticle_id = a.id + WHERE child.parentarticle_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, + a.is_parent AS isParent, + a.parentattributename_id AS parentAttributeNameId, + fan.name AS parentAttributeName, + fan.exclusive AS parentAttributeExclusive, + a.parentarticle_id AS parentArticleId, + fa.sku AS parentSku, ( SELECT child.id, child.sku, - child.fathersort, + child.parentsort, at_child.name, av.id AS attributeValueId, av.value AS attributeValue @@ -504,10 +504,10 @@ SELECT LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = auth.languageId LEFT JOIN ( articleattributevalue aav - INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.fatherattributename_id + INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.parentattributename_id ) ON aav.article_id = child.id - WHERE child.fatherarticle_id = a.id - ORDER BY child.fathersort ASC, child.id ASC + WHERE child.parentarticle_id = a.id + ORDER BY child.parentsort ASC, child.id ASC FOR JSON PATH ) AS childrenJson, ( @@ -637,8 +637,8 @@ SELECT ORDER BY bp2.sort ASC, bp2.id ASC ) 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 + LEFT JOIN article fa ON fa.id = a.parentarticle_id + LEFT JOIN attributename fan ON fan.id = a.parentattributename_id WHERE a.id = auth.articleId FOR JSON PATH, WITHOUT_ARRAY_WRAPPER ) AS articleJson diff --git a/src/providers/mssql/queries/articleDelete.js b/src/providers/mssql/queries/articleDelete.js index 6c32ee4..51078a8 100644 --- a/src/providers/mssql/queries/articleDelete.js +++ b/src/providers/mssql/queries/articleDelete.js @@ -1,13 +1,13 @@ export const GET_CHILD_COUNT = ` SELECT COUNT(*) AS count FROM article -WHERE fatherarticle_id = @articleId +WHERE parentarticle_id = @articleId `; export const GET_CHILDREN_IDS = ` SELECT id FROM article -WHERE fatherarticle_id = @id +WHERE parentarticle_id = @id `; export const GET_LIST_IDS = ` diff --git a/src/providers/mssql/queries/articleDetail.js b/src/providers/mssql/queries/articleDetail.js index a97c800..f5c9f38 100644 --- a/src/providers/mssql/queries/articleDetail.js +++ b/src/providers/mssql/queries/articleDetail.js @@ -36,10 +36,10 @@ export const ARTICLE_DETAIL_COLUMNS = ` ( SELECT COUNT(*) FROM article child - WHERE child.fatherarticle_id = a.id + WHERE child.parentarticle_id = a.id ) AS childCount, - a.fatherarticle_id AS fatherArticleId, - father.sku AS fatherSku + a.parentarticle_id AS parentArticleId, + parent.sku AS parentSku `; export const GET_DETAIL_BY_SKU = ` @@ -59,7 +59,7 @@ OUTER APPLY ( 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 parent ON parent.id = a.parentarticle_id WHERE a.sku = @sku AND (@companyId IS NULL OR a.company_id = @companyId) `; @@ -80,7 +80,7 @@ OUTER APPLY ( 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 parent ON parent.id = a.parentarticle_id WHERE a.id = @articleId `; diff --git a/src/providers/mssql/queries/category.js b/src/providers/mssql/queries/category.js index 70405ce..01a0881 100644 --- a/src/providers/mssql/queries/category.js +++ b/src/providers/mssql/queries/category.js @@ -45,7 +45,7 @@ FROM categoryarticle ca INNER JOIN article a ON a.id = ca.article_id ${ARTICLE_LIST_JOINS} WHERE ca.category_id = @categoryId - AND a.fatherarticle_id IS NULL + AND a.parentarticle_id IS NULL ORDER BY a.id `; diff --git a/src/providers/mssql/queries/manifestDoc.js b/src/providers/mssql/queries/manifestDoc.js index d102268..e67810c 100644 --- a/src/providers/mssql/queries/manifestDoc.js +++ b/src/providers/mssql/queries/manifestDoc.js @@ -109,10 +109,10 @@ VALUES ( ) `; -export const CHECK_ARTICLE_IS_FATHER = ` +export const CHECK_ARTICLE_IS_PARENT = ` SELECT a.id, - a.is_father AS isFather, + a.is_parent AS isParent, COALESCE( (SELECT at.name FROM articletext at WHERE at.article_id = a.id AND at.language_id = co.default_language_id), (SELECT MIN(at2.name) FROM articletext at2 WHERE at2.article_id = a.id), diff --git a/src/providers/mssql/queries/posterminal.js b/src/providers/mssql/queries/posterminal.js index 8f8430e..94a76cb 100644 --- a/src/providers/mssql/queries/posterminal.js +++ b/src/providers/mssql/queries/posterminal.js @@ -253,7 +253,7 @@ SELECT TOP (@limit) SELECT TOP 1 p2.hash FROM articlepic ap2 INNER JOIN pic p2 ON p2.id = ap2.pic_id - WHERE a.fatherarticle_id IS NOT NULL AND ap2.article_id = a.fatherarticle_id + WHERE a.parentarticle_id IS NOT NULL AND ap2.article_id = a.parentarticle_id ORDER BY ap2.sort ASC, ap2.id ASC ) ) AS imgHash, @@ -263,15 +263,15 @@ SELECT TOP (@limit) WHERE ae.article_id = a.id ORDER BY ae.id ASC ) AS barcode, - a.is_father AS isParent, - a.fatherarticle_id AS parentArticleId, + a.is_parent AS isParent, + a.parentarticle_id AS parentArticleId, ( - SELECT TOP 1 CONCAT(fan.name, '|', av.value, '|', fan.sort, '|', a.fathersort) + SELECT TOP 1 CONCAT(fan.name, '|', av.value, '|', fan.sort, '|', a.parentsort) FROM article fa - INNER JOIN attributename fan ON fan.id = fa.fatherattributename_id + INNER JOIN attributename fan ON fan.id = fa.parentattributename_id INNER JOIN articleattributevalue aav ON aav.article_id = a.id INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id - WHERE a.fatherarticle_id IS NOT NULL AND fa.id = a.fatherarticle_id + WHERE a.parentarticle_id IS NOT NULL AND fa.id = a.parentarticle_id ) AS variantName, CASE WHEN a.articlelist_id IS NOT NULL AND EXISTS ( diff --git a/src/providers/mssql/schema.js b/src/providers/mssql/schema.js index e2aa9be..66385ed 100644 --- a/src/providers/mssql/schema.js +++ b/src/providers/mssql/schema.js @@ -323,10 +323,10 @@ BEGIN brand_id INT NULL, articlelist_id INT NULL, 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, + is_parent BIT NOT NULL CONSTRAINT DF_article_is_parent DEFAULT 0, + parentarticle_id INT NULL, + parentattributename_id INT NULL, + parentsort INT NOT NULL CONSTRAINT DF_article_parentsort 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(), CONSTRAINT FK_article_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION, @@ -334,23 +334,23 @@ BEGIN CONSTRAINT FK_article_brand FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE SET NULL, 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 FK_article_parentarticle FOREIGN KEY (parentarticle_id) REFERENCES article(id) ON DELETE NO ACTION, + CONSTRAINT FK_article_parentattributename FOREIGN KEY (parentattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION, CONSTRAINT UQ_article_company_sku UNIQUE (company_id, sku) ); - -- Only enforce sibling sort uniqueness among children of the same father. + -- Only enforce sibling sort uniqueness among children of the same parent. -- (SQL Server UNIQUE treats NULLs as equal, which would block multiple root articles.) - CREATE UNIQUE INDEX UQ_article_fatherarticle_fathersort - ON article (fatherarticle_id, fathersort) - WHERE fatherarticle_id IS NOT NULL; + CREATE UNIQUE INDEX UQ_article_parentarticle_parentsort + ON article (parentarticle_id, parentsort) + WHERE parentarticle_id IS NOT NULL; END ELSE BEGIN - IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('article') AND name = 'fatherattributename_id') + IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('article') AND name = 'parentattributename_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; + ALTER TABLE article ADD parentattributename_id INT NULL; + ALTER TABLE article ADD CONSTRAINT FK_article_parentattributename FOREIGN KEY (parentattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION; END IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('article') AND name = 'track_stock') diff --git a/src/providers/sqlite3/index.js b/src/providers/sqlite3/index.js index 22b9770..994293b 100644 --- a/src/providers/sqlite3/index.js +++ b/src/providers/sqlite3/index.js @@ -434,9 +434,9 @@ 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')) { + if (articleCols.length > 0 && !articleCols.includes('parentattributename_id')) { this.db.exec( - `ALTER TABLE article ADD COLUMN fatherattributename_id INTEGER REFERENCES attributename(id) ON DELETE NO ACTION`, + `ALTER TABLE article ADD COLUMN parentattributename_id INTEGER REFERENCES attributename(id) ON DELETE NO ACTION`, ); } diff --git a/src/providers/sqlite3/queries/article.js b/src/providers/sqlite3/queries/article.js index 27e301a..6f0ecfc 100644 --- a/src/providers/sqlite3/queries/article.js +++ b/src/providers/sqlite3/queries/article.js @@ -7,9 +7,9 @@ export const { 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, TOGGLE_FATHER, UNLINK_CHILD, UPDATE_BRAND, UPDATE_FATHER_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT, + LIST_CHILDREN_BY_PARENT_ID, + SET_PARENT_SORT, + SET_IS_PARENT, TOGGLE_PARENT, UNLINK_CHILD, UPDATE_BRAND, UPDATE_PARENT_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT, UPDATE_UNITAMOUNT, UPDATE_UNIT_AND_AMOUNT, UPDATE_WEIGHT, } = queries; @@ -30,7 +30,7 @@ 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 parent ON parent.id = a.parentarticle_id `; export const RESOLVE_ARTICLE_ACCESS_BY_SKU = ` @@ -160,16 +160,16 @@ SELECT auth.*, LEFT JOIN articletext at_item ON at_item.article_id = a_item.id AND at_item.language_id = auth.languageId WHERE ala.articlelist_id = a.articlelist_id ORDER BY ala.sort ASC, ala.id ASC)), - '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, + 'childCount', (SELECT COUNT(*) FROM article WHERE parentarticle_id = a.id), + 'isParent', a.is_parent, + 'parentAttributeNameId', a.parentattributename_id, + 'parentAttributeName', fan.name, + 'parentAttributeExclusive', fan.exclusive, + 'parentArticleId', a.parentarticle_id, 'parentSku', parent.sku, 'childrenJson', json((SELECT COALESCE(json_group_array(json_object( 'id', child.id, 'sku', child.sku, - 'fathersort', child.fathersort, + 'parentsort', child.parentsort, 'name', at_child.name, 'attributeValueId', av.id, 'attributeValue', av.value @@ -177,10 +177,10 @@ SELECT auth.*, LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = auth.languageId LEFT JOIN ( articleattributevalue aav - INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.fatherattributename_id + INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.parentattributename_id ) ON aav.article_id = child.id - WHERE child.fatherarticle_id = a.id - ORDER BY child.fathersort ASC, child.id ASC)), + WHERE child.parentarticle_id = a.id + ORDER BY child.parentsort 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, @@ -270,6 +270,6 @@ LEFT JOIN brand b ON b.id = a.brand_id 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 +LEFT JOIN article parent ON parent.id = a.parentarticle_id +LEFT JOIN attributename fan ON fan.id = a.parentattributename_id `; \ No newline at end of file diff --git a/src/providers/sqlite3/queries/articleDetail.js b/src/providers/sqlite3/queries/articleDetail.js index 4f01b7c..c2ce28c 100644 --- a/src/providers/sqlite3/queries/articleDetail.js +++ b/src/providers/sqlite3/queries/articleDetail.js @@ -19,7 +19,7 @@ LEFT JOIN pic brand_pic ON brand_pic.id = ( 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 parent ON parent.id = a.parentarticle_id `; export const GET_DETAIL_BY_SKU = ` diff --git a/src/providers/sqlite3/queries/category.js b/src/providers/sqlite3/queries/category.js index 8bcc6c6..ce09f2c 100644 --- a/src/providers/sqlite3/queries/category.js +++ b/src/providers/sqlite3/queries/category.js @@ -65,7 +65,7 @@ FROM categoryarticle ca INNER JOIN article a ON a.id = ca.article_id ${ARTICLE_LIST_JOINS} WHERE ca.category_id = @categoryId - AND a.fatherarticle_id IS NULL + AND a.parentarticle_id IS NULL ORDER BY a.id `; diff --git a/src/providers/sqlite3/queries/manifestDoc.js b/src/providers/sqlite3/queries/manifestDoc.js index cbe1913..a727a3e 100644 --- a/src/providers/sqlite3/queries/manifestDoc.js +++ b/src/providers/sqlite3/queries/manifestDoc.js @@ -3,7 +3,7 @@ import { translateQueries } from './translate.js'; const queries = translateQueries(source, 'manifestDoc'); export const { - CHECK_ARTICLE_IS_FATHER, CREATE_LINE, CREATE_MANIFEST, DELETE_LINES_BY_MANIFEST, + CHECK_ARTICLE_IS_PARENT, CREATE_LINE, CREATE_MANIFEST, DELETE_LINES_BY_MANIFEST, DELETE_MANIFEST, GET_MANIFEST_HEADER, GET_MANIFEST_LINES, LIST_MANIFESTS, ORDER_TYPE, QUOTE_TYPE, UPDATE_MANIFEST, } = queries; \ No newline at end of file diff --git a/src/providers/sqlite3/queries/posterminal.js b/src/providers/sqlite3/queries/posterminal.js index b00fb8e..9d6ae66 100644 --- a/src/providers/sqlite3/queries/posterminal.js +++ b/src/providers/sqlite3/queries/posterminal.js @@ -78,7 +78,7 @@ SELECT SELECT p2.hash FROM articlepic ap2 INNER JOIN pic p2 ON p2.id = ap2.pic_id - WHERE a.fatherarticle_id IS NOT NULL AND ap2.article_id = a.fatherarticle_id + WHERE a.parentarticle_id IS NOT NULL AND ap2.article_id = a.parentarticle_id ORDER BY ap2.sort ASC, ap2.id ASC LIMIT 1 ) @@ -90,15 +90,15 @@ SELECT ORDER BY ae.id ASC LIMIT 1 ) AS barcode, - a.is_father AS isParent, - a.fatherarticle_id AS parentArticleId, + a.is_parent AS isParent, + a.parentarticle_id AS parentArticleId, ( - SELECT fan.name || '|' || av.value || '|' || fan.sort || '|' || a.fathersort + SELECT fan.name || '|' || av.value || '|' || fan.sort || '|' || a.parentsort FROM article fa - INNER JOIN attributename fan ON fan.id = fa.fatherattributename_id + INNER JOIN attributename fan ON fan.id = fa.parentattributename_id INNER JOIN articleattributevalue aav ON aav.article_id = a.id INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id - WHERE a.fatherarticle_id IS NOT NULL AND fa.id = a.fatherarticle_id + WHERE a.parentarticle_id IS NOT NULL AND fa.id = a.parentarticle_id LIMIT 1 ) AS variantName, CASE diff --git a/src/providers/sqlite3/schema.js b/src/providers/sqlite3/schema.js index 00bba19..8f40670 100644 --- a/src/providers/sqlite3/schema.js +++ b/src/providers/sqlite3/schema.js @@ -222,10 +222,10 @@ CREATE TABLE IF NOT EXISTS article ( brand_id INTEGER, articlelist_id INTEGER, optionslist_id INTEGER, - is_father INTEGER NOT NULL DEFAULT 0, - fatherarticle_id INTEGER, - fathersort INTEGER NOT NULL DEFAULT 0, - fatherattributename_id INTEGER, + is_parent INTEGER NOT NULL DEFAULT 0, + parentarticle_id INTEGER, + parentsort INTEGER NOT NULL DEFAULT 0, + parentattributename_id INTEGER, created_at TEXT NOT NULL DEFAULT (${UTC_NOW}), updated_at TEXT NOT NULL DEFAULT (${UTC_NOW}), UNIQUE (company_id, sku), @@ -234,12 +234,12 @@ 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 (fatherattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION + FOREIGN KEY (parentarticle_id) REFERENCES article(id) ON DELETE NO ACTION, + FOREIGN KEY (parentattributename_id) REFERENCES attributename(id) ON DELETE NO ACTION ); -CREATE UNIQUE INDEX IF NOT EXISTS UQ_article_fatherarticle_fathersort - ON article(fatherarticle_id, fathersort) - WHERE fatherarticle_id IS NOT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS UQ_article_parentarticle_parentsort + ON article(parentarticle_id, parentsort) + WHERE parentarticle_id IS NOT NULL; `; export const INIT_ARTICLEEAN = ` diff --git a/src/store/article/addChild.js b/src/store/article/addChild.js index 8d592c6..2f72d6f 100644 --- a/src/store/article/addChild.js +++ b/src/store/article/addChild.js @@ -34,24 +34,24 @@ export default function addChild(provider) { const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** - * Adds a child article to a father article (creates and attaches with auto-generated SKU). + * Adds a child article to a parent article (creates and attaches with auto-generated SKU). * * @param {number} googleAuthId - The id of the Google auth user. - * @param {string} fatherSku - The father article SKU. + * @param {string} parentSku - The parent article SKU. * @param {object} options * @param {boolean} [options.attach=false] - If true, attaches an existing article instead of creating. - * @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.attributeValueId] - Existing attribute value ID for parent's attribute. + * @param {string} [options.value] - String value to find or create under parent's attribute. + * @param {string} [options.name] - Custom name for child article (defaults to parent 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} The created child article detail. */ - return async (googleAuthId, fatherSku, options = {}, tx) => { + return async (googleAuthId, parentSku, options = {}, tx) => { if (options.attach) { - return attach(googleAuthId, fatherSku, options, tx); + return attach(googleAuthId, parentSku, options, tx); } const run = async (runner) => { @@ -59,31 +59,31 @@ export default function addChild(provider) { throw new Error('sku is not allowed when creating a child article; omit sku to auto-generate or use PUT to attach an existing article'); } - const { articleId: fatherId, companyId, languageId, companyuserId } = await resolve( + const { articleId: parentId, companyId, languageId, companyuserId } = await resolve( googleAuthId, - fatherSku, + parentSku, 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 }, + // Fetch parent article fields + const parentRes = await runner.query( + 'SELECT id, sku, company_id, unit_id, unitamount, weight, track_stock, brand_id, is_parent, parentarticle_id, parentattributename_id FROM article WHERE id = @parentId', + { parentId }, ); - const father = fatherRes.rows[0]; - if (!father) { - throw new Error('Father article not found'); + const parent = parentRes.rows[0]; + if (!parent) { + throw new Error('Parent article not found'); } - if (!father.is_father || !father.fatherattributename_id) { - throw new Error('Article is not a father article'); + if (!parent.is_parent || !parent.parentattributename_id) { + throw new Error('Article is not a parent 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 }, + // Fetch parent's text / name + const parentTextRes = await runner.query( + 'SELECT name FROM articletext WHERE article_id = @parentId AND language_id = @languageId', + { parentId, languageId }, ); - const fatherName = fatherTextRes.rows[0]?.name || father.sku; + const parentName = parentTextRes.rows[0]?.name || parent.sku; // Determine attribute value let finalValueId = null; @@ -98,15 +98,15 @@ export default function addChild(provider) { 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'); + if (!row || row.attributeId !== parent.parentattributename_id) { + throw new Error('Attribute value not found or does not match parent 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, + attributeId: parent.parentattributename_id, value: valStr, }); if (existingValRes.rows[0]) { @@ -114,11 +114,11 @@ export default function addChild(provider) { finalValue = existingValRes.rows[0].value; } else { const sortRes = await runner.query(attrValQueries.GET_NEXT_SORT, { - attributeId: father.fatherattributename_id, + attributeId: parent.parentattributename_id, }); const sort = sortRes.rows[0]?.sort ?? 0; const createdValRes = await runner.query(attrValQueries.CREATE, { - attributeId: father.fatherattributename_id, + attributeId: parent.parentattributename_id, value: valStr, sort, }); @@ -129,26 +129,26 @@ export default function addChild(provider) { throw new Error('Attribute value is required for child article'); } - // Check if father already has a variant with this attribute value + // Check if parent already has a variant with this attribute value const existingVariantRes = await runner.query( `SELECT a.id FROM article a INNER JOIN articleattributevalue aav ON aav.article_id = a.id - WHERE a.fatherarticle_id = @fatherId + WHERE a.parentarticle_id = @parentId AND aav.attributevalue_id = @finalValueId`, - { fatherId, finalValueId }, + { parentId, finalValueId }, ); if (existingVariantRes.rows.length > 0) { throw new Error('Variant with this attribute value already exists'); } - // Compute child SKU using next free index: fatherSku-1, fatherSku-2, ... + // Compute child SKU using next free index: parentSku-1, parentSku-2, ... const existingSkusRes = await runner.query( 'SELECT sku FROM article WHERE company_id = @companyId AND sku LIKE @prefix', - { companyId, prefix: `${father.sku}-%` }, + { companyId, prefix: `${parent.sku}-%` }, ); const usedIndices = new Set(); - const prefix = `${father.sku}-`; + const prefix = `${parent.sku}-`; for (const row of existingSkusRes.rows) { if (row.sku && row.sku.startsWith(prefix)) { const numPart = row.sku.slice(prefix.length); @@ -161,42 +161,42 @@ export default function addChild(provider) { while (usedIndices.has(nextIndex)) { nextIndex++; } - const childSku = `${father.sku}-${nextIndex}`; + const childSku = `${parent.sku}-${nextIndex}`; - // Next free fathersort (smallest non-negative integer not used by any child of this father) + // Next free parentsort (smallest non-negative integer not used by any child of this parent) const existingSortsRes = await runner.query( - 'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC', - { fatherId }, + 'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC', + { parentId }, ); - const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort))); - let fathersort = 0; - while (usedSorts.has(fathersort)) { - fathersort++; + const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort))); + let parentsort = 0; + while (usedSorts.has(parentsort)) { + parentsort++; } // Auto-create child article const rawTrack = options.track_stock ?? options.trackStock; - const finalTrackStock = rawTrack == null ? father.track_stock : (rawTrack ? 1 : 0); + const finalTrackStock = rawTrack == null ? parent.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, + unitId: parent.unit_id, + unitamount: options.unitamount ?? parent.unitamount, + weight: options.weight ?? parent.weight, trackStock: finalTrackStock, - brandId: father.brand_id, - fatherArticleId: fatherId, - fathersort, + brandId: parent.brand_id, + parentArticleId: parentId, + parentsort, }); const childId = childRes.rows[0]?.id ?? childRes.insertId; - // Copy existing attribute values from father (ancestor attributes that father has) - const fatherAttrsRes = await runner.query( - 'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @fatherId', - { fatherId }, + // Copy existing attribute values from parent (ancestor attributes that parent has) + const parentAttrsRes = await runner.query( + 'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @parentId', + { parentId }, ); - for (const faRow of fatherAttrsRes.rows) { + for (const faRow of parentAttrsRes.rows) { if (faRow.attributevalue_id !== finalValueId) { await runner.query(articleAttrValQueries.INSERT_ROW, { articleId: childId, @@ -213,10 +213,10 @@ export default function addChild(provider) { sort: 0, }); - // Copy father's categories + // Copy parent's categories const catRes = await runner.query( - 'SELECT category_id FROM categoryarticle WHERE article_id = @fatherId', - { fatherId }, + 'SELECT category_id FROM categoryarticle WHERE article_id = @parentId', + { parentId }, ); for (const catRow of catRes.rows) { await runner.query(addCategoryArticleSql, { @@ -226,7 +226,7 @@ export default function addChild(provider) { } // Create article text - const childName = options.name?.trim() || `${fatherName} - ${finalValue}`; + const childName = options.name?.trim() || `${parentName} - ${finalValue}`; const childSlug = await resolveSlug(runner, { companyId, languageId, @@ -243,12 +243,12 @@ export default function addChild(provider) { await runner.query(insertHistorySql, { articleId: childId, companyuserId, - text: `Artikel "${childName}" (${childSku}) als Variante von "${father.sku}" angelegt`, + text: `Artikel "${childName}" (${childSku}) als Variante von "${parent.sku}" angelegt`, }); - // History on father + // History on parent await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: `Variante "${childName}" (${childSku}) hinzugefügt`, }); diff --git a/src/store/article/attachChild.js b/src/store/article/attachChild.js index 2968b31..d9f0a5e 100644 --- a/src/store/article/attachChild.js +++ b/src/store/article/attachChild.js @@ -26,23 +26,23 @@ export default function attachChild(provider) { const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** - * Attaches an existing article as a child to a father article. + * Attaches an existing article as a child to a parent article. * * @param {number} googleAuthId - The id of the Google auth user. - * @param {string} fatherSku - The father article SKU. + * @param {string} parentSku - The parent article SKU. * @param {object} options * @param {string} options.sku - The child article SKU to attach. * @param {string} [options.childSku] - Alias for options.sku. - * @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 {boolean} [options.reparent=false] - Whether to allow re-parenting if child belongs to another father. + * @param {number} [options.attributeValueId] - Existing attribute value ID for parent's attribute. + * @param {string} [options.value] - String value to find or create under parent's attribute. + * @param {boolean} [options.reparent=false] - Whether to allow re-parenting if child belongs to another parent. * @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<{article: object, children: Array}>} */ - return async (googleAuthId, fatherSku, options = {}, tx) => { + return async (googleAuthId, parentSku, options = {}, tx) => { const run = async (runner) => { const childSku = (typeof options.sku === 'string' && options.sku.trim()) || (typeof options.childSku === 'string' && options.childSku.trim()) @@ -52,35 +52,35 @@ export default function attachChild(provider) { throw new Error('sku is required to attach an existing article'); } - const { articleId: fatherId, companyId, languageId, companyuserId } = await resolve( + const { articleId: parentId, companyId, languageId, companyuserId } = await resolve( googleAuthId, - fatherSku, + parentSku, runner, ); - // Fetch father article fields - const fatherRes = await runner.query( - 'SELECT id, sku, company_id, is_father, fatherattributename_id FROM article WHERE id = @fatherId', - { fatherId }, + // Fetch parent article fields + const parentRes = await runner.query( + 'SELECT id, sku, company_id, is_parent, parentattributename_id FROM article WHERE id = @parentId', + { parentId }, ); - const father = fatherRes.rows[0]; - if (!father) { - throw new Error('Father article not found'); + const parent = parentRes.rows[0]; + if (!parent) { + throw new Error('Parent article not found'); } - if (!father.is_father || !father.fatherattributename_id) { - throw new Error('Article is not a father article'); + if (!parent.is_parent || !parent.parentattributename_id) { + throw new Error('Article is not a parent article'); } // Check child article exists in company const childRes = await runner.query( - 'SELECT id, sku, company_id, fatherarticle_id, is_father FROM article WHERE sku = @childSku AND company_id = @companyId', + 'SELECT id, sku, company_id, parentarticle_id, is_parent FROM article WHERE sku = @childSku AND company_id = @companyId', { childSku, companyId }, ); const child = childRes.rows[0]; if (!child) { throw new Error('Article not found'); } - if (child.id === fatherId) { + if (child.id === parentId) { throw new Error('An article cannot be a child of itself'); } @@ -97,15 +97,15 @@ export default function attachChild(provider) { 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'); + if (!row || row.attributeId !== parent.parentattributename_id) { + throw new Error('Attribute value not found or does not match parent 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, + attributeId: parent.parentattributename_id, value: valStr, }); if (existingValRes.rows[0]) { @@ -113,11 +113,11 @@ export default function attachChild(provider) { finalValue = existingValRes.rows[0].value; } else { const sortRes = await runner.query(attrValQueries.GET_NEXT_SORT, { - attributeId: father.fatherattributename_id, + attributeId: parent.parentattributename_id, }); const sort = sortRes.rows[0]?.sort ?? 0; const createdValRes = await runner.query(attrValQueries.CREATE, { - attributeId: father.fatherattributename_id, + attributeId: parent.parentattributename_id, value: valStr, sort, }); @@ -128,78 +128,78 @@ export default function attachChild(provider) { throw new Error('Attribute value is required for child article'); } - const isAlreadyChildOfThisFather = child.fatherarticle_id === fatherId; + const isAlreadyChildOfThisParent = child.parentarticle_id === parentId; - if (child.fatherarticle_id != null && !isAlreadyChildOfThisFather && !options.reparent) { - throw new Error('Article already belongs to another father'); + if (child.parentarticle_id != null && !isAlreadyChildOfThisParent && !options.reparent) { + throw new Error('Article already belongs to another parent'); } - // Check if father already has a different child with this attribute value + // Check if parent already has a different child with this attribute value const existingVariantRes = await runner.query( `SELECT a.id FROM article a INNER JOIN articleattributevalue aav ON aav.article_id = a.id - WHERE a.fatherarticle_id = @fatherId + WHERE a.parentarticle_id = @parentId AND a.id != @childId AND aav.attributevalue_id = @finalValueId`, - { fatherId, childId: child.id, finalValueId }, + { parentId, childId: child.id, finalValueId }, ); if (existingVariantRes.rows.length > 0) { throw new Error('Variant with this attribute value already exists'); } - // If re-parenting from another father, unparent and recompact previous father - if (child.fatherarticle_id != null && !isAlreadyChildOfThisFather && options.reparent) { - const prevFatherId = child.fatherarticle_id; + // If re-parenting from another parent, unparent and recompact previous parent + if (child.parentarticle_id != null && !isAlreadyChildOfThisParent && options.reparent) { + const prevParentId = child.parentarticle_id; await runner.query(insertHistorySql, { - articleId: prevFatherId, + articleId: prevParentId, companyuserId, - text: `Variante (${childSku}) getrennt (umgehängt zu "${father.sku}")`, + text: `Variante (${childSku}) getrennt (umgehängt zu "${parent.sku}")`, }); - // Recompact previous father's children + // Recompact previous parent's children const prevRemainingRes = await runner.query( - 'SELECT id, fathersort FROM article WHERE fatherarticle_id = @prevFatherId AND id != @childId ORDER BY fathersort ASC, id ASC', - { prevFatherId, childId: child.id }, + 'SELECT id, parentsort FROM article WHERE parentarticle_id = @prevParentId AND id != @childId ORDER BY parentsort ASC, id ASC', + { prevParentId, childId: child.id }, ); for (let i = 0; i < prevRemainingRes.rows.length; i++) { const row = prevRemainingRes.rows[i]; - if (Number(row.fathersort) !== i) { - await runner.query(queries.SET_FATHER_SORT, { + if (Number(row.parentsort) !== i) { + await runner.query(queries.SET_PARENT_SORT, { articleId: row.id, - fatherArticleId: prevFatherId, - fathersort: i, + parentArticleId: prevParentId, + parentsort: i, }); } } } - // Assign fathersort if not already child of this father - let fathersort; - if (isAlreadyChildOfThisFather) { + // Assign parentsort if not already child of this parent + let parentsort; + if (isAlreadyChildOfThisParent) { const currentSortRes = await runner.query( - 'SELECT fathersort FROM article WHERE id = @childId', + 'SELECT parentsort FROM article WHERE id = @childId', { childId: child.id }, ); - fathersort = Number(currentSortRes.rows[0]?.fathersort ?? 0); + parentsort = Number(currentSortRes.rows[0]?.parentsort ?? 0); } else { const existingSortsRes = await runner.query( - 'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC', - { fatherId }, + 'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC', + { parentId }, ); - const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort))); - fathersort = 0; - while (usedSorts.has(fathersort)) { - fathersort++; + const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort))); + parentsort = 0; + while (usedSorts.has(parentsort)) { + parentsort++; } } - // Link child to father - await runner.query(queries.UPDATE_FATHER_BY_SKU, { + // Link child to parent + await runner.query(queries.UPDATE_PARENT_BY_SKU, { sku: childSku, companyId, - fatherArticleId: fatherId, - fathersort, + parentArticleId: parentId, + parentsort, }); // Apply optional field overrides @@ -223,15 +223,15 @@ export default function attachChild(provider) { }); } - // Remove existing attribute value for this father's attribute on child (if any) - if (father.fatherattributename_id) { + // Remove existing attribute value for this parent's attribute on child (if any) + if (parent.parentattributename_id) { await runner.query( `DELETE FROM articleattributevalue WHERE article_id = @childId AND attributevalue_id IN ( SELECT id FROM attributevalue WHERE attribute_id = @attributeId )`, - { childId: child.id, attributeId: father.fatherattributename_id }, + { childId: child.id, attributeId: parent.parentattributename_id }, ); } await runner.query(articleAttrValQueries.INSERT_ROW, { @@ -240,12 +240,12 @@ export default function attachChild(provider) { sort: 0, }); - // Copy ancestor attributes from father (if missing on child) - const fatherAttrsRes = await runner.query( - 'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @fatherId', - { fatherId }, + // Copy ancestor attributes from parent (if missing on child) + const parentAttrsRes = await runner.query( + 'SELECT attributevalue_id, sort FROM articleattributevalue WHERE article_id = @parentId', + { parentId }, ); - for (const faRow of fatherAttrsRes.rows) { + for (const faRow of parentAttrsRes.rows) { if (faRow.attributevalue_id !== finalValueId) { const hasAttrRes = await runner.query( 'SELECT 1 FROM articleattributevalue WHERE article_id = @childId AND attributevalue_id = @attrValId', @@ -261,10 +261,10 @@ export default function attachChild(provider) { } } - // Copy father's categories (if missing on child) + // Copy parent's categories (if missing on child) const catRes = await runner.query( - 'SELECT category_id FROM categoryarticle WHERE article_id = @fatherId', - { fatherId }, + 'SELECT category_id FROM categoryarticle WHERE article_id = @parentId', + { parentId }, ); for (const catRow of catRes.rows) { const hasCatRes = await runner.query( @@ -287,22 +287,22 @@ export default function attachChild(provider) { const childName = childTextRes.rows[0]?.name || childSku; // History only if not already child - if (!isAlreadyChildOfThisFather) { + if (!isAlreadyChildOfThisParent) { await runner.query(insertHistorySql, { articleId: child.id, companyuserId, - text: `Artikel "${childName}" (${childSku}) als Variante von "${father.sku}" zugewiesen`, + text: `Artikel "${childName}" (${childSku}) als Variante von "${parent.sku}" zugewiesen`, }); await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: `Variante "${childName}" (${childSku}) hinzugefügt`, }); } const updated = await load(googleAuthId, childSku, runner); - const children = await list(googleAuthId, fatherSku, runner); + const children = await list(googleAuthId, parentSku, runner); return { article: updated.article, diff --git a/src/store/article/delete.js b/src/store/article/delete.js index 3da56f3..947850c 100644 --- a/src/store/article/delete.js +++ b/src/store/article/delete.js @@ -69,11 +69,11 @@ export default function del(provider) { await deleteArticleAndLists(id); }; - const fatherRes = await runner.query( - 'SELECT fatherarticle_id FROM article WHERE id = @articleId', + const parentRes = await runner.query( + 'SELECT parentarticle_id FROM article WHERE id = @articleId', { articleId }, ); - const fatherArticleId = fatherRes.rows[0]?.fatherarticle_id; + const parentArticleId = parentRes.rows[0]?.parentarticle_id; const childCount = await runner.query(queries.GET_CHILD_COUNT, { articleId }); if (childCount.rows[0].count > 0) { @@ -85,18 +85,18 @@ export default function del(provider) { await deleteArticleAndLists(articleId); } - if (fatherArticleId != null) { + if (parentArticleId != null) { const remainingChildrenRes = await runner.query( - 'SELECT id, fathersort FROM article WHERE fatherarticle_id = @fatherArticleId ORDER BY fathersort ASC, id ASC', - { fatherArticleId }, + 'SELECT id, parentsort FROM article WHERE parentarticle_id = @parentArticleId ORDER BY parentsort ASC, id ASC', + { parentArticleId }, ); for (let i = 0; i < remainingChildrenRes.rows.length; i++) { const childRow = remainingChildrenRes.rows[i]; - if (Number(childRow.fathersort) !== i) { - await runner.query(articleQueries.SET_FATHER_SORT, { + if (Number(childRow.parentsort) !== i) { + await runner.query(articleQueries.SET_PARENT_SORT, { articleId: childRow.id, - fatherArticleId, - fathersort: i, + parentArticleId, + parentsort: i, }); } } diff --git a/src/store/article/getBySku.js b/src/store/article/getBySku.js index a1294ed..6df5bd8 100644 --- a/src/store/article/getBySku.js +++ b/src/store/article/getBySku.js @@ -18,7 +18,7 @@ export default function getBySku(provider) { * @param {number} googleAuthId - The id of the Google auth user. * @param {string} sku - The article SKU. * @param {object} [tx] - Optional transaction. - * @returns {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}}>} The full article detail. + * @returns {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, parentArticleId: number|null, parentSku: string|null}}>} The full article detail. * @throws {Error} If the article is not found or the user is not authorized. * * @example @@ -118,16 +118,16 @@ export default function getBySku(provider) { sort: Number(item.sort ?? 0), })), 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, + isParent: Boolean(a.isParent), + parentAttributeNameId: a.parentAttributeNameId ?? null, + parentAttributeName: a.parentAttributeName ?? null, + parentAttributeExclusive: a.parentAttributeExclusive != null ? Boolean(a.parentAttributeExclusive) : null, + parentArticleId: a.parentArticleId, + parentSku: a.parentSku, children: parseJsonArray(a.childrenJson).map((c) => ({ id: c.id, sku: c.sku, - fathersort: c.fathersort, + parentsort: c.parentsort, name: c.name ?? null, attributeValueId: c.attributeValueId ?? null, attributeValue: c.attributeValue ?? null, diff --git a/src/store/article/importRows.js b/src/store/article/importRows.js index 33e5938..ed7c4c6 100644 --- a/src/store/article/importRows.js +++ b/src/store/article/importRows.js @@ -87,7 +87,7 @@ export default function importRows(provider) { * Imports article rows, creating articles by their SKU and returning per-row * import, skip and error outcomes. * - * The import runs in **two passes** so a `fatherSku` that references a row + * The import runs in **two passes** so a `parentSku` that references a row * appearing later in the file still resolves. A row is **skipped** when its * SKU already exists in the company (upsert mode is not applied), and an EAN * is skipped when already used by another row. @@ -97,7 +97,7 @@ export default function importRows(provider) { * @param {Array} input.rows - The article rows to import. * @param {string} input.rows[].sku - The article SKU. * @param {string} input.rows[].name - The article name. - * @param {string} [input.rows[].vk] - `'V'` (father/compound) or `'K'` (variant) marker. + * @param {string} [input.rows[].vk] - `'V'` (parent/compound) or `'K'` (variant) marker. * @param {number|string} [input.rows[].parentId] - The id of the parent category. * @param {string} [input.rows[].parentCategory] - The category breadcrumb path, e.g. `'Flowers | CBN'`. * @param {string} [input.rows[].brand] - The brand name to upsert. @@ -105,7 +105,7 @@ export default function importRows(provider) { * @param {number|string} [input.rows[].tax] - The tax rate in percent (e.g. `19`). * @param {Array|string} [input.rows[].eans] - The EANs to set. * @param {boolean|number} [input.rows[].track_stock=true] - Whether inventory stock is tracked. - * @param {string} [input.rows[].fatherSku] - The father article SKU for a V/K compound link. + * @param {string} [input.rows[].parentSku] - The parent article SKU for a V/K compound link. * @param {number|string} [input.rows[].deposit] - The deposit (Pfand) value; currently ignored. * @param {object} [tx] - Optional transaction. * @returns {Promise<{imported: Array, skipped: Array, errors: Array}>} The import outcome. @@ -114,7 +114,7 @@ export default function importRows(provider) { * const result = await store.article.importRows({ * googleAuthId: 42, * rows: [ - * { sku: '42001190-1', name: '420Strainz 1g', vk: 'K', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '11,00', tax: 19, eans: ['4262557690455'], fatherSku: '42001190' }, + * { sku: '42001190-1', name: '420Strainz 1g', vk: 'K', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '11,00', tax: 19, eans: ['4262557690455'], parentSku: '42001190' }, * { sku: '42001190', name: '420Strainz', vk: 'V', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '0,00', tax: 19 }, * ], * }); @@ -275,63 +275,63 @@ export default function importRows(provider) { } } - // Pass 2: wire V/K father links (fathers may be created in pass 1). + // Pass 2: wire V/K parent links (parents may be created in pass 1). for (const row of rows) { const sku = String(row?.sku ?? '').trim(); - const fatherSku = - row?.fatherSku != null ? String(row.fatherSku).trim() : ''; - if (!sku || !fatherSku) continue; + const parentSku = + row?.parentSku != null ? String(row.parentSku).trim() : ''; + if (!sku || !parentSku) continue; try { await runInTx(async (runner) => { const child = await runner.query(articleQueries.GET_ID_BY_SKU, { sku, companyId }); const childId = child.rows[0]?.articleId; if (childId) { - const father = await runner.query( - 'SELECT id, is_father FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)', - { sku: fatherSku, companyId }, + const parent = await runner.query( + 'SELECT id, is_parent FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)', + { sku: parentSku, companyId }, ); - const fatherRow = father.rows[0]; - const fatherId = fatherRow?.id; - if (fatherId && fatherId !== childId) { - const wasFather = Boolean(fatherRow.is_father); + const parentRow = parent.rows[0]; + const parentId = parentRow?.id; + if (parentId && parentId !== childId) { + const wasParent = Boolean(parentRow.is_parent); const existingSortsRes = await runner.query( - 'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC', - { fatherId }, + 'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC', + { parentId }, ); - const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort))); - let fathersort = 0; - while (usedSorts.has(fathersort)) { - fathersort++; + const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort))); + let parentsort = 0; + while (usedSorts.has(parentsort)) { + parentsort++; } - await runner.query(articleQueries.UPDATE_FATHER_BY_SKU, { + await runner.query(articleQueries.UPDATE_PARENT_BY_SKU, { sku, companyId, - fatherArticleId: fatherId, - fathersort, + parentArticleId: parentId, + parentsort, }); - await runner.query(articleQueries.SET_IS_FATHER, { - articleId: fatherId, + await runner.query(articleQueries.SET_IS_PARENT, { + articleId: parentId, }); - if (!wasFather) { + if (!wasParent) { await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: 'Artikel zu Vaterartikel konvertiert', }); } const childName = String(row?.name ?? sku).trim(); await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: `Variante "${childName}" (${sku}) hinzugefügt`, }); await runner.query(insertHistorySql, { articleId: childId, companyuserId, - text: `Artikel "${childName}" (${sku}) als Variante von "${fatherSku}" zugewiesen`, + text: `Artikel "${childName}" (${sku}) als Variante von "${parentSku}" zugewiesen`, }); } } diff --git a/src/store/article/listChildren.js b/src/store/article/listChildren.js index 2c15217..39f3bc5 100644 --- a/src/store/article/listChildren.js +++ b/src/store/article/listChildren.js @@ -9,40 +9,40 @@ export default function listChildren(provider) { const queries = isSqlite ? sqliteArticle : mssqlArticle; /** - * Lists all children of a father article. + * Lists all children of a parent article. * * @param {number} googleAuthId - The id of the Google auth user. - * @param {string} fatherSku - The father article SKU. + * @param {string} parentSku - The parent article SKU. * @param {object} [tx] - Optional transaction. - * @returns {Promise>} + * @returns {Promise>} */ - return async (googleAuthId, fatherSku, tx) => { + return async (googleAuthId, parentSku, tx) => { const run = async (runner) => { - const { articleId: fatherId, languageId } = await resolve( + const { articleId: parentId, languageId } = await resolve( googleAuthId, - fatherSku, + parentSku, runner, ); - const fatherRes = await runner.query( - 'SELECT is_father, fatherattributename_id FROM article WHERE id = @fatherId', - { fatherId }, + const parentRes = await runner.query( + 'SELECT is_parent, parentattributename_id FROM article WHERE id = @parentId', + { parentId }, ); - const father = fatherRes.rows[0]; - if (!father || !father.is_father) { + const parent = parentRes.rows[0]; + if (!parent || !parent.is_parent) { return []; } - const result = await runner.query(queries.LIST_CHILDREN_BY_FATHER_ID, { - fatherArticleId: fatherId, + const result = await runner.query(queries.LIST_CHILDREN_BY_PARENT_ID, { + parentArticleId: parentId, languageId, - attributeId: father.fatherattributename_id, + attributeId: parent.parentattributename_id, }); return result.rows.map((row) => ({ id: row.id, sku: row.sku, - fathersort: Number(row.fathersort ?? 0), + parentsort: Number(row.parentsort ?? 0), trackStock: Boolean(row.trackStock ?? 1), unitamount: Number(row.unitamount ?? 1), weight: Number(row.weight ?? 0), diff --git a/src/store/article/queryBySku.js b/src/store/article/queryBySku.js index e3116a3..75a858a 100644 --- a/src/store/article/queryBySku.js +++ b/src/store/article/queryBySku.js @@ -35,8 +35,8 @@ function shapeArticleDetail(row, categoryIds, photos, eans, prices) { optionsCount: row.optionsCount, articleListCount: row.articleListCount, childCount: row.childCount, - fatherArticleId: row.fatherArticleId, - fatherSku: row.fatherSku, + parentArticleId: row.parentArticleId, + parentSku: row.parentSku, }; } diff --git a/src/store/article/removeChild.js b/src/store/article/removeChild.js index e60a662..cb0bada 100644 --- a/src/store/article/removeChild.js +++ b/src/store/article/removeChild.js @@ -15,34 +15,34 @@ export default function removeChild(provider) { const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** - * Removes a child article from a father article, either by unlinking or deleting it. + * Removes a child article from a parent 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} parentSku - The parent 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 {boolean} [options.cascade=true] - Whether to cascade delete child subtrees when deleteArticle is true. * @param {object} [tx] - Optional transaction. - * @returns {Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>} + * @returns {Promise<{success: boolean, parentSku: string, childSku: string, deleted: boolean}>} */ - return async (googleAuthId, fatherSku, childSku, options = {}, tx) => { + return async (googleAuthId, parentSku, childSku, options = {}, tx) => { const run = async (runner) => { - const { articleId: fatherId, languageId, companyuserId } = await resolve( + const { articleId: parentId, languageId, companyuserId } = await resolve( googleAuthId, - fatherSku, + parentSku, 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', + 'SELECT parentarticle_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 (!child || child.parentarticle_id !== parentId) { + throw new Error('Child article does not belong to this parent'); } // Fetch child name before any deletion (needed for history regardless of path) @@ -54,18 +54,18 @@ export default function removeChild(provider) { ); const childName = childNameRes.rows[0]?.name || childSku; - const recompactFatherSort = async () => { + const recompactParentSort = async () => { const remainingChildrenRes = await runner.query( - 'SELECT id, fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC, id ASC', - { fatherId }, + 'SELECT id, parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC, id ASC', + { parentId }, ); for (let i = 0; i < remainingChildrenRes.rows.length; i++) { const childRow = remainingChildrenRes.rows[i]; - if (Number(childRow.fathersort) !== i) { - await runner.query(queries.SET_FATHER_SORT, { + if (Number(childRow.parentsort) !== i) { + await runner.query(queries.SET_PARENT_SORT, { articleId: childRow.id, - fatherArticleId: fatherId, - fathersort: i, + parentArticleId: parentId, + parentsort: i, }); } } @@ -75,67 +75,67 @@ export default function removeChild(provider) { const cascade = options.cascade !== undefined ? Boolean(options.cascade) : true; await del(googleAuthId, childSku, { cascade }, runner); - await recompactFatherSort(); + await recompactParentSort(); - // History on father (child article is gone so we can only log on father) + // History on parent (child article is gone so we can only log on parent) await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: `Variante "${childName}" (${childSku}) gelöscht`, }); return { success: true, - fatherSku, + parentSku, childSku, deleted: true, }; } - // Fetch father's attribute name id - const fatherRes = await runner.query( - 'SELECT fatherattributename_id FROM article WHERE id = @fatherId', - { fatherId }, + // Fetch parent's attribute name id + const parentRes = await runner.query( + 'SELECT parentattributename_id FROM article WHERE id = @parentId', + { parentId }, ); - const fatherAttributeNameId = fatherRes.rows[0]?.fatherattributename_id; + const parentAttributeNameId = parentRes.rows[0]?.parentattributename_id; - // Unlink child from father + // Unlink child from parent await runner.query(queries.UNLINK_CHILD, { articleId: childId, - fatherArticleId: fatherId, + parentArticleId: parentId, }); - // Remove the child's attribute value link for the father's attribute - if (fatherAttributeNameId) { + // Remove the child's attribute value link for the parent's attribute + if (parentAttributeNameId) { await runner.query( `DELETE FROM articleattributevalue WHERE article_id = @childId AND attributevalue_id IN ( - SELECT id FROM attributevalue WHERE attribute_id = @fatherAttributeNameId + SELECT id FROM attributevalue WHERE attribute_id = @parentAttributeNameId )`, - { childId, fatherAttributeNameId }, + { childId, parentAttributeNameId }, ); } - await recompactFatherSort(); + await recompactParentSort(); // Add history on child await runner.query(insertHistorySql, { articleId: childId, companyuserId, - text: `Artikel "${childSku}" von Vaterartikel "${fatherSku}" getrennt`, + text: `Artikel "${childSku}" von Vaterartikel "${parentSku}" getrennt`, }); - // Add history on father + // Add history on parent await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: `Variante "${childName}" (${childSku}) getrennt`, }); return { success: true, - fatherSku, + parentSku, childSku, deleted: false, }; diff --git a/src/store/article/setParentBySku.js b/src/store/article/setParentBySku.js index fc36b87..56d9ec7 100644 --- a/src/store/article/setParentBySku.js +++ b/src/store/article/setParentBySku.js @@ -8,7 +8,7 @@ import queryBySku from './queryBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import makeRunner from '../runner.js'; -export default function setFatherBySku(provider) { +export default function setParentBySku(provider) { const resolve = resolveArticleBySku(provider); const load = queryBySku(provider); const isSqlite = provider?.dialect === 'sqlite3'; @@ -18,20 +18,20 @@ export default function setFatherBySku(provider) { const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** - * Links an article to a father article (V/K compound) by their SKUs. + * Links an article to a parent article (V/K compound) by their SKUs. * * @param {number} googleAuthId - The id of the Google auth user. * @param {string} sku - The child article SKU. - * @param {string|null} fatherSku - The father article SKU, or `null` to unlink. + * @param {string|null} parentSku - The parent article SKU, or `null` to unlink. * @param {object} [tx] - Optional transaction to run within. * @returns {Promise} The updated child article. - * @throws {Error} If the article or father is not found, or the link is invalid. + * @throws {Error} If the article or parent is not found, or the link is invalid. * * @example - * const article = await store.article.setFatherBySku(42, '42001190-1', '42001190'); - * // => { id, sku, name, fatherSku: '42001190', ... } + * const article = await store.article.setParentBySku(42, '42001190-1', '42001190'); + * // => { id, sku, name, parentSku: '42001190', ... } */ - return async (googleAuthId, sku, fatherSku, tx) => { + return async (googleAuthId, sku, parentSku, tx) => { const runSet = async (runner) => { const { articleId, languageId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); @@ -41,34 +41,34 @@ export default function setFatherBySku(provider) { ); const childName = childTextRes.rows[0]?.name || sku; - if (fatherSku == null || fatherSku === '') { - const prevFatherRes = await runner.query( - 'SELECT fatherarticle_id FROM article WHERE id = @articleId', + if (parentSku == null || parentSku === '') { + const prevParentRes = await runner.query( + 'SELECT parentarticle_id FROM article WHERE id = @articleId', { articleId }, ); - const prevFatherId = prevFatherRes.rows[0]?.fatherarticle_id; + const prevParentId = prevParentRes.rows[0]?.parentarticle_id; - await runner.query(queries.UPDATE_FATHER_BY_SKU, { + await runner.query(queries.UPDATE_PARENT_BY_SKU, { sku, companyId, - fatherArticleId: null, + parentArticleId: null, }); - if (prevFatherId) { - const prevFatherTextRes = await runner.query( - 'SELECT sku FROM article WHERE id = @prevFatherId', - { prevFatherId }, + if (prevParentId) { + const prevParentTextRes = await runner.query( + 'SELECT sku FROM article WHERE id = @prevParentId', + { prevParentId }, ); - const prevFatherSku = prevFatherTextRes.rows[0]?.sku || String(prevFatherId); + const prevParentSku = prevParentTextRes.rows[0]?.sku || String(prevParentId); await runner.query(insertHistorySql, { articleId, companyuserId, - text: `Artikel "${sku}" von Vaterartikel "${prevFatherSku}" getrennt`, + text: `Artikel "${sku}" von Vaterartikel "${prevParentSku}" getrennt`, }); await runner.query(insertHistorySql, { - articleId: prevFatherId, + articleId: prevParentId, companyuserId, text: `Variante "${childName}" (${sku}) getrennt`, }); @@ -81,52 +81,52 @@ export default function setFatherBySku(provider) { return unlinked; } - const father = await runner.query( - 'SELECT id, is_father, sku FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)', - { sku: fatherSku, companyId }, + const parent = await runner.query( + 'SELECT id, is_parent, sku FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId)', + { sku: parentSku, companyId }, ); - const fatherRow = father.rows[0]; - const fatherId = fatherRow?.id; - if (!fatherId) { - throw new Error('Father not found'); + const parentRow = parent.rows[0]; + const parentId = parentRow?.id; + if (!parentId) { + throw new Error('Parent not found'); } - if (fatherId === articleId) { - throw new Error('Invalid father'); + if (parentId === articleId) { + throw new Error('Invalid parent'); } - const wasFather = Boolean(fatherRow.is_father); + const wasParent = Boolean(parentRow.is_parent); const existingSortsRes = await runner.query( - 'SELECT fathersort FROM article WHERE fatherarticle_id = @fatherId ORDER BY fathersort ASC', - { fatherId }, + 'SELECT parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC', + { parentId }, ); - const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.fathersort))); - let fathersort = 0; - while (usedSorts.has(fathersort)) { - fathersort++; + const usedSorts = new Set(existingSortsRes.rows.map((r) => Number(r.parentsort))); + let parentsort = 0; + while (usedSorts.has(parentsort)) { + parentsort++; } - await runner.query(queries.UPDATE_FATHER_BY_SKU, { + await runner.query(queries.UPDATE_PARENT_BY_SKU, { sku, companyId, - fatherArticleId: fatherId, - fathersort, + parentArticleId: parentId, + parentsort, }); - await runner.query(queries.SET_IS_FATHER, { - articleId: fatherId, + await runner.query(queries.SET_IS_PARENT, { + articleId: parentId, }); - if (!wasFather) { + if (!wasParent) { await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: 'Artikel zu Vaterartikel konvertiert', }); } await runner.query(insertHistorySql, { - articleId: fatherId, + articleId: parentId, companyuserId, text: `Variante "${childName}" (${sku}) hinzugefügt`, }); @@ -134,7 +134,7 @@ export default function setFatherBySku(provider) { await runner.query(insertHistorySql, { articleId, companyuserId, - text: `Artikel "${sku}" als Variante von "${fatherSku}" zugewiesen`, + text: `Artikel "${sku}" als Variante von "${parentSku}" zugewiesen`, }); const article = await load(articleId, languageId, runner); diff --git a/src/store/article/setPricesBySku.js b/src/store/article/setPricesBySku.js index 97e839a..c605872 100644 --- a/src/store/article/setPricesBySku.js +++ b/src/store/article/setPricesBySku.js @@ -86,9 +86,9 @@ export default function setPricesBySku(provider) { throw new Error('POS customer group required'); } - const artCheck = await runner.query('SELECT is_father FROM article WHERE id = @articleId', { articleId }); - if (artCheck.rows[0]?.is_father) { - throw new Error('A father article cannot have prices'); + const artCheck = await runner.query('SELECT is_parent FROM article WHERE id = @articleId', { articleId }); + if (artCheck.rows[0]?.is_parent) { + throw new Error('A parent article cannot have prices'); } // Read old prices for history diff --git a/src/store/article/toggleParent.js b/src/store/article/toggleParent.js index 79698fe..21289bb 100644 --- a/src/store/article/toggleParent.js +++ b/src/store/article/toggleParent.js @@ -10,7 +10,7 @@ import resolveArticleBySku from './resolveArticleBySku.js'; import getBySku from './getBySku.js'; import makeRunner from '../runner.js'; -export default function toggleFather(provider) { +export default function toggleParent(provider) { const resolve = resolveArticleBySku(provider); const load = getBySku(provider); const isSqlite = provider?.dialect === 'sqlite3'; @@ -21,39 +21,39 @@ export default function toggleFather(provider) { const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** - * Toggles an article into or out of father mode. + * Toggles an article into or out of parent 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 {boolean} options.isParent - Whether the article should be a parent article. + * @param {number} [options.attributeNameId] - The attribute name id required when enabling parent mode. + * @param {number} [options.parentAttributeNameId] - Alias for attributeNameId. * @param {object} [tx] - Optional transaction. * @returns {Promise} The updated article detail. */ - return async (googleAuthId, sku, { isFather, attributeNameId, fatherAttributeNameId } = {}, tx) => { + return async (googleAuthId, sku, { isParent, attributeNameId, parentAttributeNameId } = {}, tx) => { const run = async (runner) => { const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); - const targetIsFather = Boolean(isFather); - const targetAttrId = attributeNameId ?? fatherAttributeNameId; + const targetIsParent = Boolean(isParent); + const targetAttrId = attributeNameId ?? parentAttributeNameId; - if (targetIsFather) { + if (targetIsParent) { if (!targetAttrId || Number(targetAttrId) <= 0) { - throw new Error('attributeNameId is required to become a father article'); + throw new Error('attributeNameId is required to become a parent article'); } const currentRes = await runner.query( - 'SELECT fatherarticle_id, articlelist_id, optionslist_id FROM article WHERE id = @articleId', + 'SELECT parentarticle_id, articlelist_id, optionslist_id FROM article WHERE id = @articleId', { articleId }, ); const currentArt = currentRes.rows[0]; if (currentArt?.articlelist_id != null) { - throw new Error('A Stückliste article cannot become a father'); + throw new Error('A Stückliste article cannot become a parent'); } if (currentArt?.optionslist_id != null) { - throw new Error('An article with options cannot become a father'); + throw new Error('An article with options cannot become a parent'); } const stockRes = await runner.query( @@ -70,7 +70,7 @@ export default function toggleFather(provider) { ); const totalStock = Number(stockRes.rows[0]?.totalStock ?? 0); if (totalStock !== 0) { - throw new Error('An article with existing stock cannot become a father article'); + throw new Error('An article with existing stock cannot become a parent article'); } const supRes = await runner.query( @@ -78,7 +78,7 @@ export default function toggleFather(provider) { { articleId }, ); if ((supRes.rows[0]?.count ?? 0) > 0) { - throw new Error('An article with assigned suppliers cannot become a father article'); + throw new Error('An article with assigned suppliers cannot become a parent article'); } // Fetch existing prices before deletion to record in articlehistory @@ -96,22 +96,22 @@ export default function toggleFather(provider) { await runner.query('DELETE FROM price WHERE article_id = @articleId', { articleId }); await runner.query('UPDATE article SET track_stock = 0 WHERE id = @articleId', { articleId }); - // If article is a child, verify attribute is not used by father or any father up the chain - let currentFatherId = currentArt?.fatherarticle_id; + // If article is a child, verify attribute is not used by parent or any parent up the chain + let currentParentId = currentArt?.parentarticle_id; const visited = new Set(); - while (currentFatherId != null && !visited.has(currentFatherId)) { - visited.add(currentFatherId); + while (currentParentId != null && !visited.has(currentParentId)) { + visited.add(currentParentId); const fRes = await runner.query( - 'SELECT a.fatherarticle_id, a.fatherattributename_id, an.name AS fatherAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.fatherattributename_id WHERE a.id = @currentFatherId', - { currentFatherId }, + 'SELECT a.parentarticle_id, a.parentattributename_id, an.name AS parentAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.parentattributename_id WHERE a.id = @currentParentId', + { currentParentId }, ); const fRow = fRes.rows[0]; if (!fRow) break; - if (fRow.fatherattributename_id != null && Number(fRow.fatherattributename_id) === Number(targetAttrId)) { - const attrName = fRow.fatherAttributeName || 'ancestor'; - throw new Error(`Attribute "${attrName}" is already used by an ancestor father article`); + if (fRow.parentattributename_id != null && Number(fRow.parentattributename_id) === Number(targetAttrId)) { + const attrName = fRow.parentAttributeName || 'ancestor'; + throw new Error(`Attribute "${attrName}" is already used by an ancestor parent article`); } - currentFatherId = fRow.fatherarticle_id; + currentParentId = fRow.parentarticle_id; } // Verify attribute exists and belongs to company @@ -132,16 +132,16 @@ export default function toggleFather(provider) { { articleId, attributeId: Number(targetAttrId) }, ); if (existingValRes.rows.length > 0) { - throw new Error('A father article cannot have attribute values for its variant attribute'); + throw new Error('A parent article cannot have attribute values for its variant attribute'); } - await runner.query(queries.TOGGLE_FATHER, { + await runner.query(queries.TOGGLE_PARENT, { articleId, - isFather: 1, - fatherAttributeNameId: Number(targetAttrId), + isParent: 1, + parentAttributeNameId: Number(targetAttrId), }); - // Add history entry recording father conversion and deleted prices + // Add history entry recording parent conversion and deleted prices const historyLines = [ `Artikel zu Vaterartikel konvertiert (Varianten-Attribut: "${attributeName}")`, ]; @@ -170,17 +170,17 @@ export default function toggleFather(provider) { text: historyLines.join('\n'), }); } else { - // Disabling father: verify zero children + // Disabling parent: 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'); + throw new Error('Cannot disable parent mode on an article that still has children'); } - await runner.query(queries.TOGGLE_FATHER, { + await runner.query(queries.TOGGLE_PARENT, { articleId, - isFather: 0, - fatherAttributeNameId: null, + isParent: 0, + parentAttributeNameId: null, }); await runner.query(insertHistorySql, { diff --git a/src/store/articleattributevalue/setByArticleSku.js b/src/store/articleattributevalue/setByArticleSku.js index cb66a2e..5b318fa 100644 --- a/src/store/articleattributevalue/setByArticleSku.js +++ b/src/store/articleattributevalue/setByArticleSku.js @@ -123,39 +123,39 @@ export default function setByArticleSku(provider) { }); } - // Check father / child constraints + // Check parent / child constraints const artRes = await runner.query( - 'SELECT id, is_father, fatherarticle_id, fatherattributename_id FROM article WHERE id = @articleId', + 'SELECT id, is_parent, parentarticle_id, parentattributename_id FROM article WHERE id = @articleId', { articleId }, ); const article = artRes.rows[0]; - if (article?.is_father && article.fatherattributename_id) { - const hasFatherAttr = validated.some((v) => v.attributeId === article.fatherattributename_id); - if (hasFatherAttr) { - throw new Error('A father article cannot have attribute values for its variant attribute'); + if (article?.is_parent && article.parentattributename_id) { + const hasParentAttr = validated.some((v) => v.attributeId === article.parentattributename_id); + if (hasParentAttr) { + throw new Error('A parent article cannot have attribute values for its variant attribute'); } } - if (article?.fatherarticle_id != null) { - let currentFatherId = article.fatherarticle_id; + if (article?.parentarticle_id != null) { + let currentParentId = article.parentarticle_id; const visited = new Set(); - while (currentFatherId != null && !visited.has(currentFatherId)) { - visited.add(currentFatherId); - const fatherRes = await runner.query( - 'SELECT a.fatherarticle_id, a.fatherattributename_id, an.name AS fatherAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.fatherattributename_id WHERE a.id = @currentFatherId', - { currentFatherId }, + while (currentParentId != null && !visited.has(currentParentId)) { + visited.add(currentParentId); + const parentRes = await runner.query( + 'SELECT a.parentarticle_id, a.parentattributename_id, an.name AS parentAttributeName FROM article a LEFT JOIN attributename an ON an.id = a.parentattributename_id WHERE a.id = @currentParentId', + { currentParentId }, ); - const fRow = fatherRes.rows[0]; + const fRow = parentRes.rows[0]; if (!fRow) break; - if (fRow.fatherattributename_id != null) { - const hasFatherAttr = validated.some((v) => v.attributeId === fRow.fatherattributename_id); - if (!hasFatherAttr) { - const fatherAttrName = fRow.fatherAttributeName || 'ancestor variant attribute'; - throw new Error(`Child article must have an attribute value for the father attribute "${fatherAttrName}"`); + if (fRow.parentattributename_id != null) { + const hasParentAttr = validated.some((v) => v.attributeId === fRow.parentattributename_id); + if (!hasParentAttr) { + const parentAttrName = fRow.parentAttributeName || 'ancestor variant attribute'; + throw new Error(`Child article must have an attribute value for the parent attribute "${parentAttrName}"`); } } - currentFatherId = fRow.fatherarticle_id; + currentParentId = fRow.parentarticle_id; } } diff --git a/src/store/articlelist/setItemsByArticleSku.js b/src/store/articlelist/setItemsByArticleSku.js index 368a67a..b5bffbd 100644 --- a/src/store/articlelist/setItemsByArticleSku.js +++ b/src/store/articlelist/setItemsByArticleSku.js @@ -64,7 +64,7 @@ export default function setItemsByArticleSku(provider) { const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); const artRes = await runner.query( - 'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId', + 'SELECT id, is_parent, articlelist_id, optionslist_id FROM article WHERE id = @articleId', { articleId }, ); const article = artRes.rows[0]; @@ -72,8 +72,8 @@ export default function setItemsByArticleSku(provider) { throw new Error('Article not found'); } - if (article.is_father) { - throw new Error('A father article cannot be a Stückliste'); + if (article.is_parent) { + throw new Error('A parent article cannot be a Stückliste'); } // If items is empty: clear Stückliste diff --git a/src/store/articlesupplier/create.js b/src/store/articlesupplier/create.js index ff78a50..dfd0e2d 100644 --- a/src/store/articlesupplier/create.js +++ b/src/store/articlesupplier/create.js @@ -91,9 +91,9 @@ export default function create(provider) { runner, ); - const artCheck = await runner.query('SELECT is_father FROM article WHERE id = @articleId', { articleId }); - if (artCheck.rows[0]?.is_father) { - throw new Error('A father article cannot have suppliers'); + const artCheck = await runner.query('SELECT is_parent FROM article WHERE id = @articleId', { articleId }); + if (artCheck.rows[0]?.is_parent) { + throw new Error('A parent article cannot have suppliers'); } await assertSupplier(runner, companyId, entityId); diff --git a/src/store/category/getCategoryArticles.js b/src/store/category/getCategoryArticles.js index cdeb0f1..af30daa 100644 --- a/src/store/category/getCategoryArticles.js +++ b/src/store/category/getCategoryArticles.js @@ -11,7 +11,7 @@ export default function getCategoryArticles(provider) { * @param {number} googleAuthId - The id of the Google auth user. * @param {number} categoryId - The id of the category. * @param {object} [tx] - Optional transaction to run within. - * @returns {Promise>} The articles in the category. + * @returns {Promise>} The articles in the category. * * @example * const articles = await store.category.getCategoryArticles(42, 5); diff --git a/src/store/category/queryCategoryArticles.js b/src/store/category/queryCategoryArticles.js index 951bc1b..dc0aff5 100644 --- a/src/store/category/queryCategoryArticles.js +++ b/src/store/category/queryCategoryArticles.js @@ -26,8 +26,8 @@ export default function queryCategoryArticles(provider) { ...rest, track_stock: Boolean(rest.track_stock ?? 1), // MSSQL returns BIT as boolean, SQLite as 0/1 integer. Normalize so both - // dialects expose isFather as a real boolean, matching article/getBySku. - isFather: Boolean(rest.isFather), + // dialects expose isParent as a real boolean, matching article/getBySku. + isParent: Boolean(rest.isParent), brand: brandName ? { id: brandId ?? null, diff --git a/src/store/manifestLines.js b/src/store/manifestLines.js index 77a311a..2655e5c 100644 --- a/src/store/manifestLines.js +++ b/src/store/manifestLines.js @@ -1,9 +1,9 @@ import { - CHECK_ARTICLE_IS_FATHER as MSSQL_CHECK_ARTICLE_IS_FATHER, + CHECK_ARTICLE_IS_PARENT as MSSQL_CHECK_ARTICLE_IS_PARENT, CREATE_LINE as MSSQL_CREATE_LINE, } from '../providers/mssql/queries/manifestDoc.js'; import { - CHECK_ARTICLE_IS_FATHER as SQLITE_CHECK_ARTICLE_IS_FATHER, + CHECK_ARTICLE_IS_PARENT as SQLITE_CHECK_ARTICLE_IS_PARENT, CREATE_LINE as SQLITE_CREATE_LINE, } from '../providers/sqlite3/queries/manifestDoc.js'; import makeRunner from './runner.js'; @@ -122,20 +122,20 @@ export async function insertLines( const { manifestId, lines, ensureTax } = params; const isSqlite = runner?.dialect === 'sqlite3'; const createLineSql = isSqlite ? SQLITE_CREATE_LINE : MSSQL_CREATE_LINE; - const checkFatherSql = isSqlite ? SQLITE_CHECK_ARTICLE_IS_FATHER : MSSQL_CHECK_ARTICLE_IS_FATHER; + const checkParentSql = isSqlite ? SQLITE_CHECK_ARTICLE_IS_PARENT : MSSQL_CHECK_ARTICLE_IS_PARENT; for (const line of lines) { let resolvedName = line.name; let resolvedPu = line.pu; if (line.sku) { - const fatherCheck = await runner.query(checkFatherSql, { + const parentCheck = await runner.query(checkParentSql, { manifestId, sku: line.sku, }); - const art = fatherCheck.rows[0]; - if (art?.isFather) { - throw new Error(`A father article cannot be booked on documents (${line.sku})`); + const art = parentCheck.rows[0]; + if (art?.isParent) { + throw new Error(`A parent article cannot be booked on documents (${line.sku})`); } if (!resolvedName && art?.articleName) { resolvedName = art.articleName; diff --git a/src/store/optionslist/setOptionsByArticleSku.js b/src/store/optionslist/setOptionsByArticleSku.js index 1fda659..cd3b47d 100644 --- a/src/store/optionslist/setOptionsByArticleSku.js +++ b/src/store/optionslist/setOptionsByArticleSku.js @@ -64,7 +64,7 @@ export default function setOptionsByArticleSku(provider) { const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); const artRes = await runner.query( - 'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId', + 'SELECT id, is_parent, articlelist_id, optionslist_id FROM article WHERE id = @articleId', { articleId }, ); const article = artRes.rows[0]; @@ -72,8 +72,8 @@ export default function setOptionsByArticleSku(provider) { throw new Error('Article not found'); } - if (article.is_father) { - throw new Error('A father article cannot have options'); + if (article.is_parent) { + throw new Error('A parent article cannot have options'); } // If options is empty: clear optionslist diff --git a/src/store/stock/adjust.js b/src/store/stock/adjust.js index 9c01338..ae1d1cb 100644 --- a/src/store/stock/adjust.js +++ b/src/store/stock/adjust.js @@ -116,11 +116,11 @@ export default function adjust(provider) { const resolvedArticles = []; for (const item of normalizedLines) { const resolved = await resolveArticle(googleAuthId, item.sku, activeTx); - const artRes = await runner.query('SELECT is_father, articlelist_id, track_stock FROM article WHERE id = @articleId', { + const artRes = await runner.query('SELECT is_parent, articlelist_id, track_stock FROM article WHERE id = @articleId', { articleId: resolved.articleId, }); - if (artRes.rows[0]?.is_father) { - throw new Error(`A father article cannot have stock (${item.sku})`); + if (artRes.rows[0]?.is_parent) { + throw new Error(`A parent article cannot have stock (${item.sku})`); } if (artRes.rows[0]?.articlelist_id != null) {