import * as mssqlQueries from '../../providers/mssql/queries/articleDelete.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articleDelete.js'; import * as mssqlArticle from '../../providers/mssql/queries/article.js'; import * as sqliteArticle from '../../providers/sqlite3/queries/article.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import recordObituary from '../posterminal/recordObituary.js'; import makeRunner from '../runner.js'; export default function del(provider) { const resolve = resolveArticleBySku(provider); const recordObit = recordObituary(provider); const isSqlite = provider?.dialect === 'sqlite3'; const queries = isSqlite ? sqliteQueries : mssqlQueries; const articleQueries = isSqlite ? sqliteArticle : mssqlArticle; /** * Deletes an article by its SKU, refusing when it still has children unless cascade is true. * * @param {number} googleAuthId - The id of the Google auth user. * @param {string} sku - The article SKU. * @param {object|*} [optionsOrTx] - Options object ({ cascade?: boolean }) or optional transaction. * @param {object} [maybeTx] - Optional transaction if options was passed. * @returns {Promise} * @throws {Error} If the article has children (when cascade=false) or is not found. * * @example * await store.article.delete(42, 'SKU-1'); * await store.article.delete(42, 'SKU-1', { cascade: true }); */ return async (googleAuthId, sku, optionsOrTx, maybeTx) => { let options = {}; let tx; if (optionsOrTx && typeof optionsOrTx === 'object' && typeof optionsOrTx.query !== 'function') { options = optionsOrTx; tx = maybeTx; } else { tx = optionsOrTx; } const cascade = Boolean(options.cascade); const runDelete = async (runner) => { const { articleId, companyId } = await resolve(googleAuthId, sku, runner); const deleteArticleAndLists = async (id) => { const listIds = await runner.query(queries.GET_LIST_IDS, { articleId: id }); const { articlelistId, optionslistId } = listIds.rows[0] ?? {}; await recordObit({ companyId, entityType: 'article', entityId: id }, runner); await runner.query(queries.DELETE_PICS_BY_ARTICLE, { articleId: id }); // Touch any parent articles that used this article as an option before deleting it await runner.query(articleQueries.TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT, { articleId: id }); const deleted = await runner.query(queries.DELETE_BY_ID, { articleId: id }); if (!deleted.rowsAffected) { throw new Error('Article not found'); } if (articlelistId != null) { await runner.query(queries.DELETE_ARTICLELIST, { id: articlelistId }); } if (optionslistId != null) { await runner.query(queries.DELETE_OPTIONSLIST, { id: optionslistId }); } }; const deleteSubtree = async (id) => { const children = await runner.query(queries.GET_CHILDREN_IDS, { id }); for (const child of children.rows) { await deleteSubtree(child.id); } await deleteArticleAndLists(id); }; const parentRes = await runner.query( 'SELECT parentarticle_id FROM article WHERE id = @articleId', { articleId }, ); const parentArticleId = parentRes.rows[0]?.parentarticle_id; const childCount = await runner.query(queries.GET_CHILD_COUNT, { articleId }); if (childCount.rows[0].count > 0) { if (!cascade) { throw new Error('Article has children'); } await deleteSubtree(articleId); } else { await deleteArticleAndLists(articleId); } if (parentArticleId != null) { const remainingChildrenRes = await runner.query( 'SELECT id, parentsort FROM article WHERE parentarticle_id = @parentArticleId ORDER BY parentsort ASC, id ASC', { parentArticleId }, ); for (let i = 0; i < remainingChildrenRes.rows.length; i++) { const childRow = remainingChildrenRes.rows[i]; if (Number(childRow.parentsort) !== i) { await runner.query(articleQueries.SET_PARENT_SORT, { articleId: childRow.id, parentArticleId, parentsort: i, }); } } } }; if (tx) { return runDelete(makeRunner(provider, tx)); } if (typeof provider?.transaction === 'function') { return provider.transaction(runDelete); } return runDelete(makeRunner(provider)); }; }