From e31566e6ee45b095ad66e0b085ae0be4c15f9dce Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 22 Oct 2026 23:33:00 +0200 Subject: [PATCH] u --- scripts/test-obituary.js | 41 ++++++++++++++++++ src/providers/mssql/queries/article.js | 22 ++++++++++ src/providers/sqlite3/queries/article.js | 6 ++- src/store/article/setCategoryIdsBySku.js | 54 ++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/scripts/test-obituary.js b/scripts/test-obituary.js index b1994de..f6a8549 100644 --- a/scripts/test-obituary.js +++ b/scripts/test-obituary.js @@ -240,6 +240,47 @@ async function run() { console.log('Obituaries after setOnAll:', obituariesAfterSetOnAll); assert.equal(obituariesAfterSetOnAll.length, 0); + // 14. Test setCategoryIdsBySku generates obituary when removed from POS-enabled category + console.log('14. Testing setCategoryIdsBySku obituary generation and clearing...'); + // Setup terminal: only has Cat A enabled + await store.posterminal.setOffAll(googleAuthId, terminal.id); + // Clear obituaries from setOffAll to start clean + provider.db.prepare('DELETE FROM posterminalobituary WHERE posterminal_id = ?').run(terminal.id); + await store.posterminal.setOn(googleAuthId, terminal.id, catA.id); + + // Create article in Cat A + const artMoving = await store.article.createUnderParent({ + googleAuthId, + parentId: catA.id, + name: 'Moving Article', + }); + // Should have 0 obituaries on terminal + let obits = provider.db + .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') + .all(terminal.id); + assert.equal(obits.length, 0); + + // Move article from Cat A to Cat B (which is not enabled on terminal) + console.log('Moving article to Cat B (not on terminal)...'); + await store.article.setCategoryIdsBySku(googleAuthId, artMoving.sku, [catB.id]); + + obits = provider.db + .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') + .all(terminal.id); + console.log('Obituaries after moving to Cat B:', obits); + assert.equal(obits.length, 1); + assert.deepEqual(obits[0], { entity_type: 'article', entity_id: artMoving.id }); + + // Move article back to Cat A (enabled on terminal) + console.log('Moving article back to Cat A (on terminal)...'); + await store.article.setCategoryIdsBySku(googleAuthId, artMoving.sku, [catA.id]); + + obits = provider.db + .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') + .all(terminal.id); + console.log('Obituaries after moving back to Cat A:', obits); + assert.equal(obits.length, 0); + console.log('✅ ALL POSTERMINALOBITUARY TESTS PASSED'); } finally { try { diff --git a/src/providers/mssql/queries/article.js b/src/providers/mssql/queries/article.js index 613d4b7..2d3853d 100644 --- a/src/providers/mssql/queries/article.js +++ b/src/providers/mssql/queries/article.js @@ -254,6 +254,28 @@ INSERT INTO categoryarticle (category_id, article_id) VALUES (@categoryId, @articleId) `; +export const LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE = ` +SELECT DISTINCT pt.id AS posterminal_id +FROM posterminal pt +INNER JOIN posterminalcategory pc ON pc.posterminal_id = pt.id +INNER JOIN categoryarticle ca ON ca.category_id = pc.category_id +WHERE ca.article_id = @articleId + AND pt.company_id = @companyId + AND pt.token IS NOT NULL +`; + +export const INSERT_POSTERMINAL_OBITUARY = ` +INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id) +VALUES (@posterminalId, @entityType, @entityId) +`; + +export const CLEAR_POSTERMINAL_OBITUARY = ` +DELETE FROM posterminalobituary +WHERE posterminal_id = @posterminalId + AND entity_type = @entityType + AND entity_id = @entityId +`; + export const GET_BRAND_BY_COMPANY_AND_ID = ` SELECT id FROM brand diff --git a/src/providers/sqlite3/queries/article.js b/src/providers/sqlite3/queries/article.js index 55168c4..aef17f0 100644 --- a/src/providers/sqlite3/queries/article.js +++ b/src/providers/sqlite3/queries/article.js @@ -3,11 +3,13 @@ import { translateQueries } from './translate.js'; const queries = translateQueries(source, 'article'); export const { - ADD_CATEGORY, ADD_EAN, ARTICLE_LIST_COLUMNS, CHECK_ARTICLE_WAREHOUSE_STOCKS, CHECK_HAS_WAREHOUSE, CREATE, CREATE_CHILD, + ADD_CATEGORY, ADD_EAN, ARTICLE_LIST_COLUMNS, CHECK_ARTICLE_WAREHOUSE_STOCKS, CHECK_HAS_WAREHOUSE, + CLEAR_POSTERMINAL_OBITUARY, CREATE, CREATE_CHILD, DELETE_CATEGORIES_BY_ARTICLE, DELETE_EANS_BY_ARTICLE, EAN_EXISTS, EAN_EXISTS_FOR_OTHER_ARTICLE, GET_BRAND_BY_COMPANY_AND_ID, GET_CATEGORY_ID_BY_SKU, GET_CATEGORY_TREE_FLAT, GET_ID_BY_SKU, - LIST_CHILDREN_BY_PARENT_ID, + INSERT_POSTERMINAL_OBITUARY, + LIST_CHILDREN_BY_PARENT_ID, LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE, SET_PARENT_SORT, SET_IS_PARENT, TOGGLE_PARENT, TOUCH_UPDATED_AT, UNLINK_CHILD, UPDATE_BRAND, UPDATE_PARENT_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT, UPDATE_UNITAMOUNT, UPDATE_UNIT_AND_AMOUNT, UPDATE_WEIGHT, diff --git a/src/store/article/setCategoryIdsBySku.js b/src/store/article/setCategoryIdsBySku.js index 1bc4f49..8d6cf62 100644 --- a/src/store/article/setCategoryIdsBySku.js +++ b/src/store/article/setCategoryIdsBySku.js @@ -1,11 +1,17 @@ import { ADD_CATEGORY as MSSQL_ADD_CATEGORY, + CLEAR_POSTERMINAL_OBITUARY as MSSQL_CLEAR_OBITUARY, DELETE_CATEGORIES_BY_ARTICLE as MSSQL_DELETE_CATEGORIES, + INSERT_POSTERMINAL_OBITUARY as MSSQL_INSERT_OBITUARY, + LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE as MSSQL_LIST_TERMINALS, TOUCH_UPDATED_AT as MSSQL_TOUCH, } from '../../providers/mssql/queries/article.js'; import { ADD_CATEGORY as SQLITE_ADD_CATEGORY, + CLEAR_POSTERMINAL_OBITUARY as SQLITE_CLEAR_OBITUARY, DELETE_CATEGORIES_BY_ARTICLE as SQLITE_DELETE_CATEGORIES, + INSERT_POSTERMINAL_OBITUARY as SQLITE_INSERT_OBITUARY, + LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE as SQLITE_LIST_TERMINALS, TOUCH_UPDATED_AT as SQLITE_TOUCH, } from '../../providers/sqlite3/queries/article.js'; import resolveCategoryAccess from '../category/resolveCategoryAccess.js'; @@ -23,6 +29,9 @@ export default function setCategoryIdsBySku(provider) { const addCategorySql = isSqlite ? SQLITE_ADD_CATEGORY : MSSQL_ADD_CATEGORY; const deleteCategoriesSql = isSqlite ? SQLITE_DELETE_CATEGORIES : MSSQL_DELETE_CATEGORIES; const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH; + const listTerminalsSql = isSqlite ? SQLITE_LIST_TERMINALS : MSSQL_LIST_TERMINALS; + const insertObituarySql = isSqlite ? SQLITE_INSERT_OBITUARY : MSSQL_INSERT_OBITUARY; + const clearObituarySql = isSqlite ? SQLITE_CLEAR_OBITUARY : MSSQL_CLEAR_OBITUARY; /** * Replaces the category membership of an article by its SKU. @@ -77,6 +86,15 @@ export default function setCategoryIdsBySku(provider) { } } + // Find paired POS terminals where this article was visible before the category change + const terminalsBeforeRes = await runner.query(listTerminalsSql, { + articleId, + companyId, + }); + const terminalsBefore = new Set( + terminalsBeforeRes.rows.map((r) => r.posterminal_id), + ); + await runner.query(deleteCategoriesSql, { articleId }); for (const categoryId of normalized) { @@ -86,6 +104,42 @@ export default function setCategoryIdsBySku(provider) { }); } + // Find paired POS terminals where this article is visible after the category change + const terminalsAfterRes = await runner.query(listTerminalsSql, { + articleId, + companyId, + }); + const terminalsAfter = new Set( + terminalsAfterRes.rows.map((r) => r.posterminal_id), + ); + + // 1. Terminals that had the article enabled before, but no longer have it in any enabled category: + // Record an obituary so POS sync removes the orphaned product. + for (const posterminalId of terminalsBefore) { + if (!terminalsAfter.has(posterminalId)) { + await runner.query(clearObituarySql, { + posterminalId, + entityType: 'article', + entityId: articleId, + }); + await runner.query(insertObituarySql, { + posterminalId, + entityType: 'article', + entityId: articleId, + }); + } + } + + // 2. Terminals where the article IS enabled now: + // Clear any pending obituary so POS sync does not delete the active product. + for (const posterminalId of terminalsAfter) { + await runner.query(clearObituarySql, { + posterminalId, + entityType: 'article', + entityId: articleId, + }); + } + // Write history if categories changed await recordCategoryChangeHistory(runner, { articleId,