From 62c3e69ed44ffdb5d1528a6aea32210080d95dbe Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 1 Oct 2026 07:12:39 +0200 Subject: [PATCH] u --- src/store/article/setBrandIdBySku.js | 23 +++++++++++- src/store/article/setCategoryIdsBySku.js | 23 +++++++++++- src/store/article/setEansBySku.js | 23 +++++++++++- src/store/article/setPricesBySku.js | 28 +++++++++++++- src/store/article/setTrackStockBySku.js | 22 ++++++++++- src/store/article/setWeightBySku.js | 21 ++++++++++- .../articleattributevalue/setByArticleSku.js | 29 ++++++++++++++- src/store/articlelist/setItemsByArticleSku.js | 37 ++++++++++++++++++- src/store/articleproperty/setByArticleSku.js | 28 +++++++++++++- .../optionslist/setOptionsByArticleSku.js | 37 ++++++++++++++++++- src/store/stock/adjust.js | 15 +++++++- 11 files changed, 273 insertions(+), 13 deletions(-) diff --git a/src/store/article/setBrandIdBySku.js b/src/store/article/setBrandIdBySku.js index 6ac1ec9..0934685 100644 --- a/src/store/article/setBrandIdBySku.js +++ b/src/store/article/setBrandIdBySku.js @@ -6,6 +6,8 @@ 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 makeRunner from '../runner.js'; @@ -17,6 +19,7 @@ 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. @@ -34,7 +37,7 @@ export default function setBrandIdBySku(provider) { */ return async (googleAuthId, sku, brandId, tx) => { const runner = makeRunner(provider, tx); - const { articleId, languageId, companyId } = await resolve( + const { articleId, languageId, companyId, companyuserId } = await resolve( googleAuthId, sku, runner, @@ -44,7 +47,15 @@ export default function setBrandIdBySku(provider) { throw new Error('Invalid brandId'); } + // Read old brand for history + const oldBrandRes = await runner.query( + `SELECT b.name FROM article a LEFT JOIN brand b ON b.id = a.brand_id WHERE a.id = @articleId`, + { articleId }, + ); + const oldBrandName = oldBrandRes.rows[0]?.name ?? ''; + let resolvedBrandId = null; + let newBrandName = ''; if (brandId !== null) { const brand = await runner.query(getBrandSql, { brandId, @@ -54,6 +65,7 @@ export default function setBrandIdBySku(provider) { throw new Error('Brand not found'); } resolvedBrandId = brandId; + newBrandName = brand.rows[0].name ?? ''; } const result = await runner.query(updateBrandSql, { @@ -65,6 +77,15 @@ export default function setBrandIdBySku(provider) { throw new Error('Article not found'); } + // Write history if brand changed + if (oldBrandName !== newBrandName) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Marke geändert von "${oldBrandName}" auf "${newBrandName}"`, + }); + } + const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); diff --git a/src/store/article/setCategoryIdsBySku.js b/src/store/article/setCategoryIdsBySku.js index 4d6b5d8..34ce240 100644 --- a/src/store/article/setCategoryIdsBySku.js +++ b/src/store/article/setCategoryIdsBySku.js @@ -6,6 +6,8 @@ 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'; @@ -19,6 +21,7 @@ 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. @@ -52,12 +55,19 @@ export default function setCategoryIdsBySku(provider) { } const runSet = async (runner) => { - const { articleId, languageId, companyId } = await resolve( + const { articleId, languageId, companyId, companyuserId } = await resolve( googleAuthId, sku, runner, ); + // Read old category IDs for history + const oldCatRes = await runner.query( + 'SELECT category_id FROM categoryarticle WHERE article_id = @articleId ORDER BY category_id', + { articleId }, + ); + const oldCategoryIds = oldCatRes.rows.map((r) => r.category_id); + for (const categoryId of normalized) { // Ensures the category belongs to the same company as the article. const access = await categoryAccess(googleAuthId, categoryId, runner); @@ -75,6 +85,17 @@ export default function setCategoryIdsBySku(provider) { }); } + // Write history if categories changed + const oldStr = oldCategoryIds.join(', '); + const newStr = [...normalized].sort((a, b) => a - b).join(', '); + if (oldStr !== newStr) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Kategorien geändert von [${oldStr}] auf [${newStr}]`, + }); + } + const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); diff --git a/src/store/article/setEansBySku.js b/src/store/article/setEansBySku.js index 26bee3d..59de25a 100644 --- a/src/store/article/setEansBySku.js +++ b/src/store/article/setEansBySku.js @@ -6,6 +6,8 @@ import { ADD_EAN as SQLITE_ADD_EAN, DELETE_EANS_BY_ARTICLE as SQLITE_DELETE_EANS, } 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'; @@ -18,6 +20,7 @@ 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; /** * Replaces the EAN list of an article by its SKU. @@ -52,7 +55,14 @@ export default function setEansBySku(provider) { } const runSet = async (runner) => { - const { articleId, languageId } = await resolve(googleAuthId, sku, runner); + const { articleId, languageId, companyuserId } = await resolve(googleAuthId, sku, runner); + + // Read old EANs for history + const oldEanRes = await runner.query( + 'SELECT ean FROM articleean WHERE article_id = @articleId ORDER BY ean', + { articleId }, + ); + const oldEans = oldEanRes.rows.map((r) => r.ean); await runner.query(deleteEansSql, { articleId }); @@ -67,6 +77,17 @@ export default function setEansBySku(provider) { throw err; } + // Write history if EANs changed + const oldStr = oldEans.join(', '); + const newStr = [...normalized].sort().join(', '); + if (oldStr !== newStr) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `EANs geändert von "${oldStr}" auf "${newStr}"`, + }); + } + const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); diff --git a/src/store/article/setPricesBySku.js b/src/store/article/setPricesBySku.js index 78056b9..97e839a 100644 --- a/src/store/article/setPricesBySku.js +++ b/src/store/article/setPricesBySku.js @@ -6,6 +6,8 @@ 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'; @@ -55,6 +57,7 @@ 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. @@ -74,7 +77,7 @@ export default function setPricesBySku(provider) { const normalized = normalizePrices(prices); const runSet = async (runner) => { - const { articleId, languageId, posCustomerGroupId } = await resolve( + const { articleId, languageId, posCustomerGroupId, companyuserId } = await resolve( googleAuthId, sku, runner, @@ -88,6 +91,17 @@ export default function setPricesBySku(provider) { throw new Error('A father article cannot have prices'); } + // Read old prices for history + const oldPriceRes = await runner.query( + `SELECT p.net, t.rate AS taxRate, p.minmultiplier + FROM price p + LEFT JOIN tax t ON t.id = p.tax_id + WHERE p.article_id = @articleId AND p.customergroup_id = @posCustomerGroupId + ORDER BY p.minmultiplier ASC`, + { articleId, posCustomerGroupId }, + ); + const oldPrices = oldPriceRes.rows; + const taxIds = new Map(); for (const entry of normalized) { if (!taxIds.has(entry.tax)) { @@ -110,6 +124,18 @@ export default function setPricesBySku(provider) { }); } + // Write history if prices changed + const fmtPrice = (p) => `${p.net}@${p.taxRate ?? 0}%`; + const oldStr = oldPrices.map(fmtPrice).join(', '); + const newStr = normalized.map((e) => `${e.price}@${e.tax}%`).join(', '); + if (oldStr !== newStr) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Preise geändert von [${oldStr}] auf [${newStr}]`, + }); + } + const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); diff --git a/src/store/article/setTrackStockBySku.js b/src/store/article/setTrackStockBySku.js index 9103344..0444802 100644 --- a/src/store/article/setTrackStockBySku.js +++ b/src/store/article/setTrackStockBySku.js @@ -1,5 +1,7 @@ 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 makeRunner from '../runner.js'; @@ -11,6 +13,7 @@ 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. @@ -32,7 +35,15 @@ export default function setTrackStockBySku(provider) { } const runner = makeRunner(provider, tx); - const { articleId, languageId } = await resolve(googleAuthId, sku, runner); + const { articleId, languageId, companyuserId } = await resolve(googleAuthId, sku, runner); + + // Read old track_stock for history + const oldRes = await runner.query( + 'SELECT track_stock FROM article WHERE id = @articleId', + { articleId }, + ); + const oldTrack = Boolean(oldRes.rows[0]?.track_stock ?? 1); + const newTrack = Boolean(trackStock); const result = await runner.query(sql, { articleId, @@ -43,6 +54,15 @@ export default function setTrackStockBySku(provider) { throw new Error('Article not found'); } + // Write history if track_stock changed + if (oldTrack !== newTrack) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Bestandsführung geändert von "${oldTrack ? 'Ja' : 'Nein'}" auf "${newTrack ? 'Ja' : 'Nein'}"`, + }); + } + const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); diff --git a/src/store/article/setWeightBySku.js b/src/store/article/setWeightBySku.js index ed47d5a..72a98c7 100644 --- a/src/store/article/setWeightBySku.js +++ b/src/store/article/setWeightBySku.js @@ -1,5 +1,7 @@ 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 makeRunner from '../runner.js'; @@ -8,6 +10,7 @@ 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. @@ -29,7 +32,14 @@ export default function setWeightBySku(provider) { } const runner = makeRunner(provider, tx); - const { articleId, languageId } = await resolve(googleAuthId, sku, runner); + const { articleId, languageId, companyuserId } = await resolve(googleAuthId, sku, runner); + + // Read old weight for history + const oldRes = await runner.query( + 'SELECT weight FROM article WHERE id = @articleId', + { articleId }, + ); + const oldWeight = oldRes.rows[0]?.weight ?? 0; const result = await runner.query(sql, { articleId, @@ -40,6 +50,15 @@ export default function setWeightBySku(provider) { throw new Error('Article not found'); } + // Write history if weight changed + if (Number(oldWeight) !== weight) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Gewicht geändert von "${oldWeight}" auf "${weight}"`, + }); + } + const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); diff --git a/src/store/articleattributevalue/setByArticleSku.js b/src/store/articleattributevalue/setByArticleSku.js index 9ab535f..374c497 100644 --- a/src/store/articleattributevalue/setByArticleSku.js +++ b/src/store/articleattributevalue/setByArticleSku.js @@ -8,6 +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 makeRunner from '../runner.js'; @@ -17,6 +19,7 @@ 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. @@ -34,7 +37,19 @@ export default function setByArticleSku(provider) { } const runSet = async (runner) => { - const { articleId, companyId } = await resolve(googleAuthId, sku, runner); + const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); + + // Read old attribute values for history + const oldAttrRes = await runner.query( + `SELECT aav.attributevalue_id AS attributeValueId, an.name AS attributeName, av.value + FROM articleattributevalue aav + INNER JOIN attributevalue av ON av.id = aav.attributevalue_id + INNER JOIN attributename an ON an.id = av.attributename_id + WHERE aav.article_id = @articleId + ORDER BY aav.sort ASC, aav.id ASC`, + { articleId }, + ); + const oldAttrs = oldAttrRes.rows; const validated = []; const seenValues = new Set(); @@ -137,6 +152,18 @@ export default function setByArticleSku(provider) { }); } + // Write history if attribute values changed + const fmtAttr = (a) => `${a.attributeName}: ${a.value}`; + const oldStr = oldAttrs.map(fmtAttr).join(', '); + const newStr = validated.map(fmtAttr).join(', '); + if (oldStr !== newStr) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Attributwerte geändert von [${oldStr}] auf [${newStr}]`, + }); + } + return inserted; }; diff --git a/src/store/articlelist/setItemsByArticleSku.js b/src/store/articlelist/setItemsByArticleSku.js index 8920e17..368a67a 100644 --- a/src/store/articlelist/setItemsByArticleSku.js +++ b/src/store/articlelist/setItemsByArticleSku.js @@ -1,5 +1,7 @@ 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 makeRunner from '../runner.js'; @@ -42,6 +44,7 @@ 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. @@ -58,7 +61,7 @@ export default function setItemsByArticleSku(provider) { } const run = async (runner) => { - const { articleId, companyId } = await resolve(googleAuthId, sku, runner); + const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); const artRes = await runner.query( 'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId', @@ -76,6 +79,18 @@ export default function setItemsByArticleSku(provider) { // If items is empty: clear Stückliste if (items.length === 0) { if (article.articlelist_id != null) { + // Read old items for history before clearing + const oldItemRes = await runner.query( + `SELECT a.sku, at.name, ala.count + FROM articlelistarticle ala + INNER JOIN article a ON a.id = ala.article_id + LEFT JOIN articletext at ON at.article_id = a.id + WHERE ala.articlelist_id = @articlelistId + ORDER BY ala.sort ASC, ala.id ASC`, + { articlelistId: article.articlelist_id }, + ); + const oldItemNames = oldItemRes.rows.map((r) => `${r.count}x ${r.name || r.sku}`); + await runner.query(queries.SET_ARTICLE_ARTICLELIST_ID, { articleId, articlelistId: null, @@ -83,6 +98,14 @@ export default function setItemsByArticleSku(provider) { await runner.query(queries.DELETE_ARTICLELIST, { id: article.articlelist_id, }); + + if (oldItemNames.length) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Stückliste geleert (war: [${oldItemNames.join(', ')}])`, + }); + } } return []; } @@ -195,7 +218,17 @@ export default function setItemsByArticleSku(provider) { }); } - return getItems(googleAuthId, sku, runner); + const result = await getItems(googleAuthId, sku, runner); + + // Write history for Stückliste change + const newNames = result.map((r) => `${r.count}x ${r.name || r.sku}`); + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Stückliste gesetzt: [${newNames.join(', ')}]`, + }); + + return result; }; if (tx) { diff --git a/src/store/articleproperty/setByArticleSku.js b/src/store/articleproperty/setByArticleSku.js index 8d21df4..373953e 100644 --- a/src/store/articleproperty/setByArticleSku.js +++ b/src/store/articleproperty/setByArticleSku.js @@ -12,6 +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 makeRunner from '../runner.js'; @@ -21,6 +23,7 @@ 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. @@ -38,7 +41,18 @@ export default function setByArticleSku(provider) { } const runSet = async (runner) => { - const { articleId, companyId } = await resolve(googleAuthId, sku, runner); + const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); + + // Read old properties for history + const oldPropsRes = await runner.query( + `SELECT ap.property_id AS propertyId, pn.name, ap.value + FROM articleproperty ap + INNER JOIN propertyname pn ON pn.id = ap.property_id + WHERE ap.article_id = @articleId + ORDER BY ap.sort ASC, ap.id ASC`, + { articleId }, + ); + const oldProps = oldPropsRes.rows; const validated = []; const seenExclusiveProps = new Set(); @@ -97,6 +111,18 @@ export default function setByArticleSku(provider) { }); } + // Write history if properties changed + const fmtProp = (p) => `${p.name}=${p.value}`; + const oldStr = oldProps.map(fmtProp).join(', '); + const newStr = validated.map(fmtProp).join(', '); + if (oldStr !== newStr) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Eigenschaften geändert von [${oldStr}] auf [${newStr}]`, + }); + } + return inserted; }; diff --git a/src/store/optionslist/setOptionsByArticleSku.js b/src/store/optionslist/setOptionsByArticleSku.js index 4b6237c..1fda659 100644 --- a/src/store/optionslist/setOptionsByArticleSku.js +++ b/src/store/optionslist/setOptionsByArticleSku.js @@ -1,5 +1,7 @@ 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 makeRunner from '../runner.js'; @@ -42,6 +44,7 @@ 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. @@ -58,7 +61,7 @@ export default function setOptionsByArticleSku(provider) { } const run = async (runner) => { - const { articleId, companyId } = await resolve(googleAuthId, sku, runner); + const { articleId, companyId, companyuserId } = await resolve(googleAuthId, sku, runner); const artRes = await runner.query( 'SELECT id, is_father, articlelist_id, optionslist_id FROM article WHERE id = @articleId', @@ -76,6 +79,18 @@ export default function setOptionsByArticleSku(provider) { // If options is empty: clear optionslist if (options.length === 0) { if (article.optionslist_id != null) { + // Read old options for history before clearing + const oldOptRes = await runner.query( + `SELECT a.sku, at.name + FROM optionslistarticle ola + INNER JOIN article a ON a.id = ola.article_id + LEFT JOIN articletext at ON at.article_id = a.id + WHERE ola.optionslist_id = @optionslistId + ORDER BY ola.sort ASC, ola.id ASC`, + { optionslistId: article.optionslist_id }, + ); + const oldOptNames = oldOptRes.rows.map((r) => r.name || r.sku); + await runner.query(queries.SET_ARTICLE_OPTIONSLIST_ID, { articleId, optionslistId: null, @@ -83,6 +98,14 @@ export default function setOptionsByArticleSku(provider) { await runner.query(queries.DELETE_OPTIONSLIST, { id: article.optionslist_id, }); + + if (oldOptNames.length) { + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Optionsliste geleert (war: [${oldOptNames.join(', ')}])`, + }); + } } return []; } @@ -202,7 +225,17 @@ export default function setOptionsByArticleSku(provider) { }); } - return getOptions(googleAuthId, sku, runner); + const result = await getOptions(googleAuthId, sku, runner); + + // Write history for options list change + const newNames = result.map((r) => r.name || r.sku); + await runner.query(insertHistorySql, { + articleId, + companyuserId, + text: `Optionsliste gesetzt: [${newNames.join(', ')}]`, + }); + + return result; }; if (tx) { diff --git a/src/store/stock/adjust.js b/src/store/stock/adjust.js index 69e8d70..dfd5303 100644 --- a/src/store/stock/adjust.js +++ b/src/store/stock/adjust.js @@ -1,5 +1,7 @@ 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 nextNumber from '../numbers/nextNumber.js'; @@ -23,6 +25,7 @@ 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", @@ -107,7 +110,7 @@ export default function adjust(provider) { }); const run = async (runner, activeTx) => { - const { companyId } = await access(googleAuthId, runner); + const { companyId, companyuserId } = await access(googleAuthId, runner); // Validate all articles exist and belong to the user's company const resolvedArticles = []; @@ -228,6 +231,16 @@ export default function adjust(provider) { }); createdLedgerEntries.push(ledgerRow); + + // Write article history for the adjusted article + if (item.articleId) { + const sign = item.amount > 0 ? '+' : ''; + await runner.query(insertHistorySql, { + articleId: item.articleId, + companyuserId, + text: `Bestandskorrektur ${sign}${item.amount} (${item.text})`, + }); + } } return {