diff --git a/scratch/test-category-subcategories.js b/scratch/test-category-subcategories.js new file mode 100644 index 0000000..34082f9 --- /dev/null +++ b/scratch/test-category-subcategories.js @@ -0,0 +1,171 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import LibDashStore from '../src/index.js'; +import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; + +async function test() { + console.log('--- Testing setOn and setOff with subcategories ---'); + + const tempDir = mkdtempSync(join(tmpdir(), 'test-posterminal-subcats-')); + const dbFile = join(tempDir, 'test.db'); + const storageDir = join(tempDir, 'storage'); + + const provider = new BetterSqlite3Provider({ + filename: dbFile, + storageDir, + }); + + await provider.initialize(); + const store = await new LibDashStore(provider); + + try { + const googleAuthId = await store.google_auth.add('subcat-tester@example.com'); + const company = await store.company.create({ + name: 'SubCat Corp', + iso639: 'de', + taxrate: 19, + reducedtaxrate: 7, + googleAuthId, + }); + await store.google_auth.setSelectedCompany(googleAuthId, company.id); + + const terminal = await store.posterminal.upsert(googleAuthId, { + name: 'POS 1', + pairingpin: '1234', + }); + await store.posterminal.pair(terminal.id, { + token: 'pos-token-subcat', + clientserial: 'SN-001', + pairedAt: new Date().toISOString(), + }); + + // Create Category Tree: + // Root -> Cat 4 -> SubCat 41 -> SubCat 411 + // Root -> Cat 5 + const cat4 = await store.category.createUnderParent({ + googleAuthId, + parentId: company.rootCategoryId, + name: 'Getränke', + }); + const subCat41 = await store.category.createUnderParent({ + googleAuthId, + parentId: cat4.id, + name: 'Alkoholfrei', + }); + const subCat411 = await store.category.createUnderParent({ + googleAuthId, + parentId: subCat41.id, + name: 'Cola', + }); + const cat5 = await store.category.createUnderParent({ + googleAuthId, + parentId: company.rootCategoryId, + name: 'Snacks', + }); + + // Create articles in various categories + const artCola = await store.article.createUnderParent({ + googleAuthId, + parentId: subCat411.id, + name: 'Cola 0.5L', + }); + + const artSnack = await store.article.createUnderParent({ + googleAuthId, + parentId: cat5.id, + name: 'Chips', + }); + + // Set old timestamps on categories and articles to test updated_at touch + provider.db.prepare("UPDATE category SET updated_at = '2020-01-01T00:00:00.000Z'").run(); + provider.db.prepare("UPDATE article SET updated_at = '2020-01-01T00:00:00.000Z'").run(); + + console.log('1. Testing setOn on parent category Cat 4 (should enable Cat 4, SubCat 41, and SubCat 411)...'); + const onResult = await store.posterminal.setOn(googleAuthId, terminal.id, cat4.id); + console.log('Category IDs after setOn(cat4):', onResult.categoryIds); + + assert(onResult.categoryIds.includes(cat4.id), 'Should include cat4'); + assert(onResult.categoryIds.includes(subCat41.id), 'Should include subCat41'); + assert(onResult.categoryIds.includes(subCat411.id), 'Should include subCat411'); + assert(!onResult.categoryIds.includes(cat5.id), 'Should NOT include cat5'); + + // Check that categories in the subtree were touched + const catsAfterOn = provider.db.prepare('SELECT id, updated_at FROM category WHERE id IN (?, ?, ?)').all(cat4.id, subCat41.id, subCat411.id); + for (const c of catsAfterOn) { + assert.notEqual(c.updated_at, '2020-01-01T00:00:00.000Z', `Category ${c.id} updated_at should be touched on setOn`); + } + + // Check that articles in the subtree were touched + const artAfterOn = provider.db.prepare('SELECT id, updated_at FROM article WHERE id = ?').get(artCola.id); + assert.notEqual(artAfterOn.updated_at, '2020-01-01T00:00:00.000Z', 'Article updated_at should be touched on setOn'); + + // Cat 5 article should NOT be touched + const artSnackAfterOn = provider.db.prepare('SELECT id, updated_at FROM article WHERE id = ?').get(artSnack.id); + assert.equal(artSnackAfterOn.updated_at, '2020-01-01T00:00:00.000Z', 'Unrelated article updated_at should not be touched'); + + console.log('2. Also enabling Cat 5...'); + await store.posterminal.setOn(googleAuthId, terminal.id, cat5.id); + + // Reset timestamps to test setOff touch + provider.db.prepare("UPDATE category SET updated_at = '2020-01-01T00:00:00.000Z'").run(); + provider.db.prepare("UPDATE article SET updated_at = '2020-01-01T00:00:00.000Z'").run(); + + console.log('3. Testing setOff on parent category Cat 4 (should disable Cat 4, SubCat 41, and SubCat 411)...'); + const offResult = await store.posterminal.setOff(googleAuthId, terminal.id, cat4.id); + console.log('Category IDs after setOff(cat4):', offResult.categoryIds); + + assert(!offResult.categoryIds.includes(cat4.id), 'Should NOT include cat4'); + assert(!offResult.categoryIds.includes(subCat41.id), 'Should NOT include subCat41'); + assert(!offResult.categoryIds.includes(subCat411.id), 'Should NOT include subCat411'); + assert(offResult.categoryIds.includes(cat5.id), 'Should still include cat5'); + + // Check that categories in the subtree were touched on setOff + const catsAfterOff = provider.db.prepare('SELECT id, updated_at FROM category WHERE id IN (?, ?, ?)').all(cat4.id, subCat41.id, subCat411.id); + for (const c of catsAfterOff) { + assert.notEqual(c.updated_at, '2020-01-01T00:00:00.000Z', `Category ${c.id} updated_at should be touched on setOff`); + } + + // Check that article in the subtree was touched on setOff + const artAfterOff = provider.db.prepare('SELECT id, updated_at FROM article WHERE id = ?').get(artCola.id); + assert.notEqual(artAfterOff.updated_at, '2020-01-01T00:00:00.000Z', 'Article updated_at should be touched on setOff'); + + // Verify obituaries generated + const obituariesAfter = provider.db + .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ? ORDER BY id ASC') + .all(terminal.id); + console.log('Obituaries after setOff:', obituariesAfter); + + const catObituaryIds = obituariesAfter.filter(o => o.entity_type === 'category').map(o => o.entity_id); + assert(catObituaryIds.includes(cat4.id), 'Should have obituary for cat4'); + assert(catObituaryIds.includes(subCat41.id), 'Should have obituary for subCat41'); + assert(catObituaryIds.includes(subCat411.id), 'Should have obituary for subCat411'); + + const artObituaryIds = obituariesAfter.filter(o => o.entity_type === 'article').map(o => o.entity_id); + assert(artObituaryIds.includes(artCola.id), 'Should have obituary for artCola'); + assert(!artObituaryIds.includes(artSnack.id), 'Should NOT have obituary for artSnack'); + + console.log('4. Testing setOn on an intermediate subcategory (SubCat 41)...'); + const subOnResult = await store.posterminal.setOn(googleAuthId, terminal.id, subCat41.id); + console.log('Category IDs after setOn(subCat41):', subOnResult.categoryIds); + assert(!subOnResult.categoryIds.includes(cat4.id), 'Parent cat4 should not be enabled when enabling subCat41'); + assert(subOnResult.categoryIds.includes(subCat41.id), 'subCat41 should be enabled'); + assert(subOnResult.categoryIds.includes(subCat411.id), 'Child subCat411 should be enabled'); + + console.log('✅ ALL SUBCATEGORY SET_ON / SET_OFF TESTS PASSED'); + } finally { + try { + rmSync(tempDir, { recursive: true, force: true }); + } catch {} + } +} + +test() + .then(() => { + process.exit(0); + }) + .catch(err => { + console.error('Test failed:', err); + process.exit(1); + }); diff --git a/src/providers/mssql/queries/posterminal.js b/src/providers/mssql/queries/posterminal.js index 0b02293..303e78f 100644 --- a/src/providers/mssql/queries/posterminal.js +++ b/src/providers/mssql/queries/posterminal.js @@ -93,20 +93,32 @@ FROM posterminalcategory WHERE posterminal_id = @posterminalId `; -/** setOn: insert if not exists (single terminal + single category). */ +/** setOn: enable a category and all its subcategories on a terminal. */ export const SET_ON = ` -IF NOT EXISTS ( - SELECT 1 FROM posterminalcategory - WHERE posterminal_id = @posterminalId AND category_id = @categoryId +;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id ) INSERT INTO posterminalcategory (posterminal_id, category_id) -VALUES (@posterminalId, @categoryId) +SELECT @posterminalId, cats.id +FROM cats +WHERE NOT EXISTS ( + SELECT 1 FROM posterminalcategory pc + WHERE pc.posterminal_id = @posterminalId AND pc.category_id = cats.id +) `; -/** setOff: remove a single category from a single terminal. */ +/** setOff: remove a category and all its subcategories from a terminal. */ export const SET_OFF = ` +;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id +) DELETE FROM posterminalcategory -WHERE posterminal_id = @posterminalId AND category_id = @categoryId +WHERE posterminal_id = @posterminalId + AND category_id IN (SELECT id FROM cats) `; /** setOnAll: enable ALL company categories on one terminal. */ @@ -170,7 +182,7 @@ WHERE EXISTS ( WHERE ca.article_id = a.id AND pc.posterminal_id = @posterminalId ) -AND DATEDIFF_BIG(second, '1970-01-01', a.updated_at) > @cursor +AND (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) > @cursor `; export const COUNT_CATEGORIES = ` @@ -219,7 +231,7 @@ WHERE a.articlelist_id IS NOT NULL WHERE ca.article_id = a.id AND pc.posterminal_id = @posterminalId ) - AND DATEDIFF_BIG(second, '1970-01-01', a.updated_at) > @cursor + AND (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) > @cursor `; export const MAX_ORDER_ID = ` @@ -263,7 +275,7 @@ SELECT TOP (@limit) COALESCE(pr.net, 0) AS netPrice, COALESCE(ptax.rate, ctax.rate, 0) AS taxRate, a.created_at AS createdAt, - DATEDIFF_BIG(second, '1970-01-01', a.updated_at) AS lastChanged, + (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) AS lastChanged, COALESCE( ( SELECT TOP 1 p.hash @@ -321,6 +333,7 @@ SELECT TOP (@limit) ) AS sort, a.track_stock AS trackStock, CASE + WHEN a.track_stock = 0 THEN 0 WHEN a.articlelist_id IS NOT NULL AND EXISTS ( SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id ) THEN COALESCE(( @@ -339,6 +352,7 @@ SELECT TOP (@limit) OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id) ) WHERE m.company_id = a.company_id + AND l.warehouse_id IS NOT NULL GROUP BY ca.id ) comp_stock ON comp_stock.comp_id = ala.article_id WHERE ala.articlelist_id = a.articlelist_id @@ -350,6 +364,7 @@ SELECT TOP (@limit) INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id INNER JOIN manifest m ON m.id = ma.manifest_id WHERE m.company_id = a.company_id + AND l.warehouse_id IS NOT NULL AND ( ma.sku = a.sku OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id) @@ -373,8 +388,8 @@ WHERE EXISTS ( WHERE ca.article_id = a.id AND pc.posterminal_id = @posterminalId ) -AND DATEDIFF_BIG(second, '1970-01-01', a.updated_at) > @cursor -ORDER BY DATEDIFF_BIG(second, '1970-01-01', a.updated_at) ASC, a.id ASC +AND (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) > @cursor +ORDER BY (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) ASC `; export const LIST_POS_PRODUCT_CATEGORIES = ` diff --git a/src/providers/sqlite3/queries/posterminal.js b/src/providers/sqlite3/queries/posterminal.js index bafc7f4..1c8cbc2 100644 --- a/src/providers/sqlite3/queries/posterminal.js +++ b/src/providers/sqlite3/queries/posterminal.js @@ -4,7 +4,7 @@ import { translateQueries } from './translate.js'; const queries = translateQueries(source, 'posterminal'); export const { CREATE, DELETE_BY_COMPANY_AND_ID, GET_BY_ID, LIST_BY_COMPANY, - LIST_CATEGORY_IDS, POSTERMINAL_IDS_BY_COMPANY, SET_OFF, SET_OFF_ALL, UPDATE, + LIST_CATEGORY_IDS, POSTERMINAL_IDS_BY_COMPANY, SET_OFF_ALL, UPDATE, FIND_BY_PAIRINGPIN, PAIR, SET_MAX_KNOWN_OBITUARY_ID, FIND_BY_TOKEN, COUNT_PRODUCTS, COUNT_CATEGORIES, COUNT_CUSTOMERS, COUNT_CUSTOMER_GROUPS, COUNT_COMPOSITE_PRODUCTS, MAX_ORDER_ID, @@ -14,9 +14,24 @@ export const { } = queries; export const SET_ON = ` -INSERT INTO posterminalcategory (posterminal_id, category_id) -VALUES (@posterminalId, @categoryId) -ON CONFLICT (posterminal_id, category_id) DO NOTHING +WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id +) +INSERT OR IGNORE INTO posterminalcategory (posterminal_id, category_id) +SELECT @posterminalId, id FROM cats +`; + +export const SET_OFF = ` +WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id +) +DELETE FROM posterminalcategory +WHERE posterminal_id = @posterminalId + AND category_id IN (SELECT id FROM cats) `; export const SET_ON_ALL = ` @@ -25,9 +40,8 @@ WITH RECURSIVE cats AS ( UNION ALL SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id ) -INSERT INTO posterminalcategory (posterminal_id, category_id) -SELECT @posterminalId, id FROM cats WHERE 1 -ON CONFLICT (posterminal_id, category_id) DO NOTHING +INSERT OR IGNORE INTO posterminalcategory (posterminal_id, category_id) +SELECT @posterminalId, id FROM cats `; export const LIST_POS_CUSTOMER_GROUPS = ` @@ -148,7 +162,7 @@ SELECT COALESCE(pr.net, 0) AS netPrice, COALESCE(ptax.rate, ctax.rate, 0) AS taxRate, a.created_at AS createdAt, - CAST(strftime('%s', a.updated_at) AS INTEGER) AS lastChanged, + (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) AS lastChanged, COALESCE( ( SELECT p.hash @@ -210,6 +224,7 @@ SELECT ) AS sort, a.track_stock AS trackStock, CASE + WHEN a.track_stock = 0 THEN 0 WHEN a.articlelist_id IS NOT NULL AND EXISTS ( SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id ) THEN COALESCE(( @@ -228,6 +243,7 @@ SELECT OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id) ) WHERE m.company_id = a.company_id + AND l.warehouse_id IS NOT NULL GROUP BY ca.id ) comp_stock ON comp_stock.comp_id = ala.article_id WHERE ala.articlelist_id = a.articlelist_id @@ -239,6 +255,7 @@ SELECT INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id INNER JOIN manifest m ON m.id = ma.manifest_id WHERE m.company_id = a.company_id + AND l.warehouse_id IS NOT NULL AND ( ma.sku = a.sku OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id) @@ -262,8 +279,8 @@ WHERE EXISTS ( WHERE ca.article_id = a.id AND pc.posterminal_id = @posterminalId ) -AND CAST(strftime('%s', a.updated_at) AS INTEGER) > @cursor -ORDER BY lastChanged ASC, a.id ASC +AND (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) > @cursor +ORDER BY (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) ASC LIMIT @limit `; diff --git a/src/store/posterminal/getCompositeProductCount.js b/src/store/posterminal/getCompositeProductCount.js index 8ba08cf..ebacda1 100644 --- a/src/store/posterminal/getCompositeProductCount.js +++ b/src/store/posterminal/getCompositeProductCount.js @@ -5,7 +5,9 @@ import makeRunner from '../runner.js'; function normalizeCursor(cursor) { const n = Number(cursor) || 0; if (n <= 0) return 0; - return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n); + if (n < 1e11) return Math.floor(n * 1000000); + if (n < 1e14) return Math.floor((n / 1000) * 1000000); + return Math.floor(n); } export default function getCompositeProductCount(provider) { diff --git a/src/store/posterminal/getProductCount.js b/src/store/posterminal/getProductCount.js index 67d6eaf..85372bc 100644 --- a/src/store/posterminal/getProductCount.js +++ b/src/store/posterminal/getProductCount.js @@ -5,7 +5,9 @@ import makeRunner from '../runner.js'; function normalizeCursor(cursor) { const n = Number(cursor) || 0; if (n <= 0) return 0; - return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n); + if (n < 1e11) return Math.floor(n * 1000000); + if (n < 1e14) return Math.floor((n / 1000) * 1000000); + return Math.floor(n); } export default function getProductCount(provider) { diff --git a/src/store/posterminal/getProductList.js b/src/store/posterminal/getProductList.js index 74de1fb..191dcf6 100644 --- a/src/store/posterminal/getProductList.js +++ b/src/store/posterminal/getProductList.js @@ -19,7 +19,9 @@ import makeRunner from '../runner.js'; function normalizeCursor(cursor) { const n = Number(cursor) || 0; if (n <= 0) return 0; - return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n); + if (n < 1e11) return Math.floor(n * 1000000); + if (n < 1e14) return Math.floor((n / 1000) * 1000000); + return Math.floor(n); } function formatDateTime(value) { diff --git a/src/store/posterminal/setOff.js b/src/store/posterminal/setOff.js index f6651cd..55430fa 100644 --- a/src/store/posterminal/setOff.js +++ b/src/store/posterminal/setOff.js @@ -21,25 +21,121 @@ export default function setOff(provider) { ? SQLITE_LIST_CATEGORY_IDS : MSSQL_LIST_CATEGORY_IDS; - const addCategoryObituarySql = ` - INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id) - VALUES (@posterminalId, 'category', @categoryId) - `; - - const addArticleObituariesSql = ` - INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id) - SELECT DISTINCT @posterminalId, 'article', ca.article_id - FROM categoryarticle ca - WHERE ca.category_id = @categoryId - AND NOT EXISTS ( - SELECT 1 - FROM categoryarticle ca2 - INNER JOIN posterminalcategory pc ON pc.category_id = ca2.category_id - WHERE ca2.article_id = ca.article_id - AND pc.posterminal_id = @posterminalId - AND pc.category_id <> @categoryId + const touchSql = isSqlite + ? `WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id ) - `; + UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') + WHERE id IN (SELECT id FROM cats)` + : `;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + UPDATE category SET updated_at = SYSUTCDATETIME() + WHERE id IN (SELECT id FROM cats)`; + + const touchArticlesSql = isSqlite + ? `WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + UPDATE article + SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') + WHERE id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL + )` + : `;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + UPDATE article + SET updated_at = SYSUTCDATETIME() + WHERE id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL + )`; + + const addCategoryObituarySql = isSqlite + ? `WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id) + SELECT pc.posterminal_id, 'category', pc.category_id + FROM posterminalcategory pc + WHERE pc.posterminal_id = @posterminalId + AND pc.category_id IN (SELECT id FROM cats)` + : `;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id) + SELECT pc.posterminal_id, 'category', pc.category_id + FROM posterminalcategory pc + WHERE pc.posterminal_id = @posterminalId + AND pc.category_id IN (SELECT id FROM cats)`; + + const addArticleObituariesSql = isSqlite + ? `WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id) + SELECT DISTINCT @posterminalId, 'article', ca.article_id + FROM categoryarticle ca + WHERE ca.category_id IN (SELECT id FROM cats) + AND NOT EXISTS ( + SELECT 1 + FROM categoryarticle ca2 + INNER JOIN posterminalcategory pc ON pc.category_id = ca2.category_id + WHERE ca2.article_id = ca.article_id + AND pc.posterminal_id = @posterminalId + AND pc.category_id NOT IN (SELECT id FROM cats) + )` + : `;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id) + SELECT DISTINCT @posterminalId, 'article', ca.article_id + FROM categoryarticle ca + WHERE ca.category_id IN (SELECT id FROM cats) + AND NOT EXISTS ( + SELECT 1 + FROM categoryarticle ca2 + INNER JOIN posterminalcategory pc ON pc.category_id = ca2.category_id + WHERE ca2.article_id = ca.article_id + AND pc.posterminal_id = @posterminalId + AND pc.category_id NOT IN (SELECT id FROM cats) + )`; /** * Removes a category assignment from a posterminal (turns it off). @@ -84,6 +180,9 @@ export default function setOff(provider) { categoryId, }); + await runner.query(touchSql, { categoryId }); + await runner.query(touchArticlesSql, { categoryId }); + const result = await runner.query(listCatIdsSql, { posterminalId }); return { diff --git a/src/store/posterminal/setOn.js b/src/store/posterminal/setOn.js index 84849d6..51a3e70 100644 --- a/src/store/posterminal/setOn.js +++ b/src/store/posterminal/setOn.js @@ -21,30 +21,110 @@ export default function setOn(provider) { ? SQLITE_LIST_CATEGORY_IDS : MSSQL_LIST_CATEGORY_IDS; const touchSql = isSqlite - ? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId" - : 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId'; - const touchArticlesSql = isSqlite - ? `UPDATE article - SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') - WHERE id IN ( - SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id = @categoryId - )` - : `UPDATE article - SET updated_at = SYSUTCDATETIME() - WHERE id IN ( - SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id = @categoryId - )`; - - const clearObituariesSql = ` - DELETE FROM posterminalobituary - WHERE posterminal_id = @posterminalId - AND ( - (entity_type = 'category' AND entity_id = @categoryId) - OR (entity_type = 'article' AND entity_id IN ( - SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id = @categoryId - )) + ? `WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id ) - `; + UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') + WHERE id IN (SELECT id FROM cats)` + : `;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + UPDATE category SET updated_at = SYSUTCDATETIME() + WHERE id IN (SELECT id FROM cats)`; + + const touchArticlesSql = isSqlite + ? `WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + UPDATE article + SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') + WHERE id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL + )` + : `;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + UPDATE article + SET updated_at = SYSUTCDATETIME() + WHERE id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL + )`; + + const clearObituariesSql = isSqlite + ? `WITH RECURSIVE cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + DELETE FROM posterminalobituary + WHERE posterminal_id = @posterminalId + AND ( + (entity_type = 'category' AND entity_id IN (SELECT id FROM cats)) + OR (entity_type = 'article' AND entity_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL + )) + )` + : `;WITH cats AS ( + SELECT id FROM category WHERE id = @categoryId + UNION ALL + SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id + ) + DELETE FROM posterminalobituary + WHERE posterminal_id = @posterminalId + AND ( + (entity_type = 'category' AND entity_id IN (SELECT id FROM cats)) + OR (entity_type = 'article' AND entity_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + UNION + SELECT child.id FROM article child + WHERE child.parentarticle_id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) + UNION + SELECT parent.parentarticle_id FROM article parent + WHERE parent.id IN ( + SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats) + ) AND parent.parentarticle_id IS NOT NULL + )) + )`; /** * Assigns a category to a posterminal (turns it on for that category).