import { DELETE_BY_ARTICLE_ID as MSSQL_DELETE, INSERT_ROW as MSSQL_INSERT, } from '../../providers/mssql/queries/articleproperty.js'; import { DELETE_BY_ARTICLE_ID as SQLITE_DELETE, INSERT_ROW as SQLITE_INSERT, } from '../../providers/sqlite3/queries/articleproperty.js'; import { GET_BY_COMPANY_AND_ID as MSSQL_GET_PROP, } from '../../providers/mssql/queries/propertyname.js'; import { GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP, } from '../../providers/sqlite3/queries/propertyname.js'; import { TOUCH_UPDATED_AT as MSSQL_TOUCH, ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC, } from '../../providers/mssql/queries/article.js'; import { TOUCH_UPDATED_AT as SQLITE_TOUCH, ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC, } from '../../providers/sqlite3/queries/article.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js'; import { recordPropertyChangeHistory } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function setByArticleSku(provider) { const resolve = resolveArticleBySku(provider); const isSqlite = provider?.dialect === 'sqlite3'; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; const getPropSql = isSqlite ? SQLITE_GET_PROP : MSSQL_GET_PROP; const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH; const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC; /** * Replaces all properties for an article by its SKU. * Enforces exclusive flag on property names. An exclusive property that already has a value * is never overwritten: a different value throws `Exclusive property cannot be overwritten: `. * * @param {number} googleAuthId - The id of the Google auth user. * @param {string} sku - The article SKU. * @param {Array<{propertyId: number, value: string, sort?: number}>} properties - The properties to set. * @param {object} [tx] - Optional transaction. * @returns {Promise>} */ return async (googleAuthId, sku, properties = [], tx) => { if (!Array.isArray(properties)) { throw new Error('Invalid properties array'); } const runSet = async (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(); for (let i = 0; i < properties.length; i++) { const item = properties[i]; const propertyId = Number(item.propertyId); if (!Number.isInteger(propertyId) || propertyId <= 0) { throw new Error('Invalid propertyId'); } const propRes = await runner.query(getPropSql, { id: propertyId, companyId }); const prop = propRes.rows[0]; if (!prop) { throw new Error(`Property not found: ${propertyId}`); } const isExclusive = Boolean(prop.exclusive); if (isExclusive) { if (seenExclusiveProps.has(propertyId)) { throw new Error(`Property "${prop.name}" is exclusive and can only have one value`); } seenExclusiveProps.add(propertyId); } const value = item.value != null ? String(item.value).trim() : ''; if (isExclusive) { const existing = oldProps.find((p) => p.propertyId === propertyId); if (existing && existing.value !== '' && existing.value !== value) { throw new Error(`Exclusive property cannot be overwritten: ${prop.name}`); } } const sort = item.sort != null && Number.isInteger(item.sort) ? item.sort : i; validated.push({ propertyId, name: prop.name, exclusive: isExclusive, value, sort, }); } await runner.query(deleteSql, { articleId }); const inserted = []; for (const item of validated) { const res = await runner.query(insertSql, { articleId, propertyId: item.propertyId, value: item.value, sort: item.sort, }); const id = res.rows[0]?.id ?? res.insertId; inserted.push({ id, articleId, propertyId: item.propertyId, name: item.name, exclusive: item.exclusive, value: item.value, sort: item.sort, }); } // Write history if properties changed await recordPropertyChangeHistory(runner, { articleId, companyuserId, oldProps, newProps: validated, }); await runner.query(touchArticleSql, { articleId }); await runner.query(enqueueSyncSql, { articleId }); return inserted; }; if (tx) { return runSet(makeRunner(provider, tx)); } if (typeof provider?.transaction === 'function') { return provider.transaction(runSet); } return runSet(makeRunner(provider)); }; }