Compare commits
2 Commits
74ed74fc68
...
62c3e69ed4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62c3e69ed4 | ||
|
|
9c770d765e |
@@ -85,7 +85,7 @@ const article = await store.article.createUnderParent({ googleAuthId: 42, parent
|
|||||||
|
|
||||||
*Source: `src/store/article/delete.js`*
|
*Source: `src/store/article/delete.js`*
|
||||||
|
|
||||||
Deletes an article by its SKU, refusing when it still has children.
|
Deletes an article by its SKU, refusing when it still has children unless cascade is true.
|
||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
@@ -93,7 +93,8 @@ Deletes an article by its SKU, refusing when it still has children.
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||||
| `sku` | `string` | The article SKU. |
|
| `sku` | `string` | The article SKU. |
|
||||||
| `[tx]` | `object` | Optional transaction to run within. |
|
| `[optionsOrTx]` | `object|*` | Options object ({ cascade?: boolean }) or optional transaction. |
|
||||||
|
| `[maybeTx]` | `object` | Optional transaction if options was passed. |
|
||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|
||||||
@@ -101,12 +102,13 @@ Deletes an article by its SKU, refusing when it still has children.
|
|||||||
|
|
||||||
**Throws**
|
**Throws**
|
||||||
|
|
||||||
`Error` — If the article has children or is not found.
|
`Error` — If the article has children (when cascade=false) or is not found.
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await store.article.delete(42, 'SKU-1');
|
await store.article.delete(42, 'SKU-1');
|
||||||
|
await store.article.delete(42, 'SKU-1', { cascade: true });
|
||||||
```
|
```
|
||||||
|
|
||||||
## `article.deleteImage`
|
## `article.deleteImage`
|
||||||
@@ -353,6 +355,7 @@ Removes a child article from a father article, either by unlinking or deleting i
|
|||||||
| `childSku` | `string` | The child article SKU. |
|
| `childSku` | `string` | The child article SKU. |
|
||||||
| `[options]` | `object` | |
|
| `[options]` | `object` | |
|
||||||
| `[options.deleteArticle=false]` | `boolean` | Whether to delete the child article entity entirely. |
|
| `[options.deleteArticle=false]` | `boolean` | Whether to delete the child article entity entirely. |
|
||||||
|
| `[options.cascade=true]` | `boolean` | Whether to cascade delete child subtrees when deleteArticle is true. |
|
||||||
| `[tx]` | `object` | Optional transaction. |
|
| `[tx]` | `object` | Optional transaction. |
|
||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ const suppliers = await store.articlesupplier.listByArticleSku(42, 'SKU-1');
|
|||||||
|
|
||||||
*Source: `src/store/articlesupplier/listByEntityAndSkus.js`*
|
*Source: `src/store/articlesupplier/listByEntityAndSkus.js`*
|
||||||
|
|
||||||
Lists article-supplier assignments for a given supplier (entityId) and SKU list, including linked article details (name, slug, photos/articlepic, eans/articleean).
|
Lists article-supplier assignments for a given supplier (entityId) and SKU list, including linked article details (name, slug, photos, eans).
|
||||||
|
|
||||||
**Parameters**
|
**Parameters**
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ Lists article-supplier assignments for a given supplier (entityId) and SKU list,
|
|||||||
|
|
||||||
**Returns**
|
**Returns**
|
||||||
|
|
||||||
`Promise<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, article: {id: number, sku: string, name: string|null, slug: string|null, photos: Array<{sort: number, thumbhash: string, hash: string, thumbw: number, thumbh: number}>, articlepic: Array<{sort: number, thumbhash: string, hash: string, thumbw: number, thumbh: number}>, eans: string[], articleean: string[]}}>>` — The supplier assignments with article details.
|
`Promise<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, article: {id: number, sku: string, name: string|null, slug: string|null, photos: Array<{sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[]}}>>` — The supplier assignments with article details.
|
||||||
|
|
||||||
**Throws**
|
**Throws**
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ Deletes a category, refusing to delete the company's root category.
|
|||||||
|
|
||||||
**Throws**
|
**Throws**
|
||||||
|
|
||||||
`Error` — If the category is the root category.
|
`Error` — If the category is the root category or contains articles/subcategories.
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
|
|||||||
6
API.md
6
API.md
@@ -86,7 +86,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `addChild(googleAuthId: number, fatherSku: string, options: {attributeValueId?: number, value?: string, name?: string, unitamount?: number, weight?: number, track_stock?: boolean|number}, tx?: object): Promise<object>`
|
- `addChild(googleAuthId: number, fatherSku: string, options: {attributeValueId?: number, value?: string, name?: string, unitamount?: number, weight?: number, track_stock?: boolean|number}, tx?: object): Promise<object>`
|
||||||
- `addImage(googleAuthId: number, sku: string, image: Buffer, tx?: object): Promise<{picId: number, sort: number, full: {w: number, h: number, size: number}, thumb: {w: number, h: number}, fullHash: string, thumbHash: string}>`
|
- `addImage(googleAuthId: number, sku: string, image: Buffer, tx?: object): Promise<{picId: number, sort: number, full: {w: number, h: number, size: number}, thumb: {w: number, h: number}, fullHash: string, thumbHash: string}>`
|
||||||
- `createUnderParent(input: {googleAuthId: number, parentId: number, name: string, track_stock?: boolean|number}, tx?: object): Promise<{id: number, parentId: number, name: string, sku: string, track_stock: boolean}>`
|
- `createUnderParent(input: {googleAuthId: number, parentId: number, name: string, track_stock?: boolean|number}, tx?: object): Promise<{id: number, parentId: number, name: string, sku: string, track_stock: boolean}>`
|
||||||
- `delete(googleAuthId: number, sku: string, tx?: object): Promise<void>`
|
- `delete(googleAuthId: number, sku: string, optionsOrTx?: object|*, maybeTx?: object): Promise<void>`
|
||||||
- `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise<void>`
|
- `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise<void>`
|
||||||
- `getBySku(googleAuthId: number, sku: string, tx?: object): Promise<{article: {id: number, sku: string, name: string, slug: string, unitamount: number|null, weight: number|null, 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, fatherArticleId: number|null, fatherSku: string|null}}>`
|
||||||
- `getHistoryBySku(googleAuthId: number, sku: string, tx?: object): Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>`
|
- `getHistoryBySku(googleAuthId: number, sku: string, tx?: object): Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>`
|
||||||
@@ -95,7 +95,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>`
|
- `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise<Array<object>>`
|
||||||
- `listSearchIndex(googleAuthId: number, tx?: object): Promise<Array<{id: number, sku: string, name: string, eans: Array<string>, categoryIds: Array<number>}>>`
|
- `listSearchIndex(googleAuthId: number, tx?: object): Promise<Array<{id: number, sku: string, name: string, eans: Array<string>, categoryIds: Array<number>}>>`
|
||||||
- `orderImages(googleAuthId: number, sku: string, input: {fullHashes: Array<string>}, tx?: object): Promise<{fullHashes: Array<string>}>`
|
- `orderImages(googleAuthId: number, sku: string, input: {fullHashes: Array<string>}, tx?: object): Promise<{fullHashes: Array<string>}>`
|
||||||
- `removeChild(googleAuthId: number, fatherSku: string, childSku: string, options?: {deleteArticle?: boolean}, tx?: object): Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>`
|
- `removeChild(googleAuthId: number, fatherSku: string, childSku: string, options?: {deleteArticle?: boolean, cascade?: boolean}, tx?: object): Promise<{success: boolean, fatherSku: string, childSku: string, deleted: boolean}>`
|
||||||
- `setBrandIdBySku(googleAuthId: number, sku: string, brandId: number|null, tx?: object): Promise<object>`
|
- `setBrandIdBySku(googleAuthId: number, sku: string, brandId: number|null, tx?: object): Promise<object>`
|
||||||
- `setCategoryIdsBySku(googleAuthId: number, sku: string, categoryIds: Array<number>, tx?: object): Promise<object>`
|
- `setCategoryIdsBySku(googleAuthId: number, sku: string, categoryIds: Array<number>, tx?: object): Promise<object>`
|
||||||
- `setEansBySku(googleAuthId: number, sku: string, eans: Array<string>, tx?: object): Promise<object>`
|
- `setEansBySku(googleAuthId: number, sku: string, eans: Array<string>, tx?: object): Promise<object>`
|
||||||
@@ -162,7 +162,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `create(googleAuthId: number, sku: string, input: {entityId: number, sku?: string, pu?: number, stock?: number|null, sort?: number, netPrice?: number|null, taxId?: number, taxRate?: number}, tx?: object): Promise<{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}>`
|
- `create(googleAuthId: number, sku: string, input: {entityId: number, sku?: string, pu?: number, stock?: number|null, sort?: number, netPrice?: number|null, taxId?: number, taxRate?: number}, tx?: object): Promise<{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}>`
|
||||||
- `delete(googleAuthId: number, sku: string, id: number, tx?: object): Promise<void>`
|
- `delete(googleAuthId: number, sku: string, id: number, tx?: object): Promise<void>`
|
||||||
- `listByArticleSku(googleAuthId: number, sku: string, tx?: object): Promise<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}>>`
|
- `listByArticleSku(googleAuthId: number, sku: string, tx?: object): Promise<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}>>`
|
||||||
- `listByEntityAndSkus(googleAuthId: number, entityIdOrOptions: number|{entityId?: number, supplierId?: number, skus?: string[]|string}, maybeSkus?: string[]|string, tx?: object): Promise<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, article: {id: number, sku: string, name: string|null, slug: string|null, photos: Array<{sort: number, thumbhash: string, hash: string, thumbw: number, thumbh: number}>, articlepic: Array<{sort: number, thumbhash: string, hash: string, thumbw: number, thumbh: number}>, eans: string[], articleean: string[]}}>>`
|
- `listByEntityAndSkus(googleAuthId: number, entityIdOrOptions: number|{entityId?: number, supplierId?: number, skus?: string[]|string}, maybeSkus?: string[]|string, tx?: object): Promise<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, article: {id: number, sku: string, name: string|null, slug: string|null, photos: Array<{sort: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[]}}>>`
|
||||||
- `order(googleAuthId: number, sku: string, input: {ids: Array<number>}, tx?: object): Promise<{ids: Array<number>}>`
|
- `order(googleAuthId: number, sku: string, input: {ids: Array<number>}, tx?: object): Promise<{ids: Array<number>}>`
|
||||||
- `update(googleAuthId: number, sku: string, id: number, patch: {sku?: string|null, pu?: number, stock?: number|null, sort?: number, netPrice?: number|null, taxId?: number, taxRate?: number}, tx?: object): Promise<{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}>`
|
- `update(googleAuthId: number, sku: string, id: number, patch: {sku?: string|null, pu?: number, stock?: number|null, sort?: number, netPrice?: number|null, taxId?: number, taxRate?: number}, tx?: object): Promise<{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}>`
|
||||||
- [delivery](API.delivery.md)
|
- [delivery](API.delivery.md)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
GET_BRAND_BY_COMPANY_AND_ID as SQLITE_GET_BRAND,
|
GET_BRAND_BY_COMPANY_AND_ID as SQLITE_GET_BRAND,
|
||||||
UPDATE_BRAND as SQLITE_UPDATE_BRAND,
|
UPDATE_BRAND as SQLITE_UPDATE_BRAND,
|
||||||
} from '../../providers/sqlite3/queries/article.js';
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -17,6 +19,7 @@ export default function setBrandIdBySku(provider) {
|
|||||||
|
|
||||||
const getBrandSql = isSqlite ? SQLITE_GET_BRAND : MSSQL_GET_BRAND;
|
const getBrandSql = isSqlite ? SQLITE_GET_BRAND : MSSQL_GET_BRAND;
|
||||||
const updateBrandSql = isSqlite ? SQLITE_UPDATE_BRAND : MSSQL_UPDATE_BRAND;
|
const updateBrandSql = isSqlite ? SQLITE_UPDATE_BRAND : MSSQL_UPDATE_BRAND;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets (or clears) the brand of an article by its SKU.
|
* Sets (or clears) the brand of an article by its SKU.
|
||||||
@@ -34,7 +37,7 @@ export default function setBrandIdBySku(provider) {
|
|||||||
*/
|
*/
|
||||||
return async (googleAuthId, sku, brandId, tx) => {
|
return async (googleAuthId, sku, brandId, tx) => {
|
||||||
const runner = makeRunner(provider, tx);
|
const runner = makeRunner(provider, tx);
|
||||||
const { articleId, languageId, companyId } = await resolve(
|
const { articleId, languageId, companyId, companyuserId } = await resolve(
|
||||||
googleAuthId,
|
googleAuthId,
|
||||||
sku,
|
sku,
|
||||||
runner,
|
runner,
|
||||||
@@ -44,7 +47,15 @@ export default function setBrandIdBySku(provider) {
|
|||||||
throw new Error('Invalid brandId');
|
throw new Error('Invalid brandId');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read old brand for history
|
||||||
|
const oldBrandRes = await runner.query(
|
||||||
|
`SELECT b.name FROM article a LEFT JOIN brand b ON b.id = a.brand_id WHERE a.id = @articleId`,
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const oldBrandName = oldBrandRes.rows[0]?.name ?? '';
|
||||||
|
|
||||||
let resolvedBrandId = null;
|
let resolvedBrandId = null;
|
||||||
|
let newBrandName = '';
|
||||||
if (brandId !== null) {
|
if (brandId !== null) {
|
||||||
const brand = await runner.query(getBrandSql, {
|
const brand = await runner.query(getBrandSql, {
|
||||||
brandId,
|
brandId,
|
||||||
@@ -54,6 +65,7 @@ export default function setBrandIdBySku(provider) {
|
|||||||
throw new Error('Brand not found');
|
throw new Error('Brand not found');
|
||||||
}
|
}
|
||||||
resolvedBrandId = brandId;
|
resolvedBrandId = brandId;
|
||||||
|
newBrandName = brand.rows[0].name ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await runner.query(updateBrandSql, {
|
const result = await runner.query(updateBrandSql, {
|
||||||
@@ -65,6 +77,15 @@ export default function setBrandIdBySku(provider) {
|
|||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if brand changed
|
||||||
|
if (oldBrandName !== newBrandName) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Marke geändert von "${oldBrandName}" auf "${newBrandName}"`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
ADD_CATEGORY as SQLITE_ADD_CATEGORY,
|
ADD_CATEGORY as SQLITE_ADD_CATEGORY,
|
||||||
DELETE_CATEGORIES_BY_ARTICLE as SQLITE_DELETE_CATEGORIES,
|
DELETE_CATEGORIES_BY_ARTICLE as SQLITE_DELETE_CATEGORIES,
|
||||||
} from '../../providers/sqlite3/queries/article.js';
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
|
import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
@@ -19,6 +21,7 @@ export default function setCategoryIdsBySku(provider) {
|
|||||||
|
|
||||||
const addCategorySql = isSqlite ? SQLITE_ADD_CATEGORY : MSSQL_ADD_CATEGORY;
|
const addCategorySql = isSqlite ? SQLITE_ADD_CATEGORY : MSSQL_ADD_CATEGORY;
|
||||||
const deleteCategoriesSql = isSqlite ? SQLITE_DELETE_CATEGORIES : MSSQL_DELETE_CATEGORIES;
|
const deleteCategoriesSql = isSqlite ? SQLITE_DELETE_CATEGORIES : MSSQL_DELETE_CATEGORIES;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the category membership of an article by its SKU.
|
* Replaces the category membership of an article by its SKU.
|
||||||
@@ -52,12 +55,19 @@ export default function setCategoryIdsBySku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runSet = async (runner) => {
|
const runSet = async (runner) => {
|
||||||
const { articleId, languageId, companyId } = await resolve(
|
const { articleId, languageId, companyId, companyuserId } = await resolve(
|
||||||
googleAuthId,
|
googleAuthId,
|
||||||
sku,
|
sku,
|
||||||
runner,
|
runner,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Read old category IDs for history
|
||||||
|
const oldCatRes = await runner.query(
|
||||||
|
'SELECT category_id FROM categoryarticle WHERE article_id = @articleId ORDER BY category_id',
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const oldCategoryIds = oldCatRes.rows.map((r) => r.category_id);
|
||||||
|
|
||||||
for (const categoryId of normalized) {
|
for (const categoryId of normalized) {
|
||||||
// Ensures the category belongs to the same company as the article.
|
// Ensures the category belongs to the same company as the article.
|
||||||
const access = await categoryAccess(googleAuthId, categoryId, runner);
|
const access = await categoryAccess(googleAuthId, categoryId, runner);
|
||||||
@@ -75,6 +85,17 @@ export default function setCategoryIdsBySku(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if categories changed
|
||||||
|
const oldStr = oldCategoryIds.join(', ');
|
||||||
|
const newStr = [...normalized].sort((a, b) => a - b).join(', ');
|
||||||
|
if (oldStr !== newStr) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Kategorien geändert von [${oldStr}] auf [${newStr}]`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
ADD_EAN as SQLITE_ADD_EAN,
|
ADD_EAN as SQLITE_ADD_EAN,
|
||||||
DELETE_EANS_BY_ARTICLE as SQLITE_DELETE_EANS,
|
DELETE_EANS_BY_ARTICLE as SQLITE_DELETE_EANS,
|
||||||
} from '../../providers/sqlite3/queries/article.js';
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import { isUniqueConstraintError } from '../../providers/provider.js';
|
import { isUniqueConstraintError } from '../../providers/provider.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
@@ -18,6 +20,7 @@ export default function setEansBySku(provider) {
|
|||||||
|
|
||||||
const addEanSql = isSqlite ? SQLITE_ADD_EAN : MSSQL_ADD_EAN;
|
const addEanSql = isSqlite ? SQLITE_ADD_EAN : MSSQL_ADD_EAN;
|
||||||
const deleteEansSql = isSqlite ? SQLITE_DELETE_EANS : MSSQL_DELETE_EANS;
|
const deleteEansSql = isSqlite ? SQLITE_DELETE_EANS : MSSQL_DELETE_EANS;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the EAN list of an article by its SKU.
|
* Replaces the EAN list of an article by its SKU.
|
||||||
@@ -52,7 +55,14 @@ export default function setEansBySku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runSet = async (runner) => {
|
const runSet = async (runner) => {
|
||||||
const { articleId, languageId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, languageId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
|
// Read old EANs for history
|
||||||
|
const oldEanRes = await runner.query(
|
||||||
|
'SELECT ean FROM articleean WHERE article_id = @articleId ORDER BY ean',
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const oldEans = oldEanRes.rows.map((r) => r.ean);
|
||||||
|
|
||||||
await runner.query(deleteEansSql, { articleId });
|
await runner.query(deleteEansSql, { articleId });
|
||||||
|
|
||||||
@@ -67,6 +77,17 @@ export default function setEansBySku(provider) {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if EANs changed
|
||||||
|
const oldStr = oldEans.join(', ');
|
||||||
|
const newStr = [...normalized].sort().join(', ');
|
||||||
|
if (oldStr !== newStr) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `EANs geändert von "${oldStr}" auf "${newStr}"`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
CREATE as SQLITE_CREATE_PRICE,
|
CREATE as SQLITE_CREATE_PRICE,
|
||||||
DELETE_BY_ARTICLE_AND_CUSTOMERGROUP as SQLITE_DELETE_PRICE,
|
DELETE_BY_ARTICLE_AND_CUSTOMERGROUP as SQLITE_DELETE_PRICE,
|
||||||
} from '../../providers/sqlite3/queries/price.js';
|
} from '../../providers/sqlite3/queries/price.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import taxEnsure from '../tax/ensure.js';
|
import taxEnsure from '../tax/ensure.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
@@ -55,6 +57,7 @@ export default function setPricesBySku(provider) {
|
|||||||
|
|
||||||
const createPriceSql = isSqlite ? SQLITE_CREATE_PRICE : MSSQL_CREATE_PRICE;
|
const createPriceSql = isSqlite ? SQLITE_CREATE_PRICE : MSSQL_CREATE_PRICE;
|
||||||
const deletePriceSql = isSqlite ? SQLITE_DELETE_PRICE : MSSQL_DELETE_PRICE;
|
const deletePriceSql = isSqlite ? SQLITE_DELETE_PRICE : MSSQL_DELETE_PRICE;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the price list of an article by its SKU.
|
* Replaces the price list of an article by its SKU.
|
||||||
@@ -74,7 +77,7 @@ export default function setPricesBySku(provider) {
|
|||||||
const normalized = normalizePrices(prices);
|
const normalized = normalizePrices(prices);
|
||||||
|
|
||||||
const runSet = async (runner) => {
|
const runSet = async (runner) => {
|
||||||
const { articleId, languageId, posCustomerGroupId } = await resolve(
|
const { articleId, languageId, posCustomerGroupId, companyuserId } = await resolve(
|
||||||
googleAuthId,
|
googleAuthId,
|
||||||
sku,
|
sku,
|
||||||
runner,
|
runner,
|
||||||
@@ -88,6 +91,17 @@ export default function setPricesBySku(provider) {
|
|||||||
throw new Error('A father article cannot have prices');
|
throw new Error('A father article cannot have prices');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read old prices for history
|
||||||
|
const oldPriceRes = await runner.query(
|
||||||
|
`SELECT p.net, t.rate AS taxRate, p.minmultiplier
|
||||||
|
FROM price p
|
||||||
|
LEFT JOIN tax t ON t.id = p.tax_id
|
||||||
|
WHERE p.article_id = @articleId AND p.customergroup_id = @posCustomerGroupId
|
||||||
|
ORDER BY p.minmultiplier ASC`,
|
||||||
|
{ articleId, posCustomerGroupId },
|
||||||
|
);
|
||||||
|
const oldPrices = oldPriceRes.rows;
|
||||||
|
|
||||||
const taxIds = new Map();
|
const taxIds = new Map();
|
||||||
for (const entry of normalized) {
|
for (const entry of normalized) {
|
||||||
if (!taxIds.has(entry.tax)) {
|
if (!taxIds.has(entry.tax)) {
|
||||||
@@ -110,6 +124,18 @@ export default function setPricesBySku(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if prices changed
|
||||||
|
const fmtPrice = (p) => `${p.net}@${p.taxRate ?? 0}%`;
|
||||||
|
const oldStr = oldPrices.map(fmtPrice).join(', ');
|
||||||
|
const newStr = normalized.map((e) => `${e.price}@${e.tax}%`).join(', ');
|
||||||
|
if (oldStr !== newStr) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Preise geändert von [${oldStr}] auf [${newStr}]`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { UPDATE_TRACK_STOCK as MSSQL_UPDATE_TRACK_STOCK } from '../../providers/mssql/queries/article.js';
|
import { UPDATE_TRACK_STOCK as MSSQL_UPDATE_TRACK_STOCK } from '../../providers/mssql/queries/article.js';
|
||||||
import { UPDATE_TRACK_STOCK as SQLITE_UPDATE_TRACK_STOCK } from '../../providers/sqlite3/queries/article.js';
|
import { UPDATE_TRACK_STOCK as SQLITE_UPDATE_TRACK_STOCK } from '../../providers/sqlite3/queries/article.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -11,6 +13,7 @@ export default function setTrackStockBySku(provider) {
|
|||||||
provider?.dialect === 'sqlite3'
|
provider?.dialect === 'sqlite3'
|
||||||
? SQLITE_UPDATE_TRACK_STOCK
|
? SQLITE_UPDATE_TRACK_STOCK
|
||||||
: MSSQL_UPDATE_TRACK_STOCK;
|
: MSSQL_UPDATE_TRACK_STOCK;
|
||||||
|
const insertHistorySql = provider?.dialect === 'sqlite3' ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the track_stock flag of an article by its SKU.
|
* Sets the track_stock flag of an article by its SKU.
|
||||||
@@ -32,7 +35,15 @@ export default function setTrackStockBySku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runner = makeRunner(provider, tx);
|
const runner = makeRunner(provider, tx);
|
||||||
const { articleId, languageId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, languageId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
|
// Read old track_stock for history
|
||||||
|
const oldRes = await runner.query(
|
||||||
|
'SELECT track_stock FROM article WHERE id = @articleId',
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const oldTrack = Boolean(oldRes.rows[0]?.track_stock ?? 1);
|
||||||
|
const newTrack = Boolean(trackStock);
|
||||||
|
|
||||||
const result = await runner.query(sql, {
|
const result = await runner.query(sql, {
|
||||||
articleId,
|
articleId,
|
||||||
@@ -43,6 +54,15 @@ export default function setTrackStockBySku(provider) {
|
|||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if track_stock changed
|
||||||
|
if (oldTrack !== newTrack) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Bestandsführung geändert von "${oldTrack ? 'Ja' : 'Nein'}" auf "${newTrack ? 'Ja' : 'Nein'}"`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { UPDATE_WEIGHT as MSSQL_UPDATE_WEIGHT } from '../../providers/mssql/queries/article.js';
|
import { UPDATE_WEIGHT as MSSQL_UPDATE_WEIGHT } from '../../providers/mssql/queries/article.js';
|
||||||
import { UPDATE_WEIGHT as SQLITE_UPDATE_WEIGHT } from '../../providers/sqlite3/queries/article.js';
|
import { UPDATE_WEIGHT as SQLITE_UPDATE_WEIGHT } from '../../providers/sqlite3/queries/article.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -8,6 +10,7 @@ export default function setWeightBySku(provider) {
|
|||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
const load = queryBySku(provider);
|
const load = queryBySku(provider);
|
||||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_UPDATE_WEIGHT : MSSQL_UPDATE_WEIGHT;
|
const sql = provider?.dialect === 'sqlite3' ? SQLITE_UPDATE_WEIGHT : MSSQL_UPDATE_WEIGHT;
|
||||||
|
const insertHistorySql = provider?.dialect === 'sqlite3' ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the weight of an article by its SKU.
|
* Sets the weight of an article by its SKU.
|
||||||
@@ -29,7 +32,14 @@ export default function setWeightBySku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runner = makeRunner(provider, tx);
|
const runner = makeRunner(provider, tx);
|
||||||
const { articleId, languageId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, languageId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
|
// Read old weight for history
|
||||||
|
const oldRes = await runner.query(
|
||||||
|
'SELECT weight FROM article WHERE id = @articleId',
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const oldWeight = oldRes.rows[0]?.weight ?? 0;
|
||||||
|
|
||||||
const result = await runner.query(sql, {
|
const result = await runner.query(sql, {
|
||||||
articleId,
|
articleId,
|
||||||
@@ -40,6 +50,15 @@ export default function setWeightBySku(provider) {
|
|||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if weight changed
|
||||||
|
if (Number(oldWeight) !== weight) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Gewicht geändert von "${oldWeight}" auf "${weight}"`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { GET_CHILD_COUNT as MSSQL_GET_CHILD_COUNT } from '../../providers/mssql/
|
|||||||
import { GET_CHILD_COUNT as SQLITE_GET_CHILD_COUNT } from '../../providers/sqlite3/queries/articleDelete.js';
|
import { GET_CHILD_COUNT as SQLITE_GET_CHILD_COUNT } from '../../providers/sqlite3/queries/articleDelete.js';
|
||||||
import * as mssqlAttr from '../../providers/mssql/queries/attributename.js';
|
import * as mssqlAttr from '../../providers/mssql/queries/attributename.js';
|
||||||
import * as sqliteAttr from '../../providers/sqlite3/queries/attributename.js';
|
import * as sqliteAttr from '../../providers/sqlite3/queries/attributename.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
import getBySku from './getBySku.js';
|
import getBySku from './getBySku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -16,6 +18,7 @@ export default function toggleFather(provider) {
|
|||||||
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
const queries = isSqlite ? sqliteArticle : mssqlArticle;
|
||||||
const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT;
|
const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT;
|
||||||
const attrQueries = isSqlite ? sqliteAttr : mssqlAttr;
|
const attrQueries = isSqlite ? sqliteAttr : mssqlAttr;
|
||||||
|
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 father mode.
|
||||||
@@ -31,7 +34,7 @@ export default function toggleFather(provider) {
|
|||||||
*/
|
*/
|
||||||
return async (googleAuthId, sku, { isFather, attributeNameId, fatherAttributeNameId } = {}, tx) => {
|
return async (googleAuthId, sku, { isFather, attributeNameId, fatherAttributeNameId } = {}, tx) => {
|
||||||
const run = async (runner) => {
|
const run = async (runner) => {
|
||||||
const { articleId, companyId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
const targetIsFather = Boolean(isFather);
|
const targetIsFather = Boolean(isFather);
|
||||||
const targetAttrId = attributeNameId ?? fatherAttributeNameId;
|
const targetAttrId = attributeNameId ?? fatherAttributeNameId;
|
||||||
@@ -78,6 +81,18 @@ export default function toggleFather(provider) {
|
|||||||
throw new Error('An article with assigned suppliers cannot become a father article');
|
throw new Error('An article with assigned suppliers cannot become a father article');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch existing prices before deletion to record in articlehistory
|
||||||
|
const existingPricesRes = await runner.query(
|
||||||
|
`SELECT pr.net, pr.minmultiplier, t.rate AS taxRate, cg.name AS customerGroupName
|
||||||
|
FROM price pr
|
||||||
|
LEFT JOIN tax t ON t.id = pr.tax_id
|
||||||
|
LEFT JOIN customergroup cg ON cg.id = pr.customergroup_id
|
||||||
|
WHERE pr.article_id = @articleId
|
||||||
|
ORDER BY pr.customergroup_id, pr.minmultiplier`,
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const existingPrices = existingPricesRes.rows;
|
||||||
|
|
||||||
await runner.query('DELETE FROM price WHERE article_id = @articleId', { articleId });
|
await runner.query('DELETE FROM price WHERE article_id = @articleId', { articleId });
|
||||||
await runner.query('UPDATE article SET track_stock = 0 WHERE id = @articleId', { articleId });
|
await runner.query('UPDATE article SET track_stock = 0 WHERE id = @articleId', { articleId });
|
||||||
|
|
||||||
@@ -107,6 +122,7 @@ export default function toggleFather(provider) {
|
|||||||
if (!attrRes.rows[0]) {
|
if (!attrRes.rows[0]) {
|
||||||
throw new Error('Attribute name not found');
|
throw new Error('Attribute name not found');
|
||||||
}
|
}
|
||||||
|
const attributeName = attrRes.rows[0].name;
|
||||||
|
|
||||||
// Verify article does not have an attribute value for this attribute
|
// Verify article does not have an attribute value for this attribute
|
||||||
const existingValRes = await runner.query(
|
const existingValRes = await runner.query(
|
||||||
@@ -124,6 +140,35 @@ export default function toggleFather(provider) {
|
|||||||
isFather: 1,
|
isFather: 1,
|
||||||
fatherAttributeNameId: Number(targetAttrId),
|
fatherAttributeNameId: Number(targetAttrId),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add history entry recording father conversion and deleted prices
|
||||||
|
const historyLines = [
|
||||||
|
`Artikel zu Vaterartikel konvertiert (Varianten-Attribut: "${attributeName}")`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (existingPrices.length > 0) {
|
||||||
|
const priceParts = existingPrices.map((p) => {
|
||||||
|
let str = `${p.net} €`;
|
||||||
|
if (p.taxRate != null) {
|
||||||
|
str += ` (${p.taxRate}% MwSt.)`;
|
||||||
|
}
|
||||||
|
if (p.minmultiplier > 1) {
|
||||||
|
str += ` ab ${p.minmultiplier} Stk.`;
|
||||||
|
}
|
||||||
|
if (p.customerGroupName) {
|
||||||
|
str += ` [${p.customerGroupName}]`;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
});
|
||||||
|
const label = existingPrices.length === 1 ? 'Bisheriger Preis gelöscht' : 'Bisherige Preise gelöscht';
|
||||||
|
historyLines.push(`${label}: ${priceParts.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: historyLines.join('\n'),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Disabling father: verify zero children
|
// Disabling father: verify zero children
|
||||||
const childCountRes = await runner.query(getChildCountSql, { articleId });
|
const childCountRes = await runner.query(getChildCountSql, { articleId });
|
||||||
@@ -137,6 +182,12 @@ export default function toggleFather(provider) {
|
|||||||
isFather: 0,
|
isFather: 0,
|
||||||
fatherAttributeNameId: null,
|
fatherAttributeNameId: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: 'Vaterartikel-Modus deaktiviert',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return load(googleAuthId, sku, runner);
|
return load(googleAuthId, sku, runner);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
INSERT_ROW as SQLITE_INSERT,
|
INSERT_ROW as SQLITE_INSERT,
|
||||||
GET_VALUE_DETAIL as SQLITE_GET_DETAIL,
|
GET_VALUE_DETAIL as SQLITE_GET_DETAIL,
|
||||||
} from '../../providers/sqlite3/queries/articleattributevalue.js';
|
} from '../../providers/sqlite3/queries/articleattributevalue.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
@@ -17,6 +19,7 @@ export default function setByArticleSku(provider) {
|
|||||||
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
|
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
|
||||||
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
|
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
|
||||||
const getDetailSql = isSqlite ? SQLITE_GET_DETAIL : MSSQL_GET_DETAIL;
|
const getDetailSql = isSqlite ? SQLITE_GET_DETAIL : MSSQL_GET_DETAIL;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces all attribute values for an article by its SKU.
|
* Replaces all attribute values for an article by its SKU.
|
||||||
@@ -34,7 +37,19 @@ export default function setByArticleSku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runSet = async (runner) => {
|
const runSet = async (runner) => {
|
||||||
const { articleId, companyId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
|
// Read old attribute values for history
|
||||||
|
const oldAttrRes = await runner.query(
|
||||||
|
`SELECT aav.attributevalue_id AS attributeValueId, an.name AS attributeName, av.value
|
||||||
|
FROM articleattributevalue aav
|
||||||
|
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id
|
||||||
|
INNER JOIN attributename an ON an.id = av.attributename_id
|
||||||
|
WHERE aav.article_id = @articleId
|
||||||
|
ORDER BY aav.sort ASC, aav.id ASC`,
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const oldAttrs = oldAttrRes.rows;
|
||||||
|
|
||||||
const validated = [];
|
const validated = [];
|
||||||
const seenValues = new Set();
|
const seenValues = new Set();
|
||||||
@@ -137,6 +152,18 @@ export default function setByArticleSku(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if attribute values changed
|
||||||
|
const fmtAttr = (a) => `${a.attributeName}: ${a.value}`;
|
||||||
|
const oldStr = oldAttrs.map(fmtAttr).join(', ');
|
||||||
|
const newStr = validated.map(fmtAttr).join(', ');
|
||||||
|
if (oldStr !== newStr) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Attributwerte geändert von [${oldStr}] auf [${newStr}]`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return inserted;
|
return inserted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as mssqlQueries from '../../providers/mssql/queries/articlelist.js';
|
import * as mssqlQueries from '../../providers/mssql/queries/articlelist.js';
|
||||||
import * as sqliteQueries from '../../providers/sqlite3/queries/articlelist.js';
|
import * as sqliteQueries from '../../providers/sqlite3/queries/articlelist.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||||
import getItemsByArticleSku from './getItemsByArticleSku.js';
|
import getItemsByArticleSku from './getItemsByArticleSku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -42,6 +44,7 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
const getItems = getItemsByArticleSku(provider);
|
const getItems = getItemsByArticleSku(provider);
|
||||||
const isSqlite = provider?.dialect === 'sqlite3';
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets or clears the Stückliste (bundle items) for an article.
|
* Sets or clears the Stückliste (bundle items) for an article.
|
||||||
@@ -58,7 +61,7 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const run = async (runner) => {
|
const run = async (runner) => {
|
||||||
const { articleId, companyId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
const artRes = await runner.query(
|
const artRes = await runner.query(
|
||||||
'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||||
@@ -76,6 +79,18 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
// If items is empty: clear Stückliste
|
// If items is empty: clear Stückliste
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
if (article.articlelist_id != null) {
|
if (article.articlelist_id != null) {
|
||||||
|
// Read old items for history before clearing
|
||||||
|
const oldItemRes = await runner.query(
|
||||||
|
`SELECT a.sku, at.name, ala.count
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
INNER JOIN article a ON a.id = ala.article_id
|
||||||
|
LEFT JOIN articletext at ON at.article_id = a.id
|
||||||
|
WHERE ala.articlelist_id = @articlelistId
|
||||||
|
ORDER BY ala.sort ASC, ala.id ASC`,
|
||||||
|
{ articlelistId: article.articlelist_id },
|
||||||
|
);
|
||||||
|
const oldItemNames = oldItemRes.rows.map((r) => `${r.count}x ${r.name || r.sku}`);
|
||||||
|
|
||||||
await runner.query(queries.SET_ARTICLE_ARTICLELIST_ID, {
|
await runner.query(queries.SET_ARTICLE_ARTICLELIST_ID, {
|
||||||
articleId,
|
articleId,
|
||||||
articlelistId: null,
|
articlelistId: null,
|
||||||
@@ -83,6 +98,14 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
await runner.query(queries.DELETE_ARTICLELIST, {
|
await runner.query(queries.DELETE_ARTICLELIST, {
|
||||||
id: article.articlelist_id,
|
id: article.articlelist_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (oldItemNames.length) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Stückliste geleert (war: [${oldItemNames.join(', ')}])`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -94,17 +117,42 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
let itemArticleId = item?.articleId != null ? Number(item.articleId) : null;
|
if (!item || typeof item !== 'object') {
|
||||||
|
throw new Error(`Item at index ${i} must be an object`);
|
||||||
if (itemArticleId == null && item?.sku) {
|
|
||||||
const res = await runner.query(
|
|
||||||
'SELECT id FROM article WHERE sku = @itemSku AND company_id = @companyId',
|
|
||||||
{ itemSku: item.sku, companyId },
|
|
||||||
);
|
|
||||||
itemArticleId = res.rows[0]?.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itemArticleId || Number.isNaN(itemArticleId) || itemArticleId <= 0) {
|
let itemArticleId = null;
|
||||||
|
|
||||||
|
if (item.articleId !== undefined && item.articleId !== null) {
|
||||||
|
if (
|
||||||
|
typeof item.articleId !== 'number' ||
|
||||||
|
!Number.isInteger(item.articleId) ||
|
||||||
|
item.articleId <= 0
|
||||||
|
) {
|
||||||
|
throw new Error(`Item at index ${i} must specify a valid articleId or sku`);
|
||||||
|
}
|
||||||
|
const res = await runner.query(
|
||||||
|
'SELECT id FROM article WHERE id = @itemArticleId AND company_id = @companyId',
|
||||||
|
{ itemArticleId: item.articleId, companyId },
|
||||||
|
);
|
||||||
|
if (!res.rows[0]) {
|
||||||
|
throw new Error(`Article not found for component: ${item.articleId}`);
|
||||||
|
}
|
||||||
|
itemArticleId = res.rows[0].id;
|
||||||
|
} else if (item.sku !== undefined && item.sku !== null) {
|
||||||
|
if (typeof item.sku !== 'string' || !item.sku.trim()) {
|
||||||
|
throw new Error(`Item at index ${i} must specify a valid articleId or sku`);
|
||||||
|
}
|
||||||
|
const itemSku = item.sku.trim();
|
||||||
|
const res = await runner.query(
|
||||||
|
'SELECT id FROM article WHERE sku = @itemSku AND company_id = @companyId',
|
||||||
|
{ itemSku, companyId },
|
||||||
|
);
|
||||||
|
if (!res.rows[0]) {
|
||||||
|
throw new Error(`Article not found for component: ${itemSku}`);
|
||||||
|
}
|
||||||
|
itemArticleId = res.rows[0].id;
|
||||||
|
} else {
|
||||||
throw new Error(`Item at index ${i} must specify a valid articleId or sku`);
|
throw new Error(`Item at index ${i} must specify a valid articleId or sku`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,16 +165,7 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
}
|
}
|
||||||
seenArticleIds.add(itemArticleId);
|
seenArticleIds.add(itemArticleId);
|
||||||
|
|
||||||
// Verify component article exists in same company
|
const rawCount = item?.count === undefined ? 1 : item.count;
|
||||||
const itemArtRes = await runner.query(
|
|
||||||
'SELECT id FROM article WHERE id = @itemArticleId AND company_id = @companyId',
|
|
||||||
{ itemArticleId, companyId },
|
|
||||||
);
|
|
||||||
if (!itemArtRes.rows[0]) {
|
|
||||||
throw new Error(`Article not found for component: ${itemArticleId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawCount = item?.count;
|
|
||||||
if (
|
if (
|
||||||
typeof rawCount !== 'number' ||
|
typeof rawCount !== 'number' ||
|
||||||
!Number.isInteger(rawCount) ||
|
!Number.isInteger(rawCount) ||
|
||||||
@@ -136,7 +175,13 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
throw new Error(`Invalid count for Stückliste item at index ${i}: must be a positive integer`);
|
throw new Error(`Invalid count for Stückliste item at index ${i}: must be a positive integer`);
|
||||||
}
|
}
|
||||||
const count = rawCount;
|
const count = rawCount;
|
||||||
let sort = item?.sort != null && Number.isInteger(Number(item.sort)) ? Number(item.sort) : i;
|
|
||||||
|
if (item.sort !== undefined && item.sort !== null) {
|
||||||
|
if (typeof item.sort !== 'number' || !Number.isInteger(item.sort)) {
|
||||||
|
throw new Error(`Invalid sort for Stückliste item at index ${i}: must be an integer`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let sort = (typeof item?.sort === 'number' && Number.isInteger(item.sort)) ? item.sort : i;
|
||||||
|
|
||||||
while (seenSorts.has(sort)) {
|
while (seenSorts.has(sort)) {
|
||||||
sort++;
|
sort++;
|
||||||
@@ -173,7 +218,17 @@ export default function setItemsByArticleSku(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return getItems(googleAuthId, sku, runner);
|
const result = await getItems(googleAuthId, sku, runner);
|
||||||
|
|
||||||
|
// Write history for Stückliste change
|
||||||
|
const newNames = result.map((r) => `${r.count}x ${r.name || r.sku}`);
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Stückliste gesetzt: [${newNames.join(', ')}]`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tx) {
|
if (tx) {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP,
|
GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP,
|
||||||
} from '../../providers/sqlite3/queries/propertyname.js';
|
} from '../../providers/sqlite3/queries/propertyname.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ export default function setByArticleSku(provider) {
|
|||||||
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
|
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
|
||||||
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
|
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
|
||||||
const getPropSql = isSqlite ? SQLITE_GET_PROP : MSSQL_GET_PROP;
|
const getPropSql = isSqlite ? SQLITE_GET_PROP : MSSQL_GET_PROP;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces all properties for an article by its SKU.
|
* Replaces all properties for an article by its SKU.
|
||||||
@@ -38,7 +41,18 @@ export default function setByArticleSku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runSet = async (runner) => {
|
const runSet = async (runner) => {
|
||||||
const { articleId, companyId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
|
// Read old properties for history
|
||||||
|
const oldPropsRes = await runner.query(
|
||||||
|
`SELECT ap.property_id AS propertyId, pn.name, ap.value
|
||||||
|
FROM articleproperty ap
|
||||||
|
INNER JOIN propertyname pn ON pn.id = ap.property_id
|
||||||
|
WHERE ap.article_id = @articleId
|
||||||
|
ORDER BY ap.sort ASC, ap.id ASC`,
|
||||||
|
{ articleId },
|
||||||
|
);
|
||||||
|
const oldProps = oldPropsRes.rows;
|
||||||
|
|
||||||
const validated = [];
|
const validated = [];
|
||||||
const seenExclusiveProps = new Set();
|
const seenExclusiveProps = new Set();
|
||||||
@@ -97,6 +111,18 @@ export default function setByArticleSku(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write history if properties changed
|
||||||
|
const fmtProp = (p) => `${p.name}=${p.value}`;
|
||||||
|
const oldStr = oldProps.map(fmtProp).join(', ');
|
||||||
|
const newStr = validated.map(fmtProp).join(', ');
|
||||||
|
if (oldStr !== newStr) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Eigenschaften geändert von [${oldStr}] auf [${newStr}]`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return inserted;
|
return inserted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,18 +15,63 @@ export default function del(provider) {
|
|||||||
* @param {number} input.id - The id of the category to delete.
|
* @param {number} input.id - The id of the category to delete.
|
||||||
* @param {object} [tx] - Optional transaction to run within.
|
* @param {object} [tx] - Optional transaction to run within.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @throws {Error} If the category is the root category.
|
* @throws {Error} If the category is the root category or contains articles/subcategories.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* await store.category.delete({ googleAuthId: 42, id: 5 });
|
* await store.category.delete({ googleAuthId: 42, id: 5 });
|
||||||
*/
|
*/
|
||||||
return async ({ googleAuthId, id }, tx) => {
|
return async ({ googleAuthId, id }, tx) => {
|
||||||
const { rootCategoryId } = await access(googleAuthId, id, tx);
|
const run = async (runner) => {
|
||||||
|
const { rootCategoryId } = await access(googleAuthId, id, runner);
|
||||||
|
|
||||||
if (id === rootCategoryId) {
|
if (id === rootCategoryId) {
|
||||||
throw new Error('Cannot delete root category');
|
throw new Error('Cannot delete root category');
|
||||||
|
}
|
||||||
|
|
||||||
|
const childRes = await runner.query(
|
||||||
|
'SELECT id FROM category WHERE parent_id = @id',
|
||||||
|
{ id },
|
||||||
|
);
|
||||||
|
if (childRes.rows?.length > 0) {
|
||||||
|
throw new Error('Category has subcategories');
|
||||||
|
}
|
||||||
|
|
||||||
|
const artRes = await runner.query(
|
||||||
|
'SELECT article_id FROM categoryarticle WHERE category_id = @id',
|
||||||
|
{ id },
|
||||||
|
);
|
||||||
|
if (artRes.rows?.length > 0) {
|
||||||
|
throw new Error('Category has articles');
|
||||||
|
}
|
||||||
|
|
||||||
|
const posRes = await runner.query(
|
||||||
|
'SELECT posterminal_id FROM posterminalcategory WHERE category_id = @id',
|
||||||
|
{ id },
|
||||||
|
);
|
||||||
|
if (posRes.rows?.length > 0) {
|
||||||
|
throw new Error('Category is assigned to POS terminals');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runner.query(sql, { id });
|
||||||
|
} catch (err) {
|
||||||
|
if (
|
||||||
|
err.message?.includes('FOREIGN KEY constraint failed') ||
|
||||||
|
err.message?.includes('foreign key constraint') ||
|
||||||
|
err.message?.includes('REFERENCE constraint')
|
||||||
|
) {
|
||||||
|
throw new Error('Category has articles');
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
return run(makeRunner(provider, tx));
|
||||||
}
|
}
|
||||||
|
if (typeof provider?.transaction === 'function') {
|
||||||
await makeRunner(provider, tx).query(sql, { id });
|
return provider.transaction(run);
|
||||||
|
}
|
||||||
|
return run(makeRunner(provider));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as mssqlQueries from '../../providers/mssql/queries/optionslist.js';
|
import * as mssqlQueries from '../../providers/mssql/queries/optionslist.js';
|
||||||
import * as sqliteQueries from '../../providers/sqlite3/queries/optionslist.js';
|
import * as sqliteQueries from '../../providers/sqlite3/queries/optionslist.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||||
import getOptionsByArticleSku from './getOptionsByArticleSku.js';
|
import getOptionsByArticleSku from './getOptionsByArticleSku.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -42,6 +44,7 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
const getOptions = getOptionsByArticleSku(provider);
|
const getOptions = getOptionsByArticleSku(provider);
|
||||||
const isSqlite = provider?.dialect === 'sqlite3';
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets or clears the options list for an article.
|
* Sets or clears the options list for an article.
|
||||||
@@ -58,7 +61,7 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const run = async (runner) => {
|
const run = async (runner) => {
|
||||||
const { articleId, companyId } = await resolve(googleAuthId, sku, runner);
|
const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner);
|
||||||
|
|
||||||
const artRes = await runner.query(
|
const artRes = await runner.query(
|
||||||
'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId',
|
||||||
@@ -76,6 +79,18 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
// If options is empty: clear optionslist
|
// If options is empty: clear optionslist
|
||||||
if (options.length === 0) {
|
if (options.length === 0) {
|
||||||
if (article.optionslist_id != null) {
|
if (article.optionslist_id != null) {
|
||||||
|
// Read old options for history before clearing
|
||||||
|
const oldOptRes = await runner.query(
|
||||||
|
`SELECT a.sku, at.name
|
||||||
|
FROM optionslistarticle ola
|
||||||
|
INNER JOIN article a ON a.id = ola.article_id
|
||||||
|
LEFT JOIN articletext at ON at.article_id = a.id
|
||||||
|
WHERE ola.optionslist_id = @optionslistId
|
||||||
|
ORDER BY ola.sort ASC, ola.id ASC`,
|
||||||
|
{ optionslistId: article.optionslist_id },
|
||||||
|
);
|
||||||
|
const oldOptNames = oldOptRes.rows.map((r) => r.name || r.sku);
|
||||||
|
|
||||||
await runner.query(queries.SET_ARTICLE_OPTIONSLIST_ID, {
|
await runner.query(queries.SET_ARTICLE_OPTIONSLIST_ID, {
|
||||||
articleId,
|
articleId,
|
||||||
optionslistId: null,
|
optionslistId: null,
|
||||||
@@ -83,6 +98,14 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
await runner.query(queries.DELETE_OPTIONSLIST, {
|
await runner.query(queries.DELETE_OPTIONSLIST, {
|
||||||
id: article.optionslist_id,
|
id: article.optionslist_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (oldOptNames.length) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Optionsliste geleert (war: [${oldOptNames.join(', ')}])`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -94,17 +117,42 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
const item = options[i];
|
const item = options[i];
|
||||||
let itemArticleId = item?.articleId != null ? Number(item.articleId) : null;
|
if (!item || typeof item !== 'object') {
|
||||||
|
throw new Error(`Option at index ${i} must be an object`);
|
||||||
if (itemArticleId == null && item?.sku) {
|
|
||||||
const res = await runner.query(
|
|
||||||
'SELECT id FROM article WHERE sku = @itemSku AND company_id = @companyId',
|
|
||||||
{ itemSku: item.sku, companyId },
|
|
||||||
);
|
|
||||||
itemArticleId = res.rows[0]?.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itemArticleId || Number.isNaN(itemArticleId) || itemArticleId <= 0) {
|
let itemArticleId = null;
|
||||||
|
|
||||||
|
if (item.articleId !== undefined && item.articleId !== null) {
|
||||||
|
if (
|
||||||
|
typeof item.articleId !== 'number' ||
|
||||||
|
!Number.isInteger(item.articleId) ||
|
||||||
|
item.articleId <= 0
|
||||||
|
) {
|
||||||
|
throw new Error(`Option at index ${i} must specify a valid articleId or sku`);
|
||||||
|
}
|
||||||
|
const res = await runner.query(
|
||||||
|
'SELECT id FROM article WHERE id = @itemArticleId AND company_id = @companyId',
|
||||||
|
{ itemArticleId: item.articleId, companyId },
|
||||||
|
);
|
||||||
|
if (!res.rows[0]) {
|
||||||
|
throw new Error(`Article not found for option: ${item.articleId}`);
|
||||||
|
}
|
||||||
|
itemArticleId = res.rows[0].id;
|
||||||
|
} else if (item.sku !== undefined && item.sku !== null) {
|
||||||
|
if (typeof item.sku !== 'string' || !item.sku.trim()) {
|
||||||
|
throw new Error(`Option at index ${i} must specify a valid articleId or sku`);
|
||||||
|
}
|
||||||
|
const itemSku = item.sku.trim();
|
||||||
|
const res = await runner.query(
|
||||||
|
'SELECT id FROM article WHERE sku = @itemSku AND company_id = @companyId',
|
||||||
|
{ itemSku, companyId },
|
||||||
|
);
|
||||||
|
if (!res.rows[0]) {
|
||||||
|
throw new Error(`Article not found for option: ${itemSku}`);
|
||||||
|
}
|
||||||
|
itemArticleId = res.rows[0].id;
|
||||||
|
} else {
|
||||||
throw new Error(`Option at index ${i} must specify a valid articleId or sku`);
|
throw new Error(`Option at index ${i} must specify a valid articleId or sku`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,16 +165,7 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
}
|
}
|
||||||
seenArticleIds.add(itemArticleId);
|
seenArticleIds.add(itemArticleId);
|
||||||
|
|
||||||
// Verify component article exists in same company
|
const rawCount = item?.count === undefined ? 1 : item.count;
|
||||||
const itemArtRes = await runner.query(
|
|
||||||
'SELECT id FROM article WHERE id = @itemArticleId AND company_id = @companyId',
|
|
||||||
{ itemArticleId, companyId },
|
|
||||||
);
|
|
||||||
if (!itemArtRes.rows[0]) {
|
|
||||||
throw new Error(`Article not found for option: ${itemArticleId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawCount = item?.count;
|
|
||||||
if (
|
if (
|
||||||
typeof rawCount !== 'number' ||
|
typeof rawCount !== 'number' ||
|
||||||
!Number.isInteger(rawCount) ||
|
!Number.isInteger(rawCount) ||
|
||||||
@@ -136,13 +175,22 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
throw new Error(`Invalid count for options item at index ${i}: must be a positive integer`);
|
throw new Error(`Invalid count for options item at index ${i}: must be a positive integer`);
|
||||||
}
|
}
|
||||||
const count = rawCount;
|
const count = rawCount;
|
||||||
let sort = item?.sort != null && Number.isInteger(Number(item.sort)) ? Number(item.sort) : i;
|
|
||||||
|
if (item.sort !== undefined && item.sort !== null) {
|
||||||
|
if (typeof item.sort !== 'number' || !Number.isInteger(item.sort)) {
|
||||||
|
throw new Error(`Invalid sort for options item at index ${i}: must be an integer`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let sort = (typeof item?.sort === 'number' && Number.isInteger(item.sort)) ? item.sort : i;
|
||||||
|
|
||||||
while (seenSorts.has(sort)) {
|
while (seenSorts.has(sort)) {
|
||||||
sort++;
|
sort++;
|
||||||
}
|
}
|
||||||
seenSorts.add(sort);
|
seenSorts.add(sort);
|
||||||
|
|
||||||
|
if (item.mandated !== undefined && item.mandated !== null && typeof item.mandated !== 'boolean') {
|
||||||
|
throw new Error(`Invalid mandated for options item at index ${i}: must be a boolean`);
|
||||||
|
}
|
||||||
const mandated = Boolean(item?.mandated);
|
const mandated = Boolean(item?.mandated);
|
||||||
|
|
||||||
validated.push({
|
validated.push({
|
||||||
@@ -177,7 +225,17 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return getOptions(googleAuthId, sku, runner);
|
const result = await getOptions(googleAuthId, sku, runner);
|
||||||
|
|
||||||
|
// Write history for options list change
|
||||||
|
const newNames = result.map((r) => r.name || r.sku);
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Optionsliste gesetzt: [${newNames.join(', ')}]`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tx) {
|
if (tx) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as mssqlQueries from '../../providers/mssql/queries/manifestDoc.js';
|
import * as mssqlQueries from '../../providers/mssql/queries/manifestDoc.js';
|
||||||
import * as sqliteQueries from '../../providers/sqlite3/queries/manifestDoc.js';
|
import * as sqliteQueries from '../../providers/sqlite3/queries/manifestDoc.js';
|
||||||
|
import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js';
|
||||||
|
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js';
|
||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||||
import nextNumber from '../numbers/nextNumber.js';
|
import nextNumber from '../numbers/nextNumber.js';
|
||||||
@@ -23,6 +25,7 @@ export default function adjust(provider) {
|
|||||||
const addLedger = ledgerAdd(provider);
|
const addLedger = ledgerAdd(provider);
|
||||||
const isSqlite = provider?.dialect === 'sqlite3';
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts article stock manually by creating an implicit manifest of type "adjustment",
|
* Adjusts article stock manually by creating an implicit manifest of type "adjustment",
|
||||||
@@ -107,7 +110,7 @@ export default function adjust(provider) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const run = async (runner, activeTx) => {
|
const run = async (runner, activeTx) => {
|
||||||
const { companyId } = await access(googleAuthId, runner);
|
const { companyId, companyuserId } = await access(googleAuthId, runner);
|
||||||
|
|
||||||
// Validate all articles exist and belong to the user's company
|
// Validate all articles exist and belong to the user's company
|
||||||
const resolvedArticles = [];
|
const resolvedArticles = [];
|
||||||
@@ -228,6 +231,16 @@ export default function adjust(provider) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
createdLedgerEntries.push(ledgerRow);
|
createdLedgerEntries.push(ledgerRow);
|
||||||
|
|
||||||
|
// Write article history for the adjusted article
|
||||||
|
if (item.articleId) {
|
||||||
|
const sign = item.amount > 0 ? '+' : '';
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId: item.articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: `Bestandskorrektur ${sign}${item.amount} (${item.text})`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user