This commit is contained in:
seb
2026-10-22 23:33:00 +02:00
parent d6e5da93cb
commit e31566e6ee
4 changed files with 121 additions and 2 deletions

View File

@@ -240,6 +240,47 @@ async function run() {
console.log('Obituaries after setOnAll:', obituariesAfterSetOnAll); console.log('Obituaries after setOnAll:', obituariesAfterSetOnAll);
assert.equal(obituariesAfterSetOnAll.length, 0); 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'); console.log('✅ ALL POSTERMINALOBITUARY TESTS PASSED');
} finally { } finally {
try { try {

View File

@@ -254,6 +254,28 @@ INSERT INTO categoryarticle (category_id, article_id)
VALUES (@categoryId, @articleId) 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 = ` export const GET_BRAND_BY_COMPANY_AND_ID = `
SELECT id SELECT id
FROM brand FROM brand

View File

@@ -3,11 +3,13 @@ import { translateQueries } from './translate.js';
const queries = translateQueries(source, 'article'); const queries = translateQueries(source, 'article');
export const { 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, DELETE_CATEGORIES_BY_ARTICLE, DELETE_EANS_BY_ARTICLE, EAN_EXISTS, EAN_EXISTS_FOR_OTHER_ARTICLE,
GET_BRAND_BY_COMPANY_AND_ID, GET_BRAND_BY_COMPANY_AND_ID,
GET_CATEGORY_ID_BY_SKU, GET_CATEGORY_TREE_FLAT, GET_ID_BY_SKU, 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_PARENT_SORT,
SET_IS_PARENT, TOGGLE_PARENT, TOUCH_UPDATED_AT, UNLINK_CHILD, UPDATE_BRAND, UPDATE_PARENT_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT, 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, UPDATE_UNITAMOUNT, UPDATE_UNIT_AND_AMOUNT, UPDATE_WEIGHT,

View File

@@ -1,11 +1,17 @@
import { import {
ADD_CATEGORY as MSSQL_ADD_CATEGORY, ADD_CATEGORY as MSSQL_ADD_CATEGORY,
CLEAR_POSTERMINAL_OBITUARY as MSSQL_CLEAR_OBITUARY,
DELETE_CATEGORIES_BY_ARTICLE as MSSQL_DELETE_CATEGORIES, 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, TOUCH_UPDATED_AT as MSSQL_TOUCH,
} from '../../providers/mssql/queries/article.js'; } from '../../providers/mssql/queries/article.js';
import { import {
ADD_CATEGORY as SQLITE_ADD_CATEGORY, ADD_CATEGORY as SQLITE_ADD_CATEGORY,
CLEAR_POSTERMINAL_OBITUARY as SQLITE_CLEAR_OBITUARY,
DELETE_CATEGORIES_BY_ARTICLE as SQLITE_DELETE_CATEGORIES, 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, TOUCH_UPDATED_AT as SQLITE_TOUCH,
} from '../../providers/sqlite3/queries/article.js'; } from '../../providers/sqlite3/queries/article.js';
import resolveCategoryAccess from '../category/resolveCategoryAccess.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 addCategorySql = isSqlite ? SQLITE_ADD_CATEGORY : MSSQL_ADD_CATEGORY;
const deleteCategoriesSql = isSqlite ? SQLITE_DELETE_CATEGORIES : MSSQL_DELETE_CATEGORIES; const deleteCategoriesSql = isSqlite ? SQLITE_DELETE_CATEGORIES : MSSQL_DELETE_CATEGORIES;
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH; 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. * 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 }); await runner.query(deleteCategoriesSql, { articleId });
for (const categoryId of normalized) { 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 // Write history if categories changed
await recordCategoryChangeHistory(runner, { await recordCategoryChangeHistory(runner, {
articleId, articleId,