From 2e961252f737797b4652824227da0e3ed44adc82 Mon Sep 17 00:00:00 2001 From: seb Date: Mon, 12 Oct 2026 17:29:29 +0200 Subject: [PATCH] u --- API.article.md | 4 +- API.articlehistory.md | 19 + API.md | 6 +- API.order.md | 2 +- API.shipping.md | 2 +- generate-api-doc.js | 2 +- schema-graph.dot | 4 +- schema-graph.svg | 66 +- scripts/test-order-shipping-ledger.js | 28 +- src/providers/mssql/queries/articlehistory.js | 8 +- src/providers/mssql/queries/ledger.js | 4 +- src/providers/mssql/schema.js | 1 + .../sqlite3/queries/articlehistory.js | 2 +- src/providers/sqlite3/queries/ledger.js | 4 +- src/providers/sqlite3/schema.js | 1 + src/store/article/addChild.js | 23 +- src/store/article/attachChild.js | 22 +- src/store/article/createUnderParent.js | 8 +- src/store/article/getHistoryBySku.js | 5 +- src/store/article/importRows.js | 31 +- src/store/article/removeChild.js | 32 +- src/store/article/setBrandIdBySku.js | 17 +- src/store/article/setCategoryIdsBySku.js | 19 +- src/store/article/setEansBySku.js | 19 +- src/store/article/setParentBySku.js | 26 +- src/store/article/setPricesBySku.js | 20 +- src/store/article/setTrackStockBySku.js | 17 +- src/store/article/setWeightBySku.js | 17 +- src/store/article/toggleParent.js | 37 +- src/store/article/updateBySku.js | 8 +- .../articleattributevalue/setByArticleSku.js | 20 +- src/store/articlehistory/record.js | 855 ++++++++++++++++++ src/store/articlelist/setItemsByArticleSku.js | 15 +- src/store/articleproperty/delete.js | 9 +- src/store/articleproperty/setByArticleSku.js | 20 +- src/store/articlesupplier/create.js | 18 +- src/store/articlesupplier/delete.js | 15 +- src/store/articlesupplier/update.js | 17 +- src/store/ledger/add.js | 43 +- .../optionslist/setOptionsByArticleSku.js | 15 +- src/store/order/create.js | 27 +- src/store/shipping/create.js | 11 - src/store/shipping/update.js | 11 - src/store/stock/adjust.js | 10 +- 44 files changed, 1166 insertions(+), 374 deletions(-) create mode 100644 API.articlehistory.md create mode 100644 src/store/articlehistory/record.js diff --git a/API.article.md b/API.article.md index 0915c4a..8fe79c3 100644 --- a/API.article.md +++ b/API.article.md @@ -217,13 +217,13 @@ Returns the change history of an article by its SKU. **Returns** -`Promise>` — The article history entries. +`Promise>` — The article history entries. **Example** ```js const history = await store.article.getHistoryBySku(42, 'SKU-1'); -// => [{ id, companyuserId, date, text }] +// => [{ id, companyuserId, date, text, meta }] const recent = await store.article.getHistoryBySku(42, 'SKU-1', { limit: 5 }); // => up to 5 most recent entries diff --git a/API.articlehistory.md b/API.articlehistory.md new file mode 100644 index 0000000..73b5587 --- /dev/null +++ b/API.articlehistory.md @@ -0,0 +1,19 @@ +# articlehistory + +This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`. + +## `articlehistory.record` + +*Source: `src/store/articlehistory/record.js`* + +Formats a numeric currency amount in German language style: "€ 1.000,99" + +**Parameters** + +| Name | Type | Description | +| --- | --- | --- | +| `amount` | `number|string` | The currency amount. | + +**Returns** + +`string` — The formatted currency string. diff --git a/API.md b/API.md index 156020e..8b8ec2a 100644 --- a/API.md +++ b/API.md @@ -11,7 +11,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - **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` +- **articlehistory**: `id PK, article_id->article, companyuser_id?->companyuser, date:dt, text:txt, meta?:txt` - **articlelist**: `id PK` - **articlelistarticle**: `id PK, articlelist_id->articlelist, article_id->article, count:i=1, sort:i=0, UQ(articlelist_id, article_id), UQ(articlelist_id, sort)` - **articlepic**: `id PK, article_id->article, pic_id->pic, sort:i=0, UQ(article_id, pic_id), UQ(article_id, sort)` @@ -93,7 +93,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - `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, desc: string|null, seo: string|null, 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, 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>` + - `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, parentSku: string, tx?: object): Promise>` - `listExport(googleAuthId: number, categoryId?: number|null, tx?: object): Promise>` @@ -285,6 +285,8 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - `create(googleAuthId: number, input: {type: string, name: string}, tx?: object): Promise<{id: number, companyId: number, type: string, name: string}>` - `delete(googleAuthId: number, id: number, tx?: object): Promise` - `list(googleAuthId: number, tx?: object): Promise>` +- [articlehistory](API.articlehistory.md) + - `record(amount: number|string): string` - [articlelist](API.articlelist.md) - `getItemsByArticleSku(googleAuthId: number, sku: string, tx?: object): Promise>` - `setItemsByArticleSku(googleAuthId: number, sku: string, items?: Array<{articleId?: number, sku?: string, count?: number, sort?: number}>, tx?: object): Promise>` diff --git a/API.order.md b/API.order.md index cb2f109..232dcc2 100644 --- a/API.order.md +++ b/API.order.md @@ -6,7 +6,7 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin *Source: `src/store/order/create.js`* -Creates a new order for the user's selected company with its lines. +Creates a new order for the user's selected company with its lines and records article history. **Parameters** diff --git a/API.shipping.md b/API.shipping.md index 32e978b..561d46b 100644 --- a/API.shipping.md +++ b/API.shipping.md @@ -6,7 +6,7 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin *Source: `src/store/shipping/create.js`* -Creates a shipping for a manifest (order). The number is allocated from the `shipping` numbers sequence (own prefix + counter). If state is 'shipped', adjusts stock by appending negative ledger movements for tracked order lines, writes article history, and ensures the order was not already shipped. +Creates a shipping for a manifest (order). The number is allocated from the `shipping` numbers sequence (own prefix + counter). If state is 'shipped', adjusts stock by appending negative ledger movements for tracked order lines, writes article history for all items, and ensures the order was not already shipped. **Parameters** diff --git a/generate-api-doc.js b/generate-api-doc.js index 8e698a4..bd5ea3d 100644 --- a/generate-api-doc.js +++ b/generate-api-doc.js @@ -322,7 +322,7 @@ 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_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'], + ['articlehistory', 'id PK, article_id->article, companyuser_id?->companyuser, date:dt, text:txt, meta?:txt'], ['articlelist', 'id PK'], ['articlelistarticle', 'id PK, articlelist_id->articlelist, article_id->article, count:i=1, sort:i=0, UQ(articlelist_id, article_id), UQ(articlelist_id, sort)'], ['articlepic', 'id PK, article_id->article, pic_id->pic, sort:i=0, UQ(article_id, pic_id), UQ(article_id, sort)'], diff --git a/schema-graph.dot b/schema-graph.dot index cb82844..a558612 100644 --- a/schema-graph.dot +++ b/schema-graph.dot @@ -41,7 +41,7 @@ digraph schema { articlelistarticle [label=<
articlelistarticle
idINT PK
articlelist_idINT FK
article_idINT FK
countINT
sortINT
>]; optionslist [label=<
optionslist
idINT PK
>]; optionslistarticle [label=<
optionslistarticle
idINT PK
optionslist_idINT FK
article_idINT FK
countINT
sortINT
mandatedBIT
>]; - articlehistory [label=<
articlehistory
idINT PK
article_idINT FK
companyuser_id?INT FK
dateDATETIME2
textNVARCHAR(MAX)
>]; + articlehistory [label=<
articlehistory
idINT PK
article_idINT FK
companyuser_id?INT FK
dateDATETIME2
textNVARCHAR(MAX)
meta?NVARCHAR(MAX)
>]; articleean [label=<
articleean
idINT PK
article_idINT FK
eanNVARCHAR(50)
>]; article [label=<
article
idINT PK
brand_id?INT FK
articlelist_id?INT FK
optionslist_id?INT FK
parentarticle_id?INT FK
unit_idINT FK
parentattributename_id?INT 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
tax_id?INT FK
entity_idINT FK
net?FLOAT
sku?NVARCHAR(100)
stock?FLOAT
puFLOAT
sortINT
>]; @@ -144,7 +144,7 @@ digraph schema { article:parentarticle_id:e -> article:id:e [color="#c79bff", fontcolor="#c79bff", tooltip="FK_article_parentarticle", constraint=false]; article:parentattributename_id:se -> attributename:in1:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_article_parentattributename", constraint=false, weight=1]; article:parentattributename_id:se -> attributename:in1:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_article_parentattributename", constraint=false, weight=1]; - articlehistory:article_id:sw -> article:in2:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_articlehistory_article", weight=3]; + articlehistory:article_id:se -> article:in2:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_articlehistory_article", weight=3]; articlehistory:companyuser_id:e -> companyuser:in0:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_articlehistory_companyuser", constraint=false, weight=1]; companyhistory:company_id:nw -> company:in11:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_companyhistory_company", weight=3]; companyhistory:companyuser_id:e -> companyuser:in1:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_companyhistory_companyuser", constraint=false, weight=1]; diff --git a/schema-graph.svg b/schema-graph.svg index 1e9e348..64113de 100644 --- a/schema-graph.svg +++ b/schema-graph.svg @@ -1246,49 +1246,53 @@ articlehistory - - -articlehistory - - -id -INT -PK - - -article_id -INT -FK - - -companyuser_id -? -INT -FK - - -date -DATETIME2 - - -text -NVARCHAR(MAX) + + +articlehistory + + +id +INT +PK + + +article_id +INT +FK + + +companyuser_id +? +INT +FK + + +date +DATETIME2 + + +text +NVARCHAR(MAX) + + +meta? +NVARCHAR(MAX) articlehistory:e->companyuser:n - + -articlehistory:sw->article:n +articlehistory:se->article:n - - + + diff --git a/scripts/test-order-shipping-ledger.js b/scripts/test-order-shipping-ledger.js index ef95f1f..55f5505 100644 --- a/scripts/test-order-shipping-ledger.js +++ b/scripts/test-order-shipping-ledger.js @@ -145,8 +145,13 @@ async function run() { // Assert article history: order goes into history, shipment does NOT const history = await store.article.getHistoryBySku(googleAuthId, trackedArt.sku); console.log('Tracked article history:', history.map((h) => h.text)); - const orderHistory = history.find((h) => h.text === 'Auftrag O-2026-001 (Kunde: "Max Mustermann", Menge: 3)'); - assert(orderHistory, 'Article history should contain order creation entry'); + const orderHistory = history.find((h) => h.text === 'Auftrag O-2026-001 (Kunde: "Max Mustermann", Menge: 3, € 59,50)'); + assert(orderHistory, 'Article history should contain order creation entry with formatted gross price'); + assert(orderHistory.meta, 'Article history entry should have meta'); + const parsedMeta = JSON.parse(orderHistory.meta); + assert.equal(parsedMeta.type, 'order'); + assert.equal(parsedMeta.orderId, order.id); + assert.equal(parsedMeta.gross, 59.5); const shippingHistory = history.find((h) => h.text.includes('Versand Auftrag')); assert(!shippingHistory, 'Article history should NOT report the shipment'); @@ -154,14 +159,29 @@ async function run() { const untrackedHistory = await store.article.getHistoryBySku(googleAuthId, untrackedArt.sku); console.log('Untracked article history:', untrackedHistory.map((h) => h.text)); assert( - untrackedHistory.some((h) => h.text === 'Auftrag O-2026-001 (Kunde: "Max Mustermann", Menge: 2)'), - 'Untracked article history should contain order creation entry', + untrackedHistory.some((h) => h.text === 'Auftrag O-2026-001 (Kunde: "Max Mustermann", Menge: 2, € 23,80)'), + 'Untracked article history should contain order creation entry with formatted gross price', ); assert( !untrackedHistory.some((h) => h.text.includes('Versand Auftrag')), 'Untracked article history should NOT report the shipment', ); + // Test order import with historical date preserves date in article history + const historicalDate = '2023-04-15T10:30:00.000Z'; + await store.order.create(googleAuthId, { + number: 'O-HIST-001', + date: historicalDate, + createdAt: historicalDate, + lines: [{ sku: trackedArt.sku, amount: 1, net: 10, taxrate: 19 }], + }); + const histWithOld = await store.article.getHistoryBySku(googleAuthId, trackedArt.sku); + const oldEntry = histWithOld.find((h) => h.text.startsWith('Auftrag O-HIST-001')); + assert(oldEntry, 'Historical order entry should exist in article history'); + assert.equal(new Date(oldEntry.date).toISOString(), historicalDate, 'History entry should preserve historical order date'); + const oldMeta = JSON.parse(oldEntry.meta); + assert.equal(oldMeta.orderNumber, 'O-HIST-001'); + // 6. Test immutability: once shipped, cannot be deleted console.log('6. Testing cannot delete shipped shipping...'); await assert.rejects( diff --git a/src/providers/mssql/queries/articlehistory.js b/src/providers/mssql/queries/articlehistory.js index 6925301..9fa6abf 100644 --- a/src/providers/mssql/queries/articlehistory.js +++ b/src/providers/mssql/queries/articlehistory.js @@ -1,17 +1,17 @@ export const INSERT = ` -INSERT INTO articlehistory (article_id, companyuser_id, [text]) -VALUES (@articleId, @companyuserId, @text) +INSERT INTO articlehistory (article_id, companyuser_id, [date], [text], meta) +VALUES (@articleId, @companyuserId, COALESCE(@date, SYSUTCDATETIME()), @text, @meta) `; export const LIST_BY_ARTICLE_ID = ` -SELECT id, companyuser_id AS companyuserId, [date], [text] +SELECT id, companyuser_id AS companyuserId, [date], [text], meta FROM articlehistory WHERE article_id = @articleId ORDER BY [date] DESC, id DESC `; export const LIST_BY_ARTICLE_ID_LIMITED = ` -SELECT TOP (@limit) id, companyuser_id AS companyuserId, [date], [text] +SELECT TOP (@limit) id, companyuser_id AS companyuserId, [date], [text], meta FROM articlehistory WHERE article_id = @articleId ORDER BY [date] DESC, id DESC diff --git a/src/providers/mssql/queries/ledger.js b/src/providers/mssql/queries/ledger.js index d191892..ff96a7f 100644 --- a/src/providers/mssql/queries/ledger.js +++ b/src/providers/mssql/queries/ledger.js @@ -38,8 +38,8 @@ WHERE sku = @sku `; export const INSERT_ARTICLE_HISTORY = ` -INSERT INTO articlehistory (article_id, companyuser_id, [text]) -VALUES (@articleId, @companyuserId, @text); +INSERT INTO articlehistory (article_id, companyuser_id, [date], [text], meta) +VALUES (@articleId, @companyuserId, COALESCE(@date, SYSUTCDATETIME()), @text, @meta); `; export const GET_LATEST_ROW = ` diff --git a/src/providers/mssql/schema.js b/src/providers/mssql/schema.js index 9640965..d269d18 100644 --- a/src/providers/mssql/schema.js +++ b/src/providers/mssql/schema.js @@ -372,6 +372,7 @@ BEGIN companyuser_id INT NULL, [date] DATETIME2 NOT NULL CONSTRAINT DF_articlehistory_date DEFAULT SYSUTCDATETIME(), [text] NVARCHAR(MAX) NOT NULL, + meta NVARCHAR(MAX) NULL, CONSTRAINT FK_articlehistory_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE, CONSTRAINT FK_articlehistory_companyuser FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE SET NULL ); diff --git a/src/providers/sqlite3/queries/articlehistory.js b/src/providers/sqlite3/queries/articlehistory.js index e85534b..f6d52c9 100644 --- a/src/providers/sqlite3/queries/articlehistory.js +++ b/src/providers/sqlite3/queries/articlehistory.js @@ -5,7 +5,7 @@ const queries = translateQueries(source, 'articlehistory'); export const { INSERT, LIST_BY_ARTICLE_ID } = queries; export const LIST_BY_ARTICLE_ID_LIMITED = ` -SELECT id, companyuser_id AS companyuserId, date, text +SELECT id, companyuser_id AS companyuserId, date, text, meta FROM articlehistory WHERE article_id = @articleId ORDER BY date DESC, id DESC diff --git a/src/providers/sqlite3/queries/ledger.js b/src/providers/sqlite3/queries/ledger.js index b55bbb3..0255c95 100644 --- a/src/providers/sqlite3/queries/ledger.js +++ b/src/providers/sqlite3/queries/ledger.js @@ -38,8 +38,8 @@ WHERE sku = @sku `; export const INSERT_ARTICLE_HISTORY = ` -INSERT INTO articlehistory (article_id, companyuser_id, text) -VALUES (@articleId, @companyuserId, @text); +INSERT INTO articlehistory (article_id, companyuser_id, date, text, meta) +VALUES (@articleId, @companyuserId, COALESCE(@date, strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), @text, @meta); `; export const GET_LATEST_ROW = ` diff --git a/src/providers/sqlite3/schema.js b/src/providers/sqlite3/schema.js index 1841433..67ef3c2 100644 --- a/src/providers/sqlite3/schema.js +++ b/src/providers/sqlite3/schema.js @@ -551,6 +551,7 @@ CREATE TABLE IF NOT EXISTS articlehistory ( companyuser_id INTEGER, "date" TEXT NOT NULL DEFAULT (${UTC_NOW}), "text" TEXT NOT NULL, + meta TEXT, FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE, FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE SET NULL ); diff --git a/src/store/article/addChild.js b/src/store/article/addChild.js index 966efe0..fc45197 100644 --- a/src/store/article/addChild.js +++ b/src/store/article/addChild.js @@ -10,12 +10,14 @@ import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/ import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js'; import { CREATE as MSSQL_CREATE_ARTICLETEXT } from '../../providers/mssql/queries/articletext.js'; import { CREATE as SQLITE_CREATE_ARTICLETEXT } from '../../providers/sqlite3/queries/articletext.js'; -import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js'; -import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import getBySku from './getBySku.js'; import makeRunner from '../runner.js'; import resolveUniqueSlug from './resolveUniqueSlug.js'; +import { + recordVariantAddedOnParentHistory, + recordChildAssignedToParentHistory, +} from '../articlehistory/record.js'; import attachChild from './attachChild.js'; export default function addChild(provider) { @@ -31,7 +33,6 @@ export default function addChild(provider) { const articleAttrValQueries = isSqlite ? sqliteArticleAttrVal : mssqlArticleAttrVal; const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE; const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Adds a child article to a parent article (creates and attaches with auto-generated SKU). @@ -242,17 +243,21 @@ export default function addChild(provider) { }); // History on child - await runner.query(insertHistorySql, { - articleId: childId, + await recordChildAssignedToParentHistory(runner, { + childId, companyuserId, - text: `Artikel "${childName}" (${childSku}) als Variante von "${parent.sku}" angelegt`, + childName, + childSku, + parentSku: parent.sku, + created: true, }); // History on parent - await runner.query(insertHistorySql, { - articleId: parentId, + await recordVariantAddedOnParentHistory(runner, { + parentId, companyuserId, - text: `Variante "${childName}" (${childSku}) hinzugefügt`, + childName, + childSku, }); return load(googleAuthId, childSku, runner); diff --git a/src/store/article/attachChild.js b/src/store/article/attachChild.js index d9f0a5e..dd359e0 100644 --- a/src/store/article/attachChild.js +++ b/src/store/article/attachChild.js @@ -6,11 +6,13 @@ import * as mssqlArticleAttrVal from '../../providers/mssql/queries/articleattri import * as sqliteArticleAttrVal from '../../providers/sqlite3/queries/articleattributevalue.js'; import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/categoryarticle.js'; import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js'; -import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js'; -import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import getBySku from './getBySku.js'; import listChildren from './listChildren.js'; +import { + recordChildAssignedToParentHistory, + recordVariantAddedOnParentHistory, +} from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function attachChild(provider) { @@ -23,7 +25,6 @@ export default function attachChild(provider) { const attrValQueries = isSqlite ? sqliteAttrVal : mssqlAttrVal; const articleAttrValQueries = isSqlite ? sqliteArticleAttrVal : mssqlArticleAttrVal; const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Attaches an existing article as a child to a parent article. @@ -288,16 +289,19 @@ export default function attachChild(provider) { // History only if not already child if (!isAlreadyChildOfThisParent) { - await runner.query(insertHistorySql, { - articleId: child.id, + await recordChildAssignedToParentHistory(runner, { + childId: child.id, companyuserId, - text: `Artikel "${childName}" (${childSku}) als Variante von "${parent.sku}" zugewiesen`, + childName, + childSku, + parentSku: parent.sku, }); - await runner.query(insertHistorySql, { - articleId: parentId, + await recordVariantAddedOnParentHistory(runner, { + parentId, companyuserId, - text: `Variante "${childName}" (${childSku}) hinzugefügt`, + childName, + childSku, }); } diff --git a/src/store/article/createUnderParent.js b/src/store/article/createUnderParent.js index e3095d0..d89537d 100644 --- a/src/store/article/createUnderParent.js +++ b/src/store/article/createUnderParent.js @@ -4,14 +4,13 @@ import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/ import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js'; import { CREATE as MSSQL_CREATE_ARTICLETEXT } from '../../providers/mssql/queries/articletext.js'; import { CREATE as SQLITE_CREATE_ARTICLETEXT } from '../../providers/sqlite3/queries/articletext.js'; -import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js'; -import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js'; import { isUniqueConstraintError } from '../../providers/provider.js'; import resolveCategoryAccess from '../category/resolveCategoryAccess.js'; import ensureDefaultUnit from '../unit/ensureDefault.js'; import nextSku from '../numbers/nextSku.js'; import makeRunner from '../runner.js'; import resolveUniqueSlug from './resolveUniqueSlug.js'; +import { recordArticleCreatedHistory } from '../articlehistory/record.js'; export default function createUnderParent(provider) { const access = resolveCategoryAccess(provider); @@ -23,7 +22,6 @@ export default function createUnderParent(provider) { const createArticleSql = isSqlite ? SQLITE_CREATE_ARTICLE : MSSQL_CREATE_ARTICLE; const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE; const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Creates a new article under a given parent category, allocating a SKU and @@ -95,10 +93,10 @@ export default function createUnderParent(provider) { seo: finalSeo, }); - await runner.query(insertHistorySql, { + await recordArticleCreatedHistory(runner, { articleId: id, companyuserId, - text: `Artikel "${name}" angelegt`, + name, }); return { diff --git a/src/store/article/getHistoryBySku.js b/src/store/article/getHistoryBySku.js index 6c0c5bc..4013f3d 100644 --- a/src/store/article/getHistoryBySku.js +++ b/src/store/article/getHistoryBySku.js @@ -17,11 +17,11 @@ export default function getHistoryBySku(provider) { * @param {object} [opts] - Optional options. * @param {number} [opts.limit] - Maximum number of entries to return (newest first). * @param {object} [tx] - Optional transaction. - * @returns {Promise>} The article history entries. + * @returns {Promise>} The article history entries. * * @example * const history = await store.article.getHistoryBySku(42, 'SKU-1'); - * // => [{ id, companyuserId, date, text }] + * // => [{ id, companyuserId, date, text, meta }] * * const recent = await store.article.getHistoryBySku(42, 'SKU-1', { limit: 5 }); * // => up to 5 most recent entries @@ -51,6 +51,7 @@ export default function getHistoryBySku(provider) { companyuserId: row.companyuserId ?? null, date: row.date, text: row.text, + meta: row.meta ?? null, })); }; } diff --git a/src/store/article/importRows.js b/src/store/article/importRows.js index a560f34..99f0fa6 100644 --- a/src/store/article/importRows.js +++ b/src/store/article/importRows.js @@ -4,10 +4,14 @@ import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/ import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js'; import { CREATE as MSSQL_CREATE_ARTICLETEXT } from '../../providers/mssql/queries/articletext.js'; import { CREATE as SQLITE_CREATE_ARTICLETEXT } from '../../providers/sqlite3/queries/articletext.js'; -import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js'; -import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js'; import { GET_CHILD_COUNT as MSSQL_GET_CHILD_COUNT } from '../../providers/mssql/queries/articleDelete.js'; import { GET_CHILD_COUNT as SQLITE_GET_CHILD_COUNT } from '../../providers/sqlite3/queries/articleDelete.js'; +import { + recordParentConvertedHistory, + recordVariantAddedOnParentHistory, + recordChildAssignedToParentHistory, + recordArticleImportedHistory, +} from '../articlehistory/record.js'; import { CREATE as MSSQL_CREATE_BRAND, GET_BY_COMPANY_AND_NAME as MSSQL_GET_BRAND_BY_NAME, @@ -55,7 +59,6 @@ export default function importRows(provider) { const articleQueries = isSqlite ? sqliteArticle : mssqlArticle; const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE; const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT; const getBrandByNameSql = isSqlite ? SQLITE_GET_BRAND_BY_NAME : MSSQL_GET_BRAND_BY_NAME; const createBrandSql = isSqlite ? SQLITE_CREATE_BRAND : MSSQL_CREATE_BRAND; @@ -210,10 +213,10 @@ export default function importRows(provider) { seo: null, }); - await runner.query(insertHistorySql, { + await recordArticleImportedHistory(runner, { articleId, companyuserId, - text: `Artikel "${name}" importiert`, + name, }); // Brand (upsert by name). @@ -318,22 +321,24 @@ export default function importRows(provider) { }); if (!wasParent) { - await runner.query(insertHistorySql, { + await recordParentConvertedHistory(runner, { articleId: parentId, companyuserId, - text: 'Artikel zu Vaterartikel konvertiert', }); } const childName = String(row?.name ?? sku).trim(); - await runner.query(insertHistorySql, { - articleId: parentId, + await recordVariantAddedOnParentHistory(runner, { + parentId, companyuserId, - text: `Variante "${childName}" (${sku}) hinzugefügt`, + childName, + childSku: sku, }); - await runner.query(insertHistorySql, { - articleId: childId, + await recordChildAssignedToParentHistory(runner, { + childId, companyuserId, - text: `Artikel "${childName}" (${sku}) als Variante von "${parentSku}" zugewiesen`, + childName, + childSku: sku, + parentSku, }); } } diff --git a/src/store/article/removeChild.js b/src/store/article/removeChild.js index cb0bada..40bf5b9 100644 --- a/src/store/article/removeChild.js +++ b/src/store/article/removeChild.js @@ -1,9 +1,11 @@ import * as mssqlArticle from '../../providers/mssql/queries/article.js'; import * as sqliteArticle from '../../providers/sqlite3/queries/article.js'; -import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js'; -import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import articleDelete from './delete.js'; +import { + recordChildUnlinkedFromParentHistory, + recordChildDeletedFromParentHistory, +} from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function removeChild(provider) { @@ -12,7 +14,6 @@ export default function removeChild(provider) { const isSqlite = provider?.dialect === 'sqlite3'; const queries = isSqlite ? sqliteArticle : mssqlArticle; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Removes a child article from a parent article, either by unlinking or deleting it. @@ -78,10 +79,11 @@ export default function removeChild(provider) { await recompactParentSort(); // History on parent (child article is gone so we can only log on parent) - await runner.query(insertHistorySql, { - articleId: parentId, + await recordChildDeletedFromParentHistory(runner, { + parentId, companyuserId, - text: `Variante "${childName}" (${childSku}) gelöscht`, + childName, + childSku, }); return { @@ -119,18 +121,14 @@ export default function removeChild(provider) { await recompactParentSort(); - // Add history on child - await runner.query(insertHistorySql, { - articleId: childId, + // Add history on child and parent + await recordChildUnlinkedFromParentHistory(runner, { + childId, + parentId, companyuserId, - text: `Artikel "${childSku}" von Vaterartikel "${parentSku}" getrennt`, - }); - - // Add history on parent - await runner.query(insertHistorySql, { - articleId: parentId, - companyuserId, - text: `Variante "${childName}" (${childSku}) getrennt`, + childSku, + childName, + parentSku, }); return { diff --git a/src/store/article/setBrandIdBySku.js b/src/store/article/setBrandIdBySku.js index 196a67f..53bc46a 100644 --- a/src/store/article/setBrandIdBySku.js +++ b/src/store/article/setBrandIdBySku.js @@ -6,10 +6,9 @@ import { GET_BRAND_BY_COMPANY_AND_ID as SQLITE_GET_BRAND, UPDATE_BRAND as SQLITE_UPDATE_BRAND, } 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 resolveArticleBySku from './resolveArticleBySku.js'; +import { recordBrandChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setBrandIdBySku(provider) { @@ -19,7 +18,6 @@ export default function setBrandIdBySku(provider) { const getBrandSql = isSqlite ? SQLITE_GET_BRAND : MSSQL_GET_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. @@ -82,13 +80,12 @@ export default function setBrandIdBySku(provider) { } // null = no brand; treat as distinct sentinel so first assignment is logged - if (oldBrandName !== newBrandName) { - await runner.query(insertHistorySql, { - articleId, - companyuserId, - text: `Marke ge\u00e4ndert von "${oldBrandName ?? ''}" auf "${newBrandName ?? ''}"`, - }); - } + await recordBrandChangeHistory(runner, { + articleId, + companyuserId, + oldBrandName, + newBrandName, + }); const article = await load(articleId, languageId, runner); if (!article) { diff --git a/src/store/article/setCategoryIdsBySku.js b/src/store/article/setCategoryIdsBySku.js index 34ce240..7ba2963 100644 --- a/src/store/article/setCategoryIdsBySku.js +++ b/src/store/article/setCategoryIdsBySku.js @@ -6,11 +6,10 @@ import { ADD_CATEGORY as SQLITE_ADD_CATEGORY, DELETE_CATEGORIES_BY_ARTICLE as SQLITE_DELETE_CATEGORIES, } 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 queryBySku from './queryBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js'; +import { recordCategoryChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setCategoryIdsBySku(provider) { @@ -21,7 +20,6 @@ export default function setCategoryIdsBySku(provider) { const addCategorySql = isSqlite ? SQLITE_ADD_CATEGORY : MSSQL_ADD_CATEGORY; 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. @@ -86,15 +84,12 @@ 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}]`, - }); - } + await recordCategoryChangeHistory(runner, { + articleId, + companyuserId, + oldCategoryIds, + newCategoryIds: normalized, + }); const article = await load(articleId, languageId, runner); if (!article) { diff --git a/src/store/article/setEansBySku.js b/src/store/article/setEansBySku.js index 0858474..ef6ce94 100644 --- a/src/store/article/setEansBySku.js +++ b/src/store/article/setEansBySku.js @@ -8,11 +8,10 @@ import { DELETE_EANS_BY_ARTICLE as SQLITE_DELETE_EANS, EAN_EXISTS_FOR_OTHER_ARTICLE as SQLITE_CHECK_CONFLICT, } 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 queryBySku from './queryBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js'; +import { recordEanChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setEansBySku(provider) { @@ -22,7 +21,6 @@ export default function setEansBySku(provider) { const addEanSql = isSqlite ? SQLITE_ADD_EAN : MSSQL_ADD_EAN; const deleteEansSql = isSqlite ? SQLITE_DELETE_EANS : MSSQL_DELETE_EANS; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; const checkConflictSql = isSqlite ? SQLITE_CHECK_CONFLICT : MSSQL_CHECK_CONFLICT; /** @@ -93,15 +91,12 @@ export default function setEansBySku(provider) { } // 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}"`, - }); - } + await recordEanChangeHistory(runner, { + articleId, + companyuserId, + oldEans, + newEans: normalized, + }); const article = await load(articleId, languageId, runner); if (!article) { diff --git a/src/store/article/setParentBySku.js b/src/store/article/setParentBySku.js index 56d9ec7..bcf3dac 100644 --- a/src/store/article/setParentBySku.js +++ b/src/store/article/setParentBySku.js @@ -2,10 +2,13 @@ import * as mssqlArticle from '../../providers/mssql/queries/article.js'; import * as sqliteArticle from '../../providers/sqlite3/queries/article.js'; import { GET_CHILD_COUNT as MSSQL_GET_CHILD_COUNT } from '../../providers/mssql/queries/articleDelete.js'; import { GET_CHILD_COUNT as SQLITE_GET_CHILD_COUNT } from '../../providers/sqlite3/queries/articleDelete.js'; -import { 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 resolveArticleBySku from './resolveArticleBySku.js'; +import { + recordParentConvertedHistory, + recordVariantAddedOnParentHistory, + recordChildAssignedToParentHistory, +} from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setParentBySku(provider) { @@ -15,7 +18,6 @@ export default function setParentBySku(provider) { const queries = isSqlite ? sqliteArticle : mssqlArticle; const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Links an article to a parent article (V/K compound) by their SKUs. @@ -118,23 +120,25 @@ export default function setParentBySku(provider) { }); if (!wasParent) { - await runner.query(insertHistorySql, { + await recordParentConvertedHistory(runner, { articleId: parentId, companyuserId, - text: 'Artikel zu Vaterartikel konvertiert', }); } - await runner.query(insertHistorySql, { - articleId: parentId, + await recordVariantAddedOnParentHistory(runner, { + parentId, companyuserId, - text: `Variante "${childName}" (${sku}) hinzugefügt`, + childName, + childSku: sku, }); - await runner.query(insertHistorySql, { - articleId, + await recordChildAssignedToParentHistory(runner, { + childId: articleId, companyuserId, - text: `Artikel "${sku}" als Variante von "${parentSku}" zugewiesen`, + childName: null, + childSku: sku, + parentSku, }); const article = await load(articleId, languageId, runner); diff --git a/src/store/article/setPricesBySku.js b/src/store/article/setPricesBySku.js index d5c7320..2666325 100644 --- a/src/store/article/setPricesBySku.js +++ b/src/store/article/setPricesBySku.js @@ -6,11 +6,10 @@ import { CREATE as SQLITE_CREATE_PRICE, DELETE_BY_ARTICLE_AND_CUSTOMERGROUP as SQLITE_DELETE_PRICE, } 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 queryBySku from './queryBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js'; +import { recordPriceChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; function normalizePrices(prices) { @@ -66,7 +65,6 @@ export default function setPricesBySku(provider) { const createPriceSql = isSqlite ? SQLITE_CREATE_PRICE : MSSQL_CREATE_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. @@ -136,16 +134,12 @@ 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}]`, - }); - } + await recordPriceChangeHistory(runner, { + articleId, + companyuserId, + oldPrices, + newPrices: normalized, + }); const article = await load(articleId, languageId, runner); if (!article) { diff --git a/src/store/article/setTrackStockBySku.js b/src/store/article/setTrackStockBySku.js index 0444802..88fa6e5 100644 --- a/src/store/article/setTrackStockBySku.js +++ b/src/store/article/setTrackStockBySku.js @@ -1,9 +1,8 @@ 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 { 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 resolveArticleBySku from './resolveArticleBySku.js'; +import { recordTrackStockChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setTrackStockBySku(provider) { @@ -13,7 +12,6 @@ export default function setTrackStockBySku(provider) { provider?.dialect === 'sqlite3' ? SQLITE_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. @@ -55,13 +53,12 @@ export default function setTrackStockBySku(provider) { } // 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'}"`, - }); - } + await recordTrackStockChangeHistory(runner, { + articleId, + companyuserId, + oldTrack, + newTrack, + }); const article = await load(articleId, languageId, runner); if (!article) { diff --git a/src/store/article/setWeightBySku.js b/src/store/article/setWeightBySku.js index 72a98c7..2127c60 100644 --- a/src/store/article/setWeightBySku.js +++ b/src/store/article/setWeightBySku.js @@ -1,16 +1,14 @@ 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 { 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 resolveArticleBySku from './resolveArticleBySku.js'; +import { recordWeightChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setWeightBySku(provider) { const resolve = resolveArticleBySku(provider); const load = queryBySku(provider); 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. @@ -51,13 +49,12 @@ export default function setWeightBySku(provider) { } // Write history if weight changed - if (Number(oldWeight) !== weight) { - await runner.query(insertHistorySql, { - articleId, - companyuserId, - text: `Gewicht geändert von "${oldWeight}" auf "${weight}"`, - }); - } + await recordWeightChangeHistory(runner, { + articleId, + companyuserId, + oldWeight, + newWeight: weight, + }); const article = await load(articleId, languageId, runner); if (!article) { diff --git a/src/store/article/toggleParent.js b/src/store/article/toggleParent.js index 21289bb..cadc88b 100644 --- a/src/store/article/toggleParent.js +++ b/src/store/article/toggleParent.js @@ -4,10 +4,12 @@ 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 * as mssqlAttr from '../../providers/mssql/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 getBySku from './getBySku.js'; +import { + recordParentConvertedHistory, + recordParentDeactivatedHistory, +} from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function toggleParent(provider) { @@ -18,7 +20,6 @@ export default function toggleParent(provider) { const queries = isSqlite ? sqliteArticle : mssqlArticle; const getChildCountSql = isSqlite ? SQLITE_GET_CHILD_COUNT : MSSQL_GET_CHILD_COUNT; const attrQueries = isSqlite ? sqliteAttr : mssqlAttr; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Toggles an article into or out of parent mode. @@ -142,32 +143,11 @@ export default function toggleParent(provider) { }); // Add history entry recording parent 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, { + await recordParentConvertedHistory(runner, { articleId, companyuserId, - text: historyLines.join('\n'), + attributeName, + existingPrices, }); } else { // Disabling parent: verify zero children @@ -183,10 +163,9 @@ export default function toggleParent(provider) { parentAttributeNameId: null, }); - await runner.query(insertHistorySql, { + await recordParentDeactivatedHistory(runner, { articleId, companyuserId, - text: 'Vaterartikel-Modus deaktiviert', }); } diff --git a/src/store/article/updateBySku.js b/src/store/article/updateBySku.js index 0d81d13..6319e2b 100644 --- a/src/store/article/updateBySku.js +++ b/src/store/article/updateBySku.js @@ -1,7 +1,5 @@ import * as mssqlArticle from '../../providers/mssql/queries/article.js'; import * as sqliteArticle from '../../providers/sqlite3/queries/article.js'; -import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/articlehistory.js'; -import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/articlehistory.js'; import { UPDATE_BY_ARTICLE_AND_LANGUAGE as MSSQL_UPDATE_TEXT } from '../../providers/mssql/queries/articletext.js'; import { UPDATE_BY_ARTICLE_AND_LANGUAGE as SQLITE_UPDATE_TEXT } from '../../providers/sqlite3/queries/articletext.js'; import { GET_DETAIL_BY_ARTICLE_ID as MSSQL_GET_DETAIL } from '../../providers/mssql/queries/articleDetail.js'; @@ -12,6 +10,7 @@ import queryBySku from './queryBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import makeRunner from '../runner.js'; import resolveUniqueSlug from './resolveUniqueSlug.js'; +import { recordArticleUpdateHistory } from '../articlehistory/record.js'; function formatUnitLabel(amount, symbol) { const sym = symbol ?? ''; @@ -28,7 +27,6 @@ export default function updateBySku(provider) { const isSqlite = provider?.dialect === 'sqlite3'; const articleQueries = isSqlite ? sqliteArticle : mssqlArticle; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; const updateTextSql = isSqlite ? SQLITE_UPDATE_TEXT : MSSQL_UPDATE_TEXT; const getDetailSql = isSqlite ? SQLITE_GET_DETAIL : MSSQL_GET_DETAIL; const getUnitSql = isSqlite ? SQLITE_GET_UNIT : MSSQL_GET_UNIT; @@ -221,10 +219,10 @@ export default function updateBySku(provider) { } if (historyLines.length) { - await runner.query(insertHistorySql, { + await recordArticleUpdateHistory(runner, { articleId, companyuserId, - text: historyLines.join('\n'), + historyLines, }); } diff --git a/src/store/articleattributevalue/setByArticleSku.js b/src/store/articleattributevalue/setByArticleSku.js index 5b318fa..6280f89 100644 --- a/src/store/articleattributevalue/setByArticleSku.js +++ b/src/store/articleattributevalue/setByArticleSku.js @@ -8,9 +8,8 @@ import { INSERT_ROW as SQLITE_INSERT, GET_VALUE_DETAIL as SQLITE_GET_DETAIL, } 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 { recordAttributeChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setByArticleSku(provider) { @@ -19,7 +18,6 @@ export default function setByArticleSku(provider) { const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; 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. @@ -182,16 +180,12 @@ 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}]`, - }); - } + await recordAttributeChangeHistory(runner, { + articleId, + companyuserId, + oldAttrs, + newAttrs: validated, + }); return inserted; }; diff --git a/src/store/articlehistory/record.js b/src/store/articlehistory/record.js new file mode 100644 index 0000000..6e85108 --- /dev/null +++ b/src/store/articlehistory/record.js @@ -0,0 +1,855 @@ +import { INSERT as MSSQL_INSERT } from '../../providers/mssql/queries/articlehistory.js'; +import { INSERT as SQLITE_INSERT } from '../../providers/sqlite3/queries/articlehistory.js'; + +/** + * Formats a numeric currency amount in German language style: "€ 1.000,99" + * + * @param {number|string} amount - The currency amount. + * @returns {string} The formatted currency string. + */ +export function formatCurrencyDe(amount) { + if (amount == null || !Number.isFinite(Number(amount))) return ''; + const num = Number(amount); + const formatted = new Intl.NumberFormat('de-DE', { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(num); + return `€ ${formatted}`; +} + +/** + * Calculates unit gross price given net, rebate, and taxrate. + * + * @param {number|string} net - Unit net price. + * @param {number|string} [rebate=0] - Rebate percentage. + * @param {number|string} [taxrate=0] - Tax rate percentage. + * @returns {number} The calculated gross price. + */ +export function calculateGross(net, rebate = 0, taxrate = 0) { + const n = Number(net || 0); + const r = Number(rebate || 0); + const t = Number(taxrate || 0); + return n * (1 - r / 100) * (1 + t / 100); +} + +/** + * Core function for recording an article history entry. + * Serializes meta into a JSON string if provided as an object. + * + * @param {object} runner - The database runner. + * @param {object} params - Insertion parameters. + * @param {number} params.articleId - The ID of the article. + * @param {number|null} [params.companyuserId] - The ID of the acting company user. + * @param {string} params.text - The human-readable history text. + * @param {object|string|null} [params.meta] - Structured meta object or JSON string. + * @param {string} [params.type] - Optional fallback type if meta is not an object. + * @returns {Promise} Query result. + */ +export async function recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + type, +}) { + if (!articleId) { + throw new Error('articleId required for article history'); + } + const cleanText = typeof text === 'string' ? text.trim() : ''; + if (!cleanText) { + throw new Error('text required for article history'); + } + + const isSqlite = runner?.dialect === 'sqlite3'; + const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; + + let metaObj = meta; + if (metaObj === undefined || metaObj === null) { + metaObj = type ? { type } : null; + } else if (typeof metaObj === 'object' && type && !metaObj.type) { + metaObj = { type, ...metaObj }; + } + + let serializedMeta = null; + if (metaObj !== undefined && metaObj !== null) { + serializedMeta = typeof metaObj === 'string' ? metaObj : JSON.stringify(metaObj); + } + + let parsedDate = null; + if (date != null) { + parsedDate = date instanceof Date ? date : new Date(date); + if (Number.isNaN(parsedDate.getTime())) { + parsedDate = null; + } + } + + return runner.query(insertSql, { + articleId, + companyuserId: companyuserId ?? null, + date: parsedDate, + text: cleanText, + meta: serializedMeta, + }); +} + +/** + * Records an order entry in article history, including German currency formatted gross price. + * Format: "Auftrag {number} (Kunde: "{customerName}", Menge: {amount}, € 1.000,99)" + */ +export async function recordOrderHistory(runner, { + articleId, + companyuserId, + date, + orderId, + number, + customerName, + amount, + net, + rebate, + taxrate, +}) { + const details = []; + if (customerName) { + details.push(`Kunde: "${customerName}"`); + } + if (amount != null && Number.isFinite(Number(amount))) { + details.push(`Menge: ${amount}`); + } + let gross = null; + if (net != null && Number.isFinite(Number(net))) { + gross = calculateGross(net, rebate, taxrate); + const formattedGross = formatCurrencyDe(gross); + if (formattedGross) { + details.push(formattedGross); + } + } + const suffix = details.length ? ` (${details.join(', ')})` : ''; + const text = `Auftrag ${number}${suffix}`; + + const meta = { + type: 'order', + ...(orderId != null ? { orderId } : {}), + orderNumber: number, + customerName: customerName ?? null, + amount: amount != null ? Number(amount) : null, + net: net != null ? Number(net) : null, + rebate: rebate != null ? Number(rebate) : 0, + taxrate: taxrate != null ? Number(taxrate) : 0, + gross: gross != null ? Math.round(gross * 100) / 100 : null, + }; + + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records a delivery / warehouse ledger entry in article history. + */ +export async function recordDeliveryHistory(runner, { + articleId, + companyuserId, + date, + manifestNumber, + supplierName, + warehouseName, + net, + rebate, + taxrate, + amount, + transactionText, + isDelivery = true, +}) { + let historyText = transactionText; + let ekBrutto = null; + + if (isDelivery) { + const details = []; + if (supplierName) { + details.push(`Lieferant: "${supplierName}"`); + } + if (warehouseName) { + details.push(`Lager: "${warehouseName}"`); + } + if (net != null && Number.isFinite(Number(net))) { + const netNum = Number(net); + const rebateNum = Number(rebate ?? 0); + const taxrateNum = Number(taxrate ?? 0); + const netAfterRebate = netNum * (1 - rebateNum / 100); + const brutto = netAfterRebate * (1 + taxrateNum / 100); + ekBrutto = (Math.round(brutto * 100) / 100).toFixed(2); + details.push(`EK brutto: ${ekBrutto}`); + } + if (amount != null && Number.isFinite(Number(amount))) { + details.push(`Menge: ${amount}`); + } + const suffix = details.length ? ` (${details.join(', ')})` : ''; + const base = + transactionText?.trim() || + (manifestNumber ? `Lieferung ${manifestNumber}` : 'Lieferung'); + historyText = `${base}${suffix}`; + } else if (warehouseName && transactionText && !transactionText.includes(warehouseName)) { + historyText = `${transactionText} (Lager: "${warehouseName}")`; + } + + const meta = { + type: isDelivery ? 'delivery' : 'movement', + manifestNumber: manifestNumber ?? null, + supplierName: supplierName ?? null, + warehouseName: warehouseName ?? null, + amount: amount != null ? Number(amount) : null, + net: net != null ? Number(net) : null, + rebate: rebate != null ? Number(rebate) : 0, + taxrate: taxrate != null ? Number(taxrate) : 0, + ekBrutto: ekBrutto != null ? Number(ekBrutto) : null, + transactionText: transactionText ?? null, + }; + + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text: historyText, + meta, + }); +} + +/** + * Records a stock adjustment in article history. + */ +export async function recordStockAdjustmentHistory(runner, { + articleId, + companyuserId, + date, + amount, + reason, +}) { + const sign = amount > 0 ? '+' : ''; + const text = `Bestandskorrektur ${sign}${amount} (${reason})`; + const meta = { + type: 'stock_adjustment', + amount, + reason, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records price changes in article history. + */ +export async function recordPriceChangeHistory(runner, { + articleId, + companyuserId, + date, + oldPrices, + newPrices, +}) { + const fmtPrice = (p) => `${p.net ?? p.price}@${p.taxRate ?? p.tax ?? 0}%`; + const oldStr = (oldPrices || []).map(fmtPrice).join(', '); + const newStr = (newPrices || []).map(fmtPrice).join(', '); + if (oldStr === newStr) return; + + const text = `Preise geändert von [${oldStr}] auf [${newStr}]`; + const meta = { + type: 'price_change', + oldPrices, + newPrices, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records category assignment changes in article history. + */ +export async function recordCategoryChangeHistory(runner, { + articleId, + companyuserId, + date, + oldCategoryIds, + newCategoryIds, +}) { + const oldStr = [...(oldCategoryIds || [])].sort((a, b) => a - b).join(', '); + const newStr = [...(newCategoryIds || [])].sort((a, b) => a - b).join(', '); + if (oldStr === newStr) return; + + const text = `Kategorien geändert von [${oldStr}] auf [${newStr}]`; + const meta = { + type: 'category_change', + oldCategoryIds, + newCategoryIds, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records brand changes in article history. + */ +export async function recordBrandChangeHistory(runner, { + articleId, + companyuserId, + date, + oldBrandName, + newBrandName, +}) { + if (oldBrandName === newBrandName) return; + + const text = `Marke geändert von "${oldBrandName ?? ''}" auf "${newBrandName ?? ''}"`; + const meta = { + type: 'brand_change', + oldBrandName: oldBrandName ?? null, + newBrandName: newBrandName ?? null, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records EAN changes in article history. + */ +export async function recordEanChangeHistory(runner, { + articleId, + companyuserId, + date, + oldEans, + newEans, +}) { + const oldStr = (oldEans || []).join(', '); + const newStr = [...(newEans || [])].sort().join(', '); + if (oldStr === newStr) return; + + const text = `EANs geändert von "${oldStr}" auf "${newStr}"`; + const meta = { + type: 'ean_change', + oldEans, + newEans, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records weight changes in article history. + */ +export async function recordWeightChangeHistory(runner, { + articleId, + companyuserId, + date, + oldWeight, + newWeight, +}) { + if (Number(oldWeight) === Number(newWeight)) return; + + const text = `Gewicht geändert von "${oldWeight}" auf "${newWeight}"`; + const meta = { + type: 'weight_change', + oldWeight: Number(oldWeight), + newWeight: Number(newWeight), + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records track_stock changes in article history. + */ +export async function recordTrackStockChangeHistory(runner, { + articleId, + companyuserId, + date, + oldTrack, + newTrack, +}) { + if (Boolean(oldTrack) === Boolean(newTrack)) return; + + const text = `Bestandsführung geändert von "${oldTrack ? 'Ja' : 'Nein'}" auf "${newTrack ? 'Ja' : 'Nein'}"`; + const meta = { + type: 'track_stock_change', + oldTrackStock: Boolean(oldTrack), + newTrackStock: Boolean(newTrack), + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records general article field updates (name, unit, description, track_stock). + */ +export async function recordArticleUpdateHistory(runner, { + articleId, + companyuserId, + date, + historyLines, + changes, +}) { + if (!historyLines || !historyLines.length) return; + + const text = historyLines.join('\n'); + const meta = { + type: 'article_update', + changes: changes ?? historyLines, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records initial article creation. + */ +export async function recordArticleCreatedHistory(runner, { + articleId, + companyuserId, + date, + name, +}) { + const text = `Artikel "${name}" angelegt`; + const meta = { + type: 'article_created', + name, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records article import. + */ +export async function recordArticleImportedHistory(runner, { + articleId, + companyuserId, + date, + name, +}) { + const text = `Artikel "${name}" importiert`; + const meta = { + type: 'article_imported', + name, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + date, + text, + meta, + }); +} + +/** + * Records conversion of an article to parent article. + */ +export async function recordParentConvertedHistory(runner, { + articleId, + companyuserId, + attributeName, + existingPrices, +}) { + const historyLines = [ + attributeName + ? `Artikel zu Vaterartikel konvertiert (Varianten-Attribut: "${attributeName}")` + : 'Artikel zu Vaterartikel konvertiert', + ]; + if (existingPrices && 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(', ')}`); + } + const text = historyLines.join('\n'); + const meta = { + type: 'parent_converted', + attributeName: attributeName ?? null, + deletedPricesCount: existingPrices ? existingPrices.length : 0, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records deactivation of parent article mode. + */ +export async function recordParentDeactivatedHistory(runner, { + articleId, + companyuserId, +}) { + const text = 'Vaterartikel-Modus deaktiviert'; + const meta = { + type: 'parent_deactivated', + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records adding a variant onto a parent article. + */ +export async function recordVariantAddedOnParentHistory(runner, { + parentId, + companyuserId, + childName, + childSku, +}) { + const text = `Variante "${childName}" (${childSku}) hinzugefügt`; + const meta = { + type: 'variant_added_to_parent', + childName, + childSku, + }; + return recordArticleHistory(runner, { + articleId: parentId, + companyuserId, + text, + meta, + }); +} + +/** + * Records assigning a child article to a parent article. + */ +export async function recordChildAssignedToParentHistory(runner, { + childId, + companyuserId, + childName, + childSku, + parentSku, + created = false, +}) { + const text = created + ? `Artikel "${childName}" (${childSku}) als Variante von "${parentSku}" angelegt` + : (childName + ? `Artikel "${childName}" (${childSku}) als Variante von "${parentSku}" zugewiesen` + : `Artikel "${childSku}" als Variante von "${parentSku}" zugewiesen`); + const meta = { + type: created ? 'child_created_under_parent' : 'child_assigned_to_parent', + childName: childName ?? null, + childSku, + parentSku, + }; + return recordArticleHistory(runner, { + articleId: childId, + companyuserId, + text, + meta, + }); +} + +/** + * Records unlinking a child article from a parent article on both child and parent. + */ +export async function recordChildUnlinkedFromParentHistory(runner, { + childId, + parentId, + companyuserId, + childSku, + childName, + parentSku, +}) { + await recordArticleHistory(runner, { + articleId: childId, + companyuserId, + text: `Artikel "${childSku}" von Vaterartikel "${parentSku}" getrennt`, + meta: { + type: 'child_unlinked', + childSku, + parentSku, + }, + }); + + await recordArticleHistory(runner, { + articleId: parentId, + companyuserId, + text: `Variante "${childName || childSku}" (${childSku}) getrennt`, + meta: { + type: 'variant_separated', + childSku, + childName: childName ?? null, + }, + }); +} + +/** + * Records deleting a variant child article from a parent. + */ +export async function recordChildDeletedFromParentHistory(runner, { + parentId, + companyuserId, + childName, + childSku, +}) { + const text = `Variante "${childName || childSku}" (${childSku}) gelöscht`; + const meta = { + type: 'variant_deleted', + childName: childName ?? null, + childSku, + }; + return recordArticleHistory(runner, { + articleId: parentId, + companyuserId, + text, + meta, + }); +} + +/** + * Records adding a supplier to an article. + */ +export async function recordSupplierAddHistory(runner, { + articleId, + companyuserId, + supplierName, + supplierSku, + netPrice, +}) { + const details = []; + if (supplierSku) details.push(`Artikelnr: "${supplierSku}"`); + if (netPrice !== null && netPrice !== undefined) details.push(`EK: ${netPrice}`); + const suffix = details.length ? ` (${details.join(', ')})` : ''; + const text = `Lieferant "${supplierName}" hinzugefügt${suffix}`; + const meta = { + type: 'supplier_add', + supplierName, + supplierSku: supplierSku ?? null, + netPrice: netPrice ?? null, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records updating supplier details on an article. + */ +export async function recordSupplierUpdateHistory(runner, { + articleId, + companyuserId, + historyLines, + supplierName, +}) { + if (!historyLines || !historyLines.length) return; + const text = historyLines.join('\n'); + const meta = { + type: 'supplier_update', + supplierName, + changes: historyLines, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records deleting a supplier from an article. + */ +export async function recordSupplierDeleteHistory(runner, { + articleId, + companyuserId, + supplierName, + supplierSku, +}) { + const suffix = supplierSku ? ` (Artikelnr: "${supplierSku}")` : ''; + const text = `Lieferant "${supplierName}" entfernt${suffix}`; + const meta = { + type: 'supplier_delete', + supplierName, + supplierSku: supplierSku ?? null, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records property changes on an article. + */ +export async function recordPropertyChangeHistory(runner, { + articleId, + companyuserId, + oldProps, + newProps, +}) { + const fmtProp = (p) => `${p.name}=${p.value}`; + const oldStr = (oldProps || []).map(fmtProp).join(', '); + const newStr = (newProps || []).map(fmtProp).join(', '); + if (oldStr === newStr) return; + + const text = `Eigenschaften geändert von [${oldStr}] auf [${newStr}]`; + const meta = { + type: 'property_change', + oldProps, + newProps, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records property deletion from an article. + */ +export async function recordPropertyDeleteHistory(runner, { + articleId, + companyuserId, + name, + value, +}) { + const text = `Eigenschaft "${name}" (${value}) gelöscht`; + const meta = { + type: 'property_delete', + name, + value, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records attribute value changes on an article. + */ +export async function recordAttributeChangeHistory(runner, { + articleId, + companyuserId, + oldAttrs, + newAttrs, +}) { + const fmtAttr = (a) => `${a.attributeName}: ${a.value}`; + const oldStr = (oldAttrs || []).map(fmtAttr).join(', '); + const newStr = (newAttrs || []).map(fmtAttr).join(', '); + if (oldStr === newStr) return; + + const text = `Attributwerte geändert von [${oldStr}] auf [${newStr}]`; + const meta = { + type: 'attribute_change', + oldAttrs, + newAttrs, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records Stückliste (component items) assignment on an article. + */ +export async function recordArticleListChangeHistory(runner, { + articleId, + companyuserId, + items, +}) { + const newNames = (items || []).map((r) => `${r.count}x ${r.name || r.sku}`); + const text = `Stückliste gesetzt: [${newNames.join(', ')}]`; + const meta = { + type: 'articlelist_change', + items, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} + +/** + * Records Optionsliste assignment on an article. + */ +export async function recordOptionsListChangeHistory(runner, { + articleId, + companyuserId, + options, +}) { + const newNames = (options || []).map((r) => r.name || r.sku); + const text = `Optionsliste gesetzt: [${newNames.join(', ')}]`; + const meta = { + type: 'optionslist_change', + options, + }; + return recordArticleHistory(runner, { + articleId, + companyuserId, + text, + meta, + }); +} diff --git a/src/store/articlelist/setItemsByArticleSku.js b/src/store/articlelist/setItemsByArticleSku.js index b5bffbd..1cb4bd5 100644 --- a/src/store/articlelist/setItemsByArticleSku.js +++ b/src/store/articlelist/setItemsByArticleSku.js @@ -1,9 +1,11 @@ import * as mssqlQueries from '../../providers/mssql/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 getItemsByArticleSku from './getItemsByArticleSku.js'; +import { + recordArticleListChangeHistory, + recordArticleHistory, +} from '../articlehistory/record.js'; import makeRunner from '../runner.js'; async function hasArticlelistCycle(runner, rootArticleId, candidateComponentId) { @@ -44,7 +46,6 @@ export default function setItemsByArticleSku(provider) { const getItems = getItemsByArticleSku(provider); const isSqlite = provider?.dialect === 'sqlite3'; 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. @@ -100,10 +101,11 @@ export default function setItemsByArticleSku(provider) { }); if (oldItemNames.length) { - await runner.query(insertHistorySql, { + await recordArticleHistory(runner, { articleId, companyuserId, text: `Stückliste geleert (war: [${oldItemNames.join(', ')}])`, + type: 'articlelist_cleared', }); } } @@ -221,11 +223,10 @@ export default function setItemsByArticleSku(provider) { 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, { + await recordArticleListChangeHistory(runner, { articleId, companyuserId, - text: `Stückliste gesetzt: [${newNames.join(', ')}]`, + items: result, }); return result; diff --git a/src/store/articleproperty/delete.js b/src/store/articleproperty/delete.js index 2f48edd..76ea7cc 100644 --- a/src/store/articleproperty/delete.js +++ b/src/store/articleproperty/delete.js @@ -4,16 +4,14 @@ import { import { DELETE_BY_ARTICLE_AND_PROPERTY_ID as SQLITE_DELETE, } from '../../providers/sqlite3/queries/articleproperty.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 { recordPropertyDeleteHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function deleteArticleProperty(provider) { const resolve = resolveArticleBySku(provider); const isSqlite = provider?.dialect === 'sqlite3'; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Deletes a property from an article by SKU and propertyId. @@ -41,10 +39,11 @@ export default function deleteArticleProperty(provider) { await runner.query(deleteSql, { articleId, propertyId }); if (prop) { - await runner.query(insertHistorySql, { + await recordPropertyDeleteHistory(runner, { articleId, companyuserId, - text: `Eigenschaft "${prop.name}" (${prop.value}) gelöscht`, + name: prop.name, + value: prop.value, }); } }; diff --git a/src/store/articleproperty/setByArticleSku.js b/src/store/articleproperty/setByArticleSku.js index 3868216..983e717 100644 --- a/src/store/articleproperty/setByArticleSku.js +++ b/src/store/articleproperty/setByArticleSku.js @@ -12,9 +12,8 @@ import { import { GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP, } 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 { recordPropertyChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setByArticleSku(provider) { @@ -23,7 +22,6 @@ export default function setByArticleSku(provider) { const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; 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. @@ -119,16 +117,12 @@ 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}]`, - }); - } + await recordPropertyChangeHistory(runner, { + articleId, + companyuserId, + oldProps, + newProps: validated, + }); return inserted; }; diff --git a/src/store/articlesupplier/create.js b/src/store/articlesupplier/create.js index 968dd82..468bfa8 100644 --- a/src/store/articlesupplier/create.js +++ b/src/store/articlesupplier/create.js @@ -1,7 +1,5 @@ import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.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 taxEnsure from '../tax/ensure.js'; import { @@ -10,6 +8,7 @@ import { resolveCompanyDefaultTax, shapeArticleSupplier, } from './helpers.js'; +import { recordSupplierAddHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function create(provider) { @@ -18,7 +17,6 @@ export default function create(provider) { const isSqlite = provider?.dialect === 'sqlite3'; const queries = isSqlite ? sqliteQueries : mssqlQueries; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Assigns a supplier to an article, optionally creating a purchase price. @@ -150,21 +148,15 @@ export default function create(provider) { const supplierName = supplierRow?.supplierName ?? (entityId ? `Lieferant #${entityId}` : 'Lieferant'); - const details = []; const actualSku = supplierRow?.sku ?? supplierSku; - if (actualSku) { - details.push(`Artikelnr: "${actualSku}"`); - } const actualPrice = supplierRow?.priceNet ?? netPrice; - if (actualPrice !== null && actualPrice !== undefined) { - details.push(`EK: ${actualPrice}`); - } - const suffix = details.length ? ` (${details.join(', ')})` : ''; - await runner.query(insertHistorySql, { + await recordSupplierAddHistory(runner, { articleId, companyuserId, - text: `Lieferant "${supplierName}" hinzugefügt${suffix}`, + supplierName, + supplierSku: actualSku, + netPrice: actualPrice, }); return shapeArticleSupplier(supplierRow); diff --git a/src/store/articlesupplier/delete.js b/src/store/articlesupplier/delete.js index c161c86..84cd076 100644 --- a/src/store/articlesupplier/delete.js +++ b/src/store/articlesupplier/delete.js @@ -1,15 +1,13 @@ import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.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 { recordSupplierDeleteHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function del(provider) { const resolve = resolveArticleBySku(provider); const isSqlite = provider?.dialect === 'sqlite3'; const queries = isSqlite ? sqliteQueries : mssqlQueries; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Deletes a supplier assignment and cleans up its associated purchase price. @@ -55,16 +53,11 @@ export default function del(provider) { const supplierName = existing.supplierName ?? (existing.entityId ? `Lieferant #${existing.entityId}` : 'Lieferant'); - const details = []; - if (existing.sku) { - details.push(`Artikelnr: "${existing.sku}"`); - } - const suffix = details.length ? ` (${details.join(', ')})` : ''; - - await runner.query(insertHistorySql, { + await recordSupplierDeleteHistory(runner, { articleId, companyuserId, - text: `Lieferant "${supplierName}" entfernt${suffix}`, + supplierName, + supplierSku: existing.sku, }); }; diff --git a/src/store/articlesupplier/update.js b/src/store/articlesupplier/update.js index f610ee2..1a0d00e 100644 --- a/src/store/articlesupplier/update.js +++ b/src/store/articlesupplier/update.js @@ -1,7 +1,5 @@ import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.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 taxEnsure from '../tax/ensure.js'; import { @@ -9,6 +7,7 @@ import { resolveCompanyDefaultTax, shapeArticleSupplier, } from './helpers.js'; +import { recordSupplierUpdateHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function update(provider) { @@ -17,7 +16,6 @@ export default function update(provider) { const isSqlite = provider?.dialect === 'sqlite3'; const queries = isSqlite ? sqliteQueries : mssqlQueries; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Updates an existing supplier assignment and its associated purchase price. @@ -213,13 +211,12 @@ export default function update(provider) { } } - if (historyLines.length) { - await runner.query(insertHistorySql, { - articleId, - companyuserId, - text: historyLines.join('\n'), - }); - } + await recordSupplierUpdateHistory(runner, { + articleId, + companyuserId, + historyLines, + supplierName, + }); const loadRes = await runner.query(queries.GET_BY_ID_AND_ARTICLE_ID, { id, diff --git a/src/store/ledger/add.js b/src/store/ledger/add.js index 2923d81..675a540 100644 --- a/src/store/ledger/add.js +++ b/src/store/ledger/add.js @@ -2,6 +2,7 @@ import { createHash } from 'node:crypto'; import * as mssqlQueries from '../../providers/mssql/queries/ledger.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/ledger.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; +import { recordDeliveryHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export const INIT_HASH = 'TODO_TSE_BASED_INITHASH'; @@ -205,42 +206,20 @@ export default function add(provider) { !upperType.includes('LOSS') && !upperType.includes('WRITE_OFF')); - let historyText = transactionText; - if (isDelivery) { - const details = []; - if (manifestArticle.supplierName) { - details.push(`Lieferant: "${manifestArticle.supplierName}"`); - } - if (warehouseName) { - details.push(`Lager: "${warehouseName}"`); - } - if (manifestArticle.net != null && Number.isFinite(Number(manifestArticle.net))) { - const net = Number(manifestArticle.net); - const rebate = Number(manifestArticle.rebate ?? 0); - const taxrate = Number(manifestArticle.taxrate ?? 0); - const netAfterRebate = net * (1 - rebate / 100); - const brutto = netAfterRebate * (1 + taxrate / 100); - const ekBrutto = (Math.round(brutto * 100) / 100).toFixed(2); - details.push(`EK brutto: ${ekBrutto}`); - } - if (amount != null && Number.isFinite(Number(amount))) { - details.push(`Menge: ${amount}`); - } - const suffix = details.length ? ` (${details.join(', ')})` : ''; - const base = - transactionText?.trim() || - (manifestArticle.manifestNumber ? `Lieferung ${manifestArticle.manifestNumber}` : 'Lieferung'); - historyText = `${base}${suffix}`; - } else if (warehouseName && !transactionText.includes(warehouseName)) { - historyText = `${transactionText} (Lager: "${warehouseName}")`; - } - if (!isShipping) { for (const match of articleMatches.rows) { - await runner.query(queries.INSERT_ARTICLE_HISTORY, { + await recordDeliveryHistory(runner, { articleId: match.articleId, companyuserId, - text: historyText, + manifestNumber: manifestArticle.manifestNumber, + supplierName: manifestArticle.supplierName, + warehouseName, + net: manifestArticle.net, + rebate: manifestArticle.rebate, + taxrate: manifestArticle.taxrate, + amount, + transactionText, + isDelivery, }); } } diff --git a/src/store/optionslist/setOptionsByArticleSku.js b/src/store/optionslist/setOptionsByArticleSku.js index cd3b47d..56474f0 100644 --- a/src/store/optionslist/setOptionsByArticleSku.js +++ b/src/store/optionslist/setOptionsByArticleSku.js @@ -1,9 +1,11 @@ import * as mssqlQueries from '../../providers/mssql/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 getOptionsByArticleSku from './getOptionsByArticleSku.js'; +import { + recordOptionsListChangeHistory, + recordArticleHistory, +} from '../articlehistory/record.js'; import makeRunner from '../runner.js'; async function hasOptionslistCycle(runner, rootArticleId, candidateOptionId) { @@ -44,7 +46,6 @@ export default function setOptionsByArticleSku(provider) { const getOptions = getOptionsByArticleSku(provider); const isSqlite = provider?.dialect === 'sqlite3'; const queries = isSqlite ? sqliteQueries : mssqlQueries; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Sets or clears the options list for an article. @@ -100,10 +101,11 @@ export default function setOptionsByArticleSku(provider) { }); if (oldOptNames.length) { - await runner.query(insertHistorySql, { + await recordArticleHistory(runner, { articleId, companyuserId, text: `Optionsliste geleert (war: [${oldOptNames.join(', ')}])`, + type: 'optionslist_cleared', }); } } @@ -228,11 +230,10 @@ export default function setOptionsByArticleSku(provider) { 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, { + await recordOptionsListChangeHistory(runner, { articleId, companyuserId, - text: `Optionsliste gesetzt: [${newNames.join(', ')}]`, + options: result, }); return result; diff --git a/src/store/order/create.js b/src/store/order/create.js index 12d1a2d..2fcdc31 100644 --- a/src/store/order/create.js +++ b/src/store/order/create.js @@ -8,16 +8,15 @@ import { } from '../../providers/sqlite3/queries/manifestDoc.js'; import { FIND_ARTICLES_FOR_HISTORY as MSSQL_FIND_ARTICLES, - INSERT_ARTICLE_HISTORY as MSSQL_INSERT_HISTORY, } from '../../providers/mssql/queries/ledger.js'; import { FIND_ARTICLES_FOR_HISTORY as SQLITE_FIND_ARTICLES, - INSERT_ARTICLE_HISTORY as SQLITE_INSERT_HISTORY, } from '../../providers/sqlite3/queries/ledger.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import taxEnsure from '../tax/ensure.js'; import { insertLines, normalizeLines, parseDate } from '../manifestLines.js'; import { assertCustomerEntity, loadDoc } from '../manifestDoc/helpers.js'; +import { recordOrderHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function create(provider) { @@ -28,7 +27,6 @@ export default function create(provider) { const createManifestSql = isSqlite ? SQLITE_CREATE_MANIFEST : MSSQL_CREATE_MANIFEST; const orderType = isSqlite ? SQLITE_ORDER_TYPE : MSSQL_ORDER_TYPE; const findArticlesSql = isSqlite ? SQLITE_FIND_ARTICLES : MSSQL_FIND_ARTICLES; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Creates a new order for the user's selected company with its lines and records article history. @@ -98,6 +96,8 @@ export default function create(provider) { customerName = entRes.rows[0]?.name ?? null; } + const historyDate = body.createdAt ? createdAt : date; + for (const line of lines) { const sku = line.sku ? String(line.sku).trim() : ''; if (!sku) continue; @@ -107,21 +107,18 @@ export default function create(provider) { companyId, }); - const details = []; - if (customerName) { - details.push(`Kunde: "${customerName}"`); - } - if (line.amount != null && Number.isFinite(Number(line.amount))) { - details.push(`Menge: ${line.amount}`); - } - const suffix = details.length ? ` (${details.join(', ')})` : ''; - const historyText = `Auftrag ${number}${suffix}`; - for (const match of articleMatches.rows) { - await runner.query(insertHistorySql, { + await recordOrderHistory(runner, { articleId: match.articleId, companyuserId, - text: historyText, + date: historyDate, + orderId: manifestId, + number, + customerName, + amount: line.amount, + net: line.net, + rebate: line.rebate, + taxrate: line.taxrate, }); } } diff --git a/src/store/shipping/create.js b/src/store/shipping/create.js index 8ee456e..0422b84 100644 --- a/src/store/shipping/create.js +++ b/src/store/shipping/create.js @@ -1,12 +1,4 @@ import { queriesFor, requireId, requireText } from '../logistics/helpers.js'; -import { - FIND_ARTICLES_FOR_HISTORY as MSSQL_FIND_ARTICLES, - INSERT_ARTICLE_HISTORY as MSSQL_INSERT_HISTORY, -} from '../../providers/mssql/queries/ledger.js'; -import { - FIND_ARTICLES_FOR_HISTORY as SQLITE_FIND_ARTICLES, - INSERT_ARTICLE_HISTORY as SQLITE_INSERT_HISTORY, -} from '../../providers/sqlite3/queries/ledger.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import nextNumber from '../numbers/nextNumber.js'; import ledgerAdd from '../ledger/add.js'; @@ -27,9 +19,6 @@ export default function create(provider) { const allocateNumber = nextNumber(provider); const addLedger = ledgerAdd(provider); const q = queriesFor(provider); - const isSqlite = provider?.dialect === 'sqlite3'; - const findArticlesSql = isSqlite ? SQLITE_FIND_ARTICLES : MSSQL_FIND_ARTICLES; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Creates a shipping for a manifest (order). The number is allocated from the diff --git a/src/store/shipping/update.js b/src/store/shipping/update.js index 13ff49c..ec378da 100644 --- a/src/store/shipping/update.js +++ b/src/store/shipping/update.js @@ -1,12 +1,4 @@ import { queriesFor, requireId, requireText } from '../logistics/helpers.js'; -import { - FIND_ARTICLES_FOR_HISTORY as MSSQL_FIND_ARTICLES, - INSERT_ARTICLE_HISTORY as MSSQL_INSERT_HISTORY, -} from '../../providers/mssql/queries/ledger.js'; -import { - FIND_ARTICLES_FOR_HISTORY as SQLITE_FIND_ARTICLES, - INSERT_ARTICLE_HISTORY as SQLITE_INSERT_HISTORY, -} from '../../providers/sqlite3/queries/ledger.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import ledgerAdd from '../ledger/add.js'; import makeRunner from '../runner.js'; @@ -32,9 +24,6 @@ export default function update(provider) { const access = resolveSelectedCompanyAccess(provider); const addLedger = ledgerAdd(provider); const q = queriesFor(provider); - const isSqlite = provider?.dialect === 'sqlite3'; - const findArticlesSql = isSqlite ? SQLITE_FIND_ARTICLES : MSSQL_FIND_ARTICLES; - const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY; /** * Updates a shipping record. diff --git a/src/store/stock/adjust.js b/src/store/stock/adjust.js index ae1d1cb..602813d 100644 --- a/src/store/stock/adjust.js +++ b/src/store/stock/adjust.js @@ -1,9 +1,8 @@ import * as mssqlQueries from '../../providers/mssql/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 resolveArticleBySku from '../article/resolveArticleBySku.js'; +import { recordStockAdjustmentHistory } from '../articlehistory/record.js'; import nextNumber from '../numbers/nextNumber.js'; import ledgerAdd from '../ledger/add.js'; import makeRunner from '../runner.js'; @@ -25,7 +24,6 @@ export default function adjust(provider) { const addLedger = ledgerAdd(provider); const isSqlite = provider?.dialect === 'sqlite3'; 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", @@ -200,11 +198,11 @@ export default function adjust(provider) { // Write article history for the adjusted article if (item.articleId) { - const sign = item.amount > 0 ? '+' : ''; - await runner.query(insertHistorySql, { + await recordStockAdjustmentHistory(runner, { articleId: item.articleId, companyuserId, - text: `Bestandskorrektur ${sign}${item.amount} (${item.text})`, + amount: item.amount, + reason: item.text, }); } }