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'; import { GET_DETAIL_BY_ARTICLE_ID as SQLITE_GET_DETAIL } from '../../providers/sqlite3/queries/articleDetail.js'; import { GET_FULL_BY_SI as MSSQL_GET_UNIT } from '../../providers/mssql/queries/unit.js'; import { GET_FULL_BY_SI as SQLITE_GET_UNIT } from '../../providers/sqlite3/queries/unit.js'; import queryBySku from './queryBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import makeRunner from '../runner.js'; import slugify from '../slugify.js'; function formatUnitLabel(amount, symbol) { const sym = symbol ?? ''; if (amount == null || !Number.isFinite(Number(amount))) { return sym; } return `${Number(amount)}${sym}`; } export default function updateBySku(provider) { const resolve = resolveArticleBySku(provider); const load = queryBySku(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; /** * Updates an article's name and/or unit (SI and amount), recording history. * * @param {number} googleAuthId - The id of the Google auth user. * @param {string} sku - The article SKU. * @param {object} patch - The update patch. * @param {string} [patch.name] - The new article name. * @param {string} [patch.unitSi] - The new unit SI code. * @param {number} [patch.unitamount] - The new unit amount (must be > 0). * @param {boolean|number} [patch.track_stock] - Whether inventory stock is tracked. * @param {object} [tx] - Optional transaction to run within. * @returns {Promise} The updated article. * @throws {Error} If no valid field is provided or the article is not found. * * @example * const article = await store.article.updateBySku(42, 'SKU-1', { name: 'Cola 1L', unitSi: 'l', unitamount: 1, track_stock: true }); * // => { id, sku, name, ... } */ return async (googleAuthId, sku, patch = {}, tx) => { const hasName = Object.prototype.hasOwnProperty.call(patch, 'name'); const hasUnitamount = Object.prototype.hasOwnProperty.call(patch, 'unitamount'); const hasUnitSi = Object.prototype.hasOwnProperty.call(patch, 'unitSi'); const hasTrackStock = Object.prototype.hasOwnProperty.call(patch, 'track_stock') || Object.prototype.hasOwnProperty.call(patch, 'trackStock'); if (!hasName && !hasUnitamount && !hasUnitSi && !hasTrackStock) { throw new Error('Name required'); } if (hasName && !patch.name?.trim()) { throw new Error('Name required'); } if (hasUnitamount && (!Number.isFinite(patch.unitamount) || patch.unitamount <= 0)) { throw new Error('Invalid unitamount'); } const rawTrackStock = Object.prototype.hasOwnProperty.call(patch, 'track_stock') ? patch.track_stock : patch.trackStock; if (hasTrackStock && typeof rawTrackStock !== 'boolean' && rawTrackStock !== 0 && rawTrackStock !== 1) { throw new Error('Invalid track_stock'); } const runUpdate = async (runner) => { const { articleId, languageId, companyId, companyuserId } = await resolve( googleAuthId, sku, runner, ); const currentResult = await runner.query(getDetailSql, { articleId, languageId, }); const current = currentResult.rows[0]; if (!current) { throw new Error('Article not found'); } let unitId; let newUnitSymbol = current.unitSymbol ?? current.unitSi ?? ''; if (hasUnitSi) { if (!patch.unitSi?.trim()) { throw new Error('Unit not found'); } const unit = await runner.query(getUnitSql, { companyId, si: patch.unitSi.trim(), languageId, }); const unitRow = unit.rows[0]; unitId = unitRow?.id; if (!unitId) { throw new Error('Unit not found'); } newUnitSymbol = unitRow.symbol ?? unitRow.si ?? ''; } const historyLines = []; if (hasName) { const oldName = current.name ?? ''; const newName = patch.name.trim(); if (oldName !== newName) { historyLines.push(`Name geändert von "${oldName}" auf "${newName}"`); } } if (hasUnitSi || hasUnitamount) { const oldLabel = formatUnitLabel( current.unitamount, current.unitSymbol ?? current.unitSi ?? '', ); const newLabel = formatUnitLabel( hasUnitamount ? patch.unitamount : current.unitamount, newUnitSymbol, ); if (oldLabel !== newLabel) { historyLines.push(`Einheit geändert von "${oldLabel}" auf "${newLabel}"`); } } if (hasTrackStock) { const oldTrack = Boolean(current.track_stock ?? 1); const newTrack = Boolean(rawTrackStock); if (oldTrack !== newTrack) { historyLines.push( `Bestandsführung geändert von "${oldTrack ? 'Ja' : 'Nein'}" auf "${newTrack ? 'Ja' : 'Nein'}"`, ); } } if (hasName) { const slug = slugify(patch.name); const update = await runner.query(updateTextSql, { articleId, languageId, name: patch.name.trim(), slug, }); if (!update.rowsAffected) { throw new Error('Article not found'); } } if (hasUnitSi && hasUnitamount) { await runner.query(articleQueries.UPDATE_UNIT_AND_AMOUNT, { articleId, unitId, unitamount: patch.unitamount, }); } else if (hasUnitSi) { await runner.query(articleQueries.UPDATE_UNIT, { articleId, unitId, }); } else if (hasUnitamount) { await runner.query(articleQueries.UPDATE_UNITAMOUNT, { articleId, unitamount: patch.unitamount, }); } if (hasTrackStock) { await runner.query(articleQueries.UPDATE_TRACK_STOCK, { articleId, trackStock: Boolean(rawTrackStock) ? 1 : 0, }); } if (historyLines.length) { await runner.query(insertHistorySql, { articleId, companyuserId, text: historyLines.join('\n'), }); } const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); } return article; }; if (tx) { return runUpdate(makeRunner(provider, tx)); } if (typeof provider?.transaction === 'function') { return provider.transaction(runUpdate); } return runUpdate(makeRunner(provider)); }; }