import * as mssqlArticle from '../../providers/mssql/queries/article.js'; import * as sqliteArticle from '../../providers/sqlite3/queries/article.js'; import resolveArticleBySku from './resolveArticleBySku.js'; import articleDelete from './delete.js'; import { recordChildUnlinkedFromParentHistory, recordChildDeletedFromParentHistory, } from '../articlehistory/record.js'; import makeRunner from '../runner.js'; export default function removeChild(provider) { const resolve = resolveArticleBySku(provider); const del = articleDelete(provider); const isSqlite = provider?.dialect === 'sqlite3'; const queries = isSqlite ? sqliteArticle : mssqlArticle; /** * Removes a child article from a parent article, either by unlinking or deleting it. * * @param {number} googleAuthId - The id of the Google auth user. * @param {string} parentSku - The parent article SKU. * @param {string} childSku - The child article SKU. * @param {object} [options] * @param {boolean} [options.deleteArticle=false] - Whether to delete the child article entity entirely. * @param {boolean} [options.cascade=true] - Whether to cascade delete child subtrees when deleteArticle is true. * @param {object} [tx] - Optional transaction. * @returns {Promise<{success: boolean, parentSku: string, childSku: string, deleted: boolean}>} */ return async (googleAuthId, parentSku, childSku, options = {}, tx) => { const run = async (runner) => { const { articleId: parentId, languageId, companyuserId } = await resolve( googleAuthId, parentSku, runner, ); const { articleId: childId } = await resolve(googleAuthId, childSku, runner); // Verify child relationship const childRes = await runner.query( 'SELECT parentarticle_id FROM article WHERE id = @childId', { childId }, ); const child = childRes.rows[0]; if (!child || child.parentarticle_id !== parentId) { throw new Error('Child article does not belong to this parent'); } // Fetch child name before any deletion (needed for history regardless of path) const childNameRes = await runner.query( `SELECT at.name FROM articletext at INNER JOIN article a ON a.id = at.article_id WHERE a.id = @childId AND at.language_id = @languageId`, { childId, languageId }, ); const childName = childNameRes.rows[0]?.name || childSku; const recompactParentSort = async () => { const remainingChildrenRes = await runner.query( 'SELECT id, parentsort FROM article WHERE parentarticle_id = @parentId ORDER BY parentsort ASC, id ASC', { parentId }, ); for (let i = 0; i < remainingChildrenRes.rows.length; i++) { const childRow = remainingChildrenRes.rows[i]; if (Number(childRow.parentsort) !== i) { await runner.query(queries.SET_PARENT_SORT, { articleId: childRow.id, parentArticleId: parentId, parentsort: i, }); } } }; if (options.deleteArticle) { const cascade = options.cascade !== undefined ? Boolean(options.cascade) : true; await del(googleAuthId, childSku, { cascade }, runner); await recompactParentSort(); // History on parent (child article is gone so we can only log on parent) await recordChildDeletedFromParentHistory(runner, { parentId, companyuserId, childName, childSku, }); // Touch parent article updated_at so POS and other consumers refresh parent options await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId }); return { success: true, parentSku, childSku, deleted: true, }; } // Fetch parent's attribute name id const parentRes = await runner.query( 'SELECT parentattributename_id FROM article WHERE id = @parentId', { parentId }, ); const parentAttributeNameId = parentRes.rows[0]?.parentattributename_id; // Unlink child from parent await runner.query(queries.UNLINK_CHILD, { articleId: childId, parentArticleId: parentId, }); // Remove the child's attribute value link for the parent's attribute if (parentAttributeNameId) { await runner.query( `DELETE FROM articleattributevalue WHERE article_id = @childId AND attributevalue_id IN ( SELECT id FROM attributevalue WHERE attribute_id = @parentAttributeNameId )`, { childId, parentAttributeNameId }, ); } await recompactParentSort(); // Add history on child and parent await recordChildUnlinkedFromParentHistory(runner, { childId, parentId, companyuserId, childSku, childName, parentSku, }); // Touch parent article updated_at so POS and other consumers refresh parent options await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId }); return { success: true, parentSku, childSku, deleted: false, }; }; if (tx) { return run(makeRunner(provider, tx)); } if (typeof provider?.transaction === 'function') { return provider.transaction(run); } return run(makeRunner(provider)); }; }