162 lines
5.3 KiB
JavaScript
162 lines
5.3 KiB
JavaScript
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<object>} 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));
|
|
};
|
|
}
|
|
|