import { CREATE as MSSQL_CREATE_PRICE, DELETE_BY_ARTICLE_AND_CUSTOMERGROUP as MSSQL_DELETE_PRICE, } from '../../providers/mssql/queries/price.js'; import { CREATE as SQLITE_CREATE_PRICE, DELETE_BY_ARTICLE_AND_CUSTOMERGROUP as SQLITE_DELETE_PRICE, } from '../../providers/sqlite3/queries/price.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) { if (!Array.isArray(prices)) { throw new Error('Invalid prices'); } const seen = new Set(); const normalized = []; for (const entry of prices) { const rawTax = entry?.taxRate ?? entry?.tax; const minmultiplier = entry?.minmultiplier === undefined || entry?.minmultiplier === null ? 1 : entry.minmultiplier; if ( typeof rawTax !== 'number' || !Number.isFinite(rawTax) || rawTax < 0 || typeof minmultiplier !== 'number' || !Number.isFinite(minmultiplier) || minmultiplier <= 0 ) { throw new Error('Invalid prices'); } let netPrice; if (typeof entry?.net === 'number' && Number.isFinite(entry.net) && entry.net >= 0) { netPrice = entry.net; } else if (typeof entry?.price === 'number' && Number.isFinite(entry.price) && entry.price >= 0) { netPrice = entry.price; } else if (typeof entry?.gross === 'number' && Number.isFinite(entry.gross) && entry.gross >= 0) { netPrice = +Number(entry.gross / (1 + rawTax / 100)).toFixed(4); } else { throw new Error('Invalid prices'); } const key = String(minmultiplier); if (seen.has(key)) { throw new Error('Invalid prices'); } seen.add(key); normalized.push({ price: netPrice, tax: rawTax, minmultiplier }); } return normalized; } export default function setPricesBySku(provider) { const resolve = resolveArticleBySku(provider); const ensureTax = taxEnsure(provider); const load = queryBySku(provider); const isSqlite = provider?.dialect === 'sqlite3'; const createPriceSql = isSqlite ? SQLITE_CREATE_PRICE : MSSQL_CREATE_PRICE; const deletePriceSql = isSqlite ? SQLITE_DELETE_PRICE : MSSQL_DELETE_PRICE; /** * Replaces the price list of an article by its SKU. * * @param {number} googleAuthId - The id of the Google auth user. * @param {string} sku - The article SKU. * @param {Array<{net?: number, gross?: number, price?: number, taxRate?: number, tax?: number, minmultiplier?: number}>} prices - The price entries (net or gross, taxRate or tax percentage, minmultiplier defaults to 1). * @param {object} [tx] - Optional transaction to run within. * @returns {Promise} The updated article. * @throws {Error} If the prices are invalid or the article is not found. * * @example * const article = await store.article.setPricesBySku(42, 'SKU-1', [{ net: 10.0, taxRate: 19, minmultiplier: 1 }]); * // or with gross: * const article = await store.article.setPricesBySku(42, 'SKU-1', [{ gross: 11.9, taxRate: 19 }]); * // => { id, sku, name, ... } */ return async (googleAuthId, sku, prices, tx) => { const normalized = normalizePrices(prices); const runSet = async (runner) => { const { articleId, languageId, posCustomerGroupId, companyuserId } = await resolve( googleAuthId, sku, runner, ); if (!posCustomerGroupId) { throw new Error('POS customer group required'); } const artCheck = await runner.query('SELECT is_parent FROM article WHERE id = @articleId', { articleId }); if (artCheck.rows[0]?.is_parent) { throw new Error('A parent article cannot have prices'); } // Read old prices for history 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)) { taxIds.set(entry.tax, await ensureTax(entry.tax, runner)); } } await runner.query(deletePriceSql, { articleId, customerGroupId: posCustomerGroupId, }); for (const entry of normalized) { await runner.query(createPriceSql, { taxId: taxIds.get(entry.tax), articleId, minmultiplier: entry.minmultiplier, customerGroupId: posCustomerGroupId, net: entry.price, }); } // Write history if prices changed await recordPriceChangeHistory(runner, { articleId, companyuserId, oldPrices, newPrices: normalized, }); const article = await load(articleId, languageId, runner); if (!article) { throw new Error('Article not found'); } return article; }; if (tx) { return runSet(makeRunner(provider, tx)); } if (typeof provider?.transaction === 'function') { return provider.transaction(runSet); } return runSet(makeRunner(provider)); }; }