Compare commits
6 Commits
e9b4ac5d79
...
8002ca56e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8002ca56e6 | ||
|
|
8a085986e5 | ||
|
|
919713fa53 | ||
|
|
5fc316461d | ||
|
|
e31566e6ee | ||
|
|
d6e5da93cb |
171
scratch/test-category-subcategories.js
Normal file
171
scratch/test-category-subcategories.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
179
scratch/test-deposit-options.js
Normal file
179
scratch/test-deposit-options.js
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
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 run() {
|
||||||
|
console.log('--- Testing deposit via mandated optionslist ---');
|
||||||
|
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), 'test-deposit-'));
|
||||||
|
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('tester@example.com');
|
||||||
|
const company = await store.company.create({
|
||||||
|
name: 'Deposit Corp',
|
||||||
|
iso639: 'de',
|
||||||
|
taxrate: 19,
|
||||||
|
reducedtaxrate: 7,
|
||||||
|
googleAuthId,
|
||||||
|
});
|
||||||
|
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||||
|
|
||||||
|
// 1. Create a category
|
||||||
|
const cat = await store.category.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: company.rootCategoryId,
|
||||||
|
name: 'Getränke',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Create terminal and enable category
|
||||||
|
const terminal = await store.posterminal.upsert(googleAuthId, {
|
||||||
|
name: 'POS 1',
|
||||||
|
pairingpin: '1234',
|
||||||
|
});
|
||||||
|
await store.posterminal.pair(terminal.id, {
|
||||||
|
token: 'pos-token-xyz',
|
||||||
|
clientserial: 'SN-001',
|
||||||
|
pairedAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
await store.posterminal.setOn(googleAuthId, terminal.id, cat.id);
|
||||||
|
|
||||||
|
// 3. Create beverage article
|
||||||
|
const clubMate = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: cat.id,
|
||||||
|
name: 'Club Mate 0.5l',
|
||||||
|
sku: 'MATE-05',
|
||||||
|
});
|
||||||
|
await store.article.setPricesBySku(googleAuthId, clubMate.sku, [
|
||||||
|
{ net: 1.5, tax: 19 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 4. Create deposit article
|
||||||
|
const pfandArt = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: cat.id,
|
||||||
|
name: 'Mehrwegpfand 0,08 €',
|
||||||
|
sku: 'PFAND-0.08',
|
||||||
|
track_stock: 0,
|
||||||
|
});
|
||||||
|
await store.article.setPricesBySku(googleAuthId, pfandArt.sku, [
|
||||||
|
{ gross: 0.08, tax: 19 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 5. Query products BEFORE linking mandated option
|
||||||
|
let prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
let mateProd = prods.find((p) => p.sku === 'MATE-05');
|
||||||
|
let pfandProd = prods.find((p) => p.sku === 'PFAND-0.08');
|
||||||
|
|
||||||
|
assert.ok(mateProd, 'Club Mate should be listed');
|
||||||
|
assert.equal(mateProd.deposit, '0');
|
||||||
|
assert.equal(mateProd.d_price, '0.0');
|
||||||
|
assert.equal(mateProd.deposit_name, '');
|
||||||
|
|
||||||
|
assert.ok(pfandProd, 'Pfand article should be listed');
|
||||||
|
assert.equal(pfandProd.deposit, '0');
|
||||||
|
|
||||||
|
// 6. Link Mehrwegpfand as mandated option to Club Mate
|
||||||
|
console.log('Linking Mehrwegpfand as mandated option...');
|
||||||
|
await store.optionslist.setOptionsByArticleSku(googleAuthId, clubMate.sku, [
|
||||||
|
{
|
||||||
|
articleId: pfandArt.id,
|
||||||
|
sku: pfandArt.sku,
|
||||||
|
count: 1,
|
||||||
|
sort: 0,
|
||||||
|
mandated: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 7. Query products AFTER linking mandated option
|
||||||
|
prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
mateProd = prods.find((p) => p.sku === 'MATE-05');
|
||||||
|
|
||||||
|
console.log('Club Mate with deposit option:', {
|
||||||
|
sku: mateProd.sku,
|
||||||
|
deposit: mateProd.deposit,
|
||||||
|
d_price: mateProd.d_price,
|
||||||
|
deposit_name: mateProd.deposit_name,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(mateProd.deposit, '1');
|
||||||
|
assert.equal(mateProd.d_price, '0.08');
|
||||||
|
assert.equal(mateProd.deposit_name, 'Mehrwegpfand 0,08 €');
|
||||||
|
|
||||||
|
// 8. Test option name change touching parent article
|
||||||
|
console.log('Testing option name change touching parent article...');
|
||||||
|
const mateBeforeRename = (await provider.query('SELECT updated_at FROM article WHERE id = @id', { id: clubMate.id })).rows[0].updated_at;
|
||||||
|
await new Promise((r) => setTimeout(r, 1100)); // wait >1s for timestamp delta
|
||||||
|
|
||||||
|
await store.article.updateBySku(googleAuthId, pfandArt.sku, {
|
||||||
|
name: 'Glas-Mehrwegpfand 0,08 €',
|
||||||
|
});
|
||||||
|
|
||||||
|
const mateAfterRename = (await provider.query('SELECT updated_at FROM article WHERE id = @id', { id: clubMate.id })).rows[0].updated_at;
|
||||||
|
assert.notEqual(
|
||||||
|
mateBeforeRename,
|
||||||
|
mateAfterRename,
|
||||||
|
'Club Mate updated_at must be bumped when its option article is renamed'
|
||||||
|
);
|
||||||
|
|
||||||
|
prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
mateProd = prods.find((p) => p.sku === 'MATE-05');
|
||||||
|
assert.equal(mateProd.deposit_name, 'Glas-Mehrwegpfand 0,08 €');
|
||||||
|
console.log('Renamed deposit reflected on parent product:', mateProd.deposit_name);
|
||||||
|
|
||||||
|
// 9. Test option price change touching parent article
|
||||||
|
console.log('Testing option price change touching parent article...');
|
||||||
|
const mateBeforePrice = mateAfterRename;
|
||||||
|
await new Promise((r) => setTimeout(r, 1100));
|
||||||
|
|
||||||
|
await store.article.setPricesBySku(googleAuthId, pfandArt.sku, [
|
||||||
|
{ gross: 0.15, tax: 19 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mateAfterPrice = (await provider.query('SELECT updated_at FROM article WHERE id = @id', { id: clubMate.id })).rows[0].updated_at;
|
||||||
|
assert.notEqual(
|
||||||
|
mateBeforePrice,
|
||||||
|
mateAfterPrice,
|
||||||
|
'Club Mate updated_at must be bumped when its option article price changes'
|
||||||
|
);
|
||||||
|
|
||||||
|
prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
mateProd = prods.find((p) => p.sku === 'MATE-05');
|
||||||
|
assert.equal(mateProd.d_price, '0.15');
|
||||||
|
console.log('New deposit price reflected on parent product:', mateProd.d_price);
|
||||||
|
|
||||||
|
// 10. Test clearing options
|
||||||
|
console.log('Clearing options...');
|
||||||
|
await store.optionslist.setOptionsByArticleSku(googleAuthId, clubMate.sku, []);
|
||||||
|
prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
mateProd = prods.find((p) => p.sku === 'MATE-05');
|
||||||
|
|
||||||
|
assert.equal(mateProd.deposit, '0');
|
||||||
|
assert.equal(mateProd.d_price, '0.0');
|
||||||
|
assert.equal(mateProd.deposit_name, '');
|
||||||
|
|
||||||
|
console.log('✅ ALL DEPOSIT OPTIONS TESTS PASSED');
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch((err) => {
|
||||||
|
console.error('Test failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
159
scratch/test-inventory-touch.js
Normal file
159
scratch/test-inventory-touch.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
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 main() {
|
||||||
|
console.log('--- Testing updated_at touch on all inventory movements ---');
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), 'test-touch-'));
|
||||||
|
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('tester@example.com');
|
||||||
|
const company = await store.company.create({
|
||||||
|
name: 'Inventory Corp',
|
||||||
|
iso639: 'de',
|
||||||
|
taxrate: 19,
|
||||||
|
reducedtaxrate: 7,
|
||||||
|
googleAuthId,
|
||||||
|
});
|
||||||
|
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||||
|
|
||||||
|
// Create warehouse
|
||||||
|
const wh = await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
|
||||||
|
|
||||||
|
const cat = await store.category.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: company.rootCategoryId,
|
||||||
|
name: 'Waren',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create article with past updated_at
|
||||||
|
const art = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: cat.id,
|
||||||
|
sku: 'TEST-SKU',
|
||||||
|
name: 'Test Article',
|
||||||
|
track_stock: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const oldTime = '2020-01-01T00:00:00.000Z';
|
||||||
|
await provider.query(`UPDATE article SET updated_at = @oldTime WHERE id = @id`, { oldTime, id: art.id });
|
||||||
|
|
||||||
|
const getUpdatedAt = async (articleId) => {
|
||||||
|
const res = await provider.query(`SELECT updated_at FROM article WHERE id = @articleId`, { articleId });
|
||||||
|
return res.rows[0]?.updated_at;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialUpdatedAt = await getUpdatedAt(art.id);
|
||||||
|
assert.equal(initialUpdatedAt, oldTime, 'Should start with old updated_at');
|
||||||
|
|
||||||
|
// 1. Test store.stock.adjust
|
||||||
|
console.log('1. Testing store.stock.adjust touches article.updated_at...');
|
||||||
|
await store.stock.adjust(googleAuthId, {
|
||||||
|
sku: 'TEST-SKU',
|
||||||
|
warehouseId: wh.id,
|
||||||
|
amount: 2,
|
||||||
|
// reason omitted so it defaults to "Manual stock addition (+2)"
|
||||||
|
});
|
||||||
|
|
||||||
|
const afterAdjustUpdatedAt = await getUpdatedAt(art.id);
|
||||||
|
assert.notEqual(afterAdjustUpdatedAt, oldTime, 'stock.adjust should touch updated_at');
|
||||||
|
assert.ok(new Date(afterAdjustUpdatedAt).getTime() > new Date(oldTime).getTime());
|
||||||
|
|
||||||
|
// Check history was recorded with exact text
|
||||||
|
const historyRes = await provider.query(`SELECT text FROM articlehistory WHERE article_id = @id`, { id: art.id });
|
||||||
|
const adjustmentEntry = historyRes.rows.find((h) => h.text.includes('Bestandskorrektur +2 (Manual stock addition (+2))'));
|
||||||
|
assert.ok(adjustmentEntry, 'Should have exact article history entry');
|
||||||
|
|
||||||
|
// Reset updated_at to old time
|
||||||
|
await provider.query(`UPDATE article SET updated_at = @oldTime WHERE id = @id`, { oldTime, id: art.id });
|
||||||
|
|
||||||
|
// 2. Test Stückliste component touch
|
||||||
|
console.log('2. Testing Stückliste parent is touched when component has stock adjustment...');
|
||||||
|
const bundle = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: cat.id,
|
||||||
|
sku: 'BUNDLE-1',
|
||||||
|
name: 'Bundle 1',
|
||||||
|
track_stock: 0,
|
||||||
|
});
|
||||||
|
await store.articlelist.setItemsByArticleSku(googleAuthId, 'BUNDLE-1', [
|
||||||
|
{ sku: 'TEST-SKU', count: 1 },
|
||||||
|
]);
|
||||||
|
await provider.query(`UPDATE article SET updated_at = @oldTime WHERE id = @id`, { oldTime, id: bundle.id });
|
||||||
|
|
||||||
|
await store.stock.adjust(googleAuthId, {
|
||||||
|
sku: 'TEST-SKU',
|
||||||
|
warehouseId: wh.id,
|
||||||
|
amount: 5,
|
||||||
|
text: 'Restock component',
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundleUpdatedAt = await getUpdatedAt(bundle.id);
|
||||||
|
assert.notEqual(bundleUpdatedAt, oldTime, 'Stückliste bundle updated_at should be touched when component changes stock');
|
||||||
|
|
||||||
|
// 3. Test delivery acceptance touches article.updated_at
|
||||||
|
console.log('3. Testing delivery touches article.updated_at...');
|
||||||
|
const supplier = await store.supplier.create(googleAuthId, { name: 'Supplier Inc' });
|
||||||
|
const delivery = await store.delivery.create(googleAuthId, {
|
||||||
|
supplierId: supplier.id,
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
lines: [{ sku: 'TEST-SKU', amount: 10, net: 5, taxrate: 19 }],
|
||||||
|
});
|
||||||
|
await provider.query(`UPDATE article SET updated_at = @oldTime WHERE id = @id`, { oldTime, id: art.id });
|
||||||
|
|
||||||
|
await store.delivery.book(googleAuthId, { id: delivery.id, warehouseId: wh.id });
|
||||||
|
const deliveryUpdatedAt = await getUpdatedAt(art.id);
|
||||||
|
assert.notEqual(deliveryUpdatedAt, oldTime, 'delivery.book should touch article.updated_at');
|
||||||
|
|
||||||
|
// 4. Test customer shipment touches article.updated_at
|
||||||
|
console.log('4. Testing shipment touches article.updated_at...');
|
||||||
|
const groups = await store.customergroup.list(googleAuthId);
|
||||||
|
const customer = await store.entity.create(googleAuthId, { name: 'Customer A', customergroupId: groups[0].id });
|
||||||
|
const order = await store.order.create(googleAuthId, {
|
||||||
|
number: 'AUF-000001',
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
entityId: customer.id,
|
||||||
|
lines: [{ sku: 'TEST-SKU', amount: 3, net: 10, taxrate: 19 }],
|
||||||
|
});
|
||||||
|
const carrier = await store.carrier.create(googleAuthId, { name: 'DHL' });
|
||||||
|
await provider.query(`UPDATE article SET updated_at = @oldTime WHERE id = @id`, { oldTime, id: art.id });
|
||||||
|
|
||||||
|
await store.shipment.create(googleAuthId, {
|
||||||
|
orderId: order.id,
|
||||||
|
lines: [{ sku: 'TEST-SKU', amount: 3 }],
|
||||||
|
warehouseId: wh.id,
|
||||||
|
shipping: {
|
||||||
|
carrierId: carrier.id,
|
||||||
|
type: 'standard',
|
||||||
|
state: 'shipped',
|
||||||
|
warehouseId: wh.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const shipmentUpdatedAt = await getUpdatedAt(art.id);
|
||||||
|
assert.notEqual(shipmentUpdatedAt, oldTime, 'shipment.create should touch article.updated_at');
|
||||||
|
|
||||||
|
console.log('✅ ALL INVENTORY TOUCH TESTS PASSED');
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error('Test failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
164
scratch/test-variants.js
Normal file
164
scratch/test-variants.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
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 run() {
|
||||||
|
console.log('--- Testing POS father/child variants and options ---');
|
||||||
|
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), 'test-variants-'));
|
||||||
|
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('tester@example.com');
|
||||||
|
const company = await store.company.create({
|
||||||
|
name: 'Variant Corp',
|
||||||
|
iso639: 'de',
|
||||||
|
taxrate: 19,
|
||||||
|
reducedtaxrate: 7,
|
||||||
|
googleAuthId,
|
||||||
|
});
|
||||||
|
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||||
|
|
||||||
|
// 1. Create a category
|
||||||
|
const cat = await store.category.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: company.rootCategoryId,
|
||||||
|
name: 'Kleidung',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Create terminal and enable category
|
||||||
|
const terminal = await store.posterminal.upsert(googleAuthId, {
|
||||||
|
name: 'POS 1',
|
||||||
|
pairingpin: '1234',
|
||||||
|
});
|
||||||
|
await store.posterminal.pair(terminal.id, {
|
||||||
|
token: 'pos-token-xyz',
|
||||||
|
clientserial: 'SN-001',
|
||||||
|
pairedAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
await store.posterminal.setOn(googleAuthId, terminal.id, cat.id);
|
||||||
|
|
||||||
|
// 3. Create attribute name "Farbe"
|
||||||
|
const attrName = await store.attributename.create(googleAuthId, {
|
||||||
|
name: 'Farbe',
|
||||||
|
sort: 1,
|
||||||
|
exclusive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Create base article and convert to parent
|
||||||
|
const tShirt = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: cat.id,
|
||||||
|
name: 'T-Shirt Basic',
|
||||||
|
sku: 'TSHIRT',
|
||||||
|
});
|
||||||
|
await store.article.toggleParent(googleAuthId, tShirt.sku, {
|
||||||
|
isParent: true,
|
||||||
|
attributeNameId: attrName.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. Add children "Rot" and "Blau"
|
||||||
|
console.log('Adding children "Rot" and "Blau"...');
|
||||||
|
const childRot = await store.article.addChild(googleAuthId, tShirt.sku, {
|
||||||
|
value: 'Rot',
|
||||||
|
name: 'T-Shirt Basic - Rot',
|
||||||
|
});
|
||||||
|
const childBlau = await store.article.addChild(googleAuthId, tShirt.sku, {
|
||||||
|
value: 'Blau',
|
||||||
|
name: 'T-Shirt Basic - Blau',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. Query POS products
|
||||||
|
let prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
let parentProd = prods.find((p) => p.sku === 'TSHIRT');
|
||||||
|
let rotProd = prods.find((p) => p.sku === childRot.article.sku);
|
||||||
|
let blauProd = prods.find((p) => p.sku === childBlau.article.sku);
|
||||||
|
|
||||||
|
console.log('Parent product:', {
|
||||||
|
sku: parentProd.sku,
|
||||||
|
is_parent: parentProd.is_parent,
|
||||||
|
parent: parentProd.parent,
|
||||||
|
variants: parentProd.variants,
|
||||||
|
options: parentProd.options,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(parentProd.is_parent, '1');
|
||||||
|
assert.equal(parentProd.parent, '0');
|
||||||
|
assert.equal(parentProd.variants, '');
|
||||||
|
assert.deepEqual(parentProd.options, [{ name: 'Farbe', value: 'Rot,Blau' }]);
|
||||||
|
|
||||||
|
console.log('Child Rot:', {
|
||||||
|
sku: rotProd.sku,
|
||||||
|
is_parent: rotProd.is_parent,
|
||||||
|
parent: rotProd.parent,
|
||||||
|
variants: rotProd.variants,
|
||||||
|
sort: rotProd.sort,
|
||||||
|
options: rotProd.options,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(rotProd.is_parent, '0');
|
||||||
|
assert.equal(rotProd.parent, String(parentProd._id));
|
||||||
|
assert.equal(rotProd.variants, 'Rot');
|
||||||
|
assert.equal(rotProd.sort, '0');
|
||||||
|
assert.equal(rotProd.options, null);
|
||||||
|
|
||||||
|
console.log('Child Blau:', {
|
||||||
|
sku: blauProd.sku,
|
||||||
|
is_parent: blauProd.is_parent,
|
||||||
|
parent: blauProd.parent,
|
||||||
|
variants: blauProd.variants,
|
||||||
|
sort: blauProd.sort,
|
||||||
|
options: blauProd.options,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(blauProd.is_parent, '0');
|
||||||
|
assert.equal(blauProd.parent, String(parentProd._id));
|
||||||
|
assert.equal(blauProd.variants, 'Blau');
|
||||||
|
assert.equal(blauProd.sort, '1');
|
||||||
|
assert.equal(blauProd.options, null);
|
||||||
|
|
||||||
|
// 7. Add third child "Grün" and verify options list update
|
||||||
|
console.log('Adding third child "Grün"...');
|
||||||
|
const childGruen = await store.article.addChild(googleAuthId, tShirt.sku, {
|
||||||
|
value: 'Grün',
|
||||||
|
name: 'T-Shirt Basic - Grün',
|
||||||
|
});
|
||||||
|
|
||||||
|
prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
parentProd = prods.find((p) => p.sku === 'TSHIRT');
|
||||||
|
assert.deepEqual(parentProd.options, [{ name: 'Farbe', value: 'Rot,Blau,Grün' }]);
|
||||||
|
console.log('Updated parent options:', parentProd.options);
|
||||||
|
|
||||||
|
// 8. Remove child "Blau" and verify options list update
|
||||||
|
console.log('Removing child "Blau"...');
|
||||||
|
await store.article.removeChild(googleAuthId, tShirt.sku, childBlau.article.sku, { deleteArticle: true });
|
||||||
|
|
||||||
|
prods = await store.posterminal.getProductList({ posterminalId: terminal.id });
|
||||||
|
parentProd = prods.find((p) => p.sku === 'TSHIRT');
|
||||||
|
assert.deepEqual(parentProd.options, [{ name: 'Farbe', value: 'Rot,Grün' }]);
|
||||||
|
console.log('Parent options after removal:', parentProd.options);
|
||||||
|
|
||||||
|
console.log('✅ ALL FATHER/CHILD VARIANT TESTS PASSED');
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch((err) => {
|
||||||
|
console.error('Test failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ WHERE id = @articleId
|
|||||||
export const TOGGLE_PARENT = `
|
export const TOGGLE_PARENT = `
|
||||||
UPDATE article
|
UPDATE article
|
||||||
SET is_parent = @isParent,
|
SET is_parent = @isParent,
|
||||||
parentattributename_id = @parentAttributeNameId
|
parentattributename_id = @parentAttributeNameId,
|
||||||
|
updated_at = SYSUTCDATETIME()
|
||||||
WHERE id = @articleId
|
WHERE id = @articleId
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -222,6 +223,33 @@ SET unit_id = @unitId, unitamount = @unitamount, updated_at = SYSUTCDATETIME()
|
|||||||
WHERE id = @articleId
|
WHERE id = @articleId
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const TOUCH_UPDATED_AT = `
|
||||||
|
UPDATE article
|
||||||
|
SET updated_at = SYSUTCDATETIME()
|
||||||
|
WHERE id = @articleId
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT = `
|
||||||
|
UPDATE article
|
||||||
|
SET updated_at = SYSUTCDATETIME()
|
||||||
|
WHERE optionslist_id IN (
|
||||||
|
SELECT ola.optionslist_id
|
||||||
|
FROM optionslistarticle ola
|
||||||
|
WHERE ola.article_id = @articleId
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT = `
|
||||||
|
UPDATE article
|
||||||
|
SET updated_at = SYSUTCDATETIME()
|
||||||
|
WHERE articlelist_id IN (
|
||||||
|
SELECT ala.articlelist_id
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
WHERE ala.article_id = @articleId
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
export const CREATE = `
|
export const CREATE = `
|
||||||
INSERT INTO article (company_id, sku, unit_id, unitamount, weight, track_stock)
|
INSERT INTO article (company_id, sku, unit_id, unitamount, weight, track_stock)
|
||||||
OUTPUT INSERTED.id
|
OUTPUT INSERTED.id
|
||||||
@@ -248,6 +276,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
|
||||||
|
|||||||
@@ -93,20 +93,32 @@ FROM posterminalcategory
|
|||||||
WHERE posterminal_id = @posterminalId
|
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 = `
|
export const SET_ON = `
|
||||||
IF NOT EXISTS (
|
;WITH cats AS (
|
||||||
SELECT 1 FROM posterminalcategory
|
SELECT id FROM category WHERE id = @categoryId
|
||||||
WHERE posterminal_id = @posterminalId AND category_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)
|
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 = `
|
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
|
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. */
|
/** setOnAll: enable ALL company categories on one terminal. */
|
||||||
@@ -170,7 +182,7 @@ WHERE EXISTS (
|
|||||||
WHERE ca.article_id = a.id
|
WHERE ca.article_id = a.id
|
||||||
AND pc.posterminal_id = @posterminalId
|
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 = `
|
export const COUNT_CATEGORIES = `
|
||||||
@@ -219,7 +231,7 @@ WHERE a.articlelist_id IS NOT NULL
|
|||||||
WHERE ca.article_id = a.id
|
WHERE ca.article_id = a.id
|
||||||
AND pc.posterminal_id = @posterminalId
|
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 = `
|
export const MAX_ORDER_ID = `
|
||||||
@@ -263,7 +275,7 @@ SELECT TOP (@limit)
|
|||||||
COALESCE(pr.net, 0) AS netPrice,
|
COALESCE(pr.net, 0) AS netPrice,
|
||||||
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
||||||
a.created_at AS createdAt,
|
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(
|
COALESCE(
|
||||||
(
|
(
|
||||||
SELECT TOP 1 p.hash
|
SELECT TOP 1 p.hash
|
||||||
@@ -288,13 +300,15 @@ SELECT TOP (@limit)
|
|||||||
) AS barcode,
|
) AS barcode,
|
||||||
a.is_parent AS isParent,
|
a.is_parent AS isParent,
|
||||||
a.parentarticle_id AS parentArticleId,
|
a.parentarticle_id AS parentArticleId,
|
||||||
|
a.parentsort AS parentsort,
|
||||||
(
|
(
|
||||||
SELECT TOP 1 CONCAT(fan.name, '|', av.value, '|', fan.sort, '|', a.parentsort)
|
SELECT TOP 1 av.value
|
||||||
FROM article fa
|
FROM articleattributevalue aav
|
||||||
INNER JOIN attributename fan ON fan.id = fa.parentattributename_id
|
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id
|
||||||
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
|
LEFT JOIN article fa ON fa.id = a.parentarticle_id
|
||||||
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id
|
WHERE aav.article_id = a.id
|
||||||
WHERE a.parentarticle_id IS NOT NULL AND fa.id = a.parentarticle_id
|
AND (fa.parentattributename_id IS NULL OR av.attribute_id = fa.parentattributename_id)
|
||||||
|
ORDER BY CASE WHEN av.attribute_id = fa.parentattributename_id THEN 0 ELSE 1 END, aav.sort ASC, aav.id ASC
|
||||||
) AS variantName,
|
) AS variantName,
|
||||||
CASE
|
CASE
|
||||||
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
@@ -319,6 +333,7 @@ SELECT TOP (@limit)
|
|||||||
) AS sort,
|
) AS sort,
|
||||||
a.track_stock AS trackStock,
|
a.track_stock AS trackStock,
|
||||||
CASE
|
CASE
|
||||||
|
WHEN a.track_stock = 0 THEN 0
|
||||||
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
) THEN COALESCE((
|
) THEN COALESCE((
|
||||||
@@ -337,6 +352,7 @@ SELECT TOP (@limit)
|
|||||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||||
)
|
)
|
||||||
WHERE m.company_id = a.company_id
|
WHERE m.company_id = a.company_id
|
||||||
|
AND l.warehouse_id IS NOT NULL
|
||||||
GROUP BY ca.id
|
GROUP BY ca.id
|
||||||
) comp_stock ON comp_stock.comp_id = ala.article_id
|
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||||
WHERE ala.articlelist_id = a.articlelist_id
|
WHERE ala.articlelist_id = a.articlelist_id
|
||||||
@@ -348,6 +364,7 @@ SELECT TOP (@limit)
|
|||||||
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
INNER JOIN manifest m ON m.id = ma.manifest_id
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
WHERE m.company_id = a.company_id
|
WHERE m.company_id = a.company_id
|
||||||
|
AND l.warehouse_id IS NOT NULL
|
||||||
AND (
|
AND (
|
||||||
ma.sku = a.sku
|
ma.sku = a.sku
|
||||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
||||||
@@ -371,8 +388,8 @@ WHERE EXISTS (
|
|||||||
WHERE ca.article_id = a.id
|
WHERE ca.article_id = a.id
|
||||||
AND pc.posterminal_id = @posterminalId
|
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
|
||||||
ORDER BY DATEDIFF_BIG(second, '1970-01-01', a.updated_at) ASC, a.id ASC
|
ORDER BY (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) ASC
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LIST_POS_PRODUCT_CATEGORIES = `
|
export const LIST_POS_PRODUCT_CATEGORIES = `
|
||||||
@@ -483,6 +500,40 @@ WHERE pr.minmultiplier = 1
|
|||||||
GROUP BY pr.article_id, pr.customergroup_id
|
GROUP BY pr.article_id, pr.customergroup_id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_MANDATED_OPTIONS = `
|
||||||
|
SELECT
|
||||||
|
a.id AS articleId,
|
||||||
|
ola.count,
|
||||||
|
COALESCE(opt_at.name, opt_a.sku) AS depositName,
|
||||||
|
opt_pr.net AS depositNetPrice,
|
||||||
|
COALESCE(opt_ptax.rate, opt_ctax.rate, 0) AS depositTaxRate
|
||||||
|
FROM article a
|
||||||
|
INNER JOIN optionslistarticle ola ON ola.optionslist_id = a.optionslist_id AND ola.mandated = 1
|
||||||
|
INNER JOIN article opt_a ON opt_a.id = ola.article_id
|
||||||
|
INNER JOIN company co ON co.id = a.company_id
|
||||||
|
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
|
||||||
|
LEFT JOIN articletext opt_at ON opt_at.article_id = opt_a.id AND opt_at.language_id = co.default_language_id
|
||||||
|
LEFT JOIN price opt_pr ON opt_pr.article_id = opt_a.id AND opt_pr.customergroup_id = co.poscustomergroup_id AND opt_pr.minmultiplier = 1
|
||||||
|
LEFT JOIN tax opt_ptax ON opt_ptax.id = opt_pr.tax_id
|
||||||
|
LEFT JOIN tax opt_ctax ON opt_ctax.id = co.taxrate
|
||||||
|
ORDER BY ola.sort ASC, ola.id ASC
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_PARENT_OPTIONS = `
|
||||||
|
SELECT
|
||||||
|
fa.id AS parentArticleId,
|
||||||
|
COALESCE(fan.name, 'Variante') AS attributeName,
|
||||||
|
av.value AS variantValue
|
||||||
|
FROM article fa
|
||||||
|
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = fa.company_id
|
||||||
|
INNER JOIN article child ON child.parentarticle_id = fa.id
|
||||||
|
LEFT JOIN attributename fan ON fan.id = fa.parentattributename_id
|
||||||
|
LEFT JOIN articleattributevalue aav ON aav.article_id = child.id
|
||||||
|
LEFT JOIN attributevalue av ON av.id = aav.attributevalue_id AND (fa.parentattributename_id IS NULL OR av.attribute_id = fa.parentattributename_id)
|
||||||
|
WHERE fa.is_parent = 1
|
||||||
|
ORDER BY fa.id ASC, child.parentsort ASC, child.id ASC
|
||||||
|
`;
|
||||||
|
|
||||||
export const RECORD_OBITUARY = `
|
export const RECORD_OBITUARY = `
|
||||||
INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id)
|
INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id)
|
||||||
SELECT pt.id, @entityType, @entityId
|
SELECT pt.id, @entityType, @entityId
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ 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, UNLINK_CHILD, UPDATE_BRAND, UPDATE_PARENT_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT,
|
SET_IS_PARENT, TOGGLE_PARENT, TOUCH_UPDATED_AT, TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT,
|
||||||
|
TOUCH_STUECKLISTE_ARTICLES_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,
|
||||||
} = queries;
|
} = queries;
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,34 @@ import { translateQueries } from './translate.js';
|
|||||||
const queries = translateQueries(source, 'posterminal');
|
const queries = translateQueries(source, 'posterminal');
|
||||||
export const {
|
export const {
|
||||||
CREATE, DELETE_BY_COMPANY_AND_ID, GET_BY_ID, LIST_BY_COMPANY,
|
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_PAIRINGPIN, PAIR, SET_MAX_KNOWN_OBITUARY_ID,
|
||||||
FIND_BY_TOKEN, COUNT_PRODUCTS, COUNT_CATEGORIES, COUNT_CUSTOMERS,
|
FIND_BY_TOKEN, COUNT_PRODUCTS, COUNT_CATEGORIES, COUNT_CUSTOMERS,
|
||||||
COUNT_CUSTOMER_GROUPS, COUNT_COMPOSITE_PRODUCTS, MAX_ORDER_ID,
|
COUNT_CUSTOMER_GROUPS, COUNT_COMPOSITE_PRODUCTS, MAX_ORDER_ID,
|
||||||
LIST_POS_PRODUCT_CATEGORIES, LIST_POS_CUSTOMER_GROUP_IDS, LIST_POS_PRODUCT_PRICES,
|
LIST_POS_PRODUCT_CATEGORIES, LIST_POS_CUSTOMER_GROUP_IDS, LIST_POS_PRODUCT_PRICES,
|
||||||
|
LIST_POS_MANDATED_OPTIONS, LIST_POS_PARENT_OPTIONS,
|
||||||
TOUCH_LASTSEEN, RECORD_OBITUARY, COUNT_DELETED_ENTITIES,
|
TOUCH_LASTSEEN, RECORD_OBITUARY, COUNT_DELETED_ENTITIES,
|
||||||
} = queries;
|
} = queries;
|
||||||
|
|
||||||
export const SET_ON = `
|
export const SET_ON = `
|
||||||
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
WITH RECURSIVE cats AS (
|
||||||
VALUES (@posterminalId, @categoryId)
|
SELECT id FROM category WHERE id = @categoryId
|
||||||
ON CONFLICT (posterminal_id, category_id) DO NOTHING
|
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 = `
|
export const SET_ON_ALL = `
|
||||||
@@ -24,9 +40,8 @@ WITH RECURSIVE cats AS (
|
|||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
|
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
|
||||||
)
|
)
|
||||||
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
INSERT OR IGNORE INTO posterminalcategory (posterminal_id, category_id)
|
||||||
SELECT @posterminalId, id FROM cats WHERE 1
|
SELECT @posterminalId, id FROM cats
|
||||||
ON CONFLICT (posterminal_id, category_id) DO NOTHING
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LIST_POS_CUSTOMER_GROUPS = `
|
export const LIST_POS_CUSTOMER_GROUPS = `
|
||||||
@@ -147,7 +162,7 @@ SELECT
|
|||||||
COALESCE(pr.net, 0) AS netPrice,
|
COALESCE(pr.net, 0) AS netPrice,
|
||||||
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
||||||
a.created_at AS createdAt,
|
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(
|
COALESCE(
|
||||||
(
|
(
|
||||||
SELECT p.hash
|
SELECT p.hash
|
||||||
@@ -175,13 +190,15 @@ SELECT
|
|||||||
) AS barcode,
|
) AS barcode,
|
||||||
a.is_parent AS isParent,
|
a.is_parent AS isParent,
|
||||||
a.parentarticle_id AS parentArticleId,
|
a.parentarticle_id AS parentArticleId,
|
||||||
|
a.parentsort AS parentsort,
|
||||||
(
|
(
|
||||||
SELECT fan.name || '|' || av.value || '|' || fan.sort || '|' || a.parentsort
|
SELECT av.value
|
||||||
FROM article fa
|
FROM articleattributevalue aav
|
||||||
INNER JOIN attributename fan ON fan.id = fa.parentattributename_id
|
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id
|
||||||
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
|
LEFT JOIN article fa ON fa.id = a.parentarticle_id
|
||||||
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id
|
WHERE aav.article_id = a.id
|
||||||
WHERE a.parentarticle_id IS NOT NULL AND fa.id = a.parentarticle_id
|
AND (fa.parentattributename_id IS NULL OR av.attribute_id = fa.parentattributename_id)
|
||||||
|
ORDER BY CASE WHEN av.attribute_id = fa.parentattributename_id THEN 0 ELSE 1 END, aav.sort ASC, aav.id ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
) AS variantName,
|
) AS variantName,
|
||||||
CASE
|
CASE
|
||||||
@@ -207,6 +224,7 @@ SELECT
|
|||||||
) AS sort,
|
) AS sort,
|
||||||
a.track_stock AS trackStock,
|
a.track_stock AS trackStock,
|
||||||
CASE
|
CASE
|
||||||
|
WHEN a.track_stock = 0 THEN 0
|
||||||
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
) THEN COALESCE((
|
) THEN COALESCE((
|
||||||
@@ -225,6 +243,7 @@ SELECT
|
|||||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||||
)
|
)
|
||||||
WHERE m.company_id = a.company_id
|
WHERE m.company_id = a.company_id
|
||||||
|
AND l.warehouse_id IS NOT NULL
|
||||||
GROUP BY ca.id
|
GROUP BY ca.id
|
||||||
) comp_stock ON comp_stock.comp_id = ala.article_id
|
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||||
WHERE ala.articlelist_id = a.articlelist_id
|
WHERE ala.articlelist_id = a.articlelist_id
|
||||||
@@ -236,6 +255,7 @@ SELECT
|
|||||||
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
INNER JOIN manifest m ON m.id = ma.manifest_id
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
WHERE m.company_id = a.company_id
|
WHERE m.company_id = a.company_id
|
||||||
|
AND l.warehouse_id IS NOT NULL
|
||||||
AND (
|
AND (
|
||||||
ma.sku = a.sku
|
ma.sku = a.sku
|
||||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
||||||
@@ -259,8 +279,8 @@ WHERE EXISTS (
|
|||||||
WHERE ca.article_id = a.id
|
WHERE ca.article_id = a.id
|
||||||
AND pc.posterminal_id = @posterminalId
|
AND pc.posterminal_id = @posterminalId
|
||||||
)
|
)
|
||||||
AND CAST(strftime('%s', a.updated_at) AS INTEGER) > @cursor
|
AND (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) > @cursor
|
||||||
ORDER BY lastChanged ASC, a.id ASC
|
ORDER BY (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) ASC
|
||||||
LIMIT @limit
|
LIMIT @limit
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -271,6 +271,9 @@ export default function addChild(provider) {
|
|||||||
await initArticleWarehouseSummary(runner, { companyId, articleId: childId });
|
await initArticleWarehouseSummary(runner, { companyId, articleId: childId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Touch parent article updated_at so POS and other consumers refresh parent options
|
||||||
|
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
|
||||||
|
|
||||||
return load(googleAuthId, childSku, runner);
|
return load(googleAuthId, childSku, runner);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
} from '../../providers/sqlite3/queries/articlepic.js';
|
} from '../../providers/sqlite3/queries/articlepic.js';
|
||||||
import { CREATE as MSSQL_CREATE_PIC } from '../../providers/mssql/queries/pic.js';
|
import { CREATE as MSSQL_CREATE_PIC } from '../../providers/mssql/queries/pic.js';
|
||||||
import { CREATE as SQLITE_CREATE_PIC } from '../../providers/sqlite3/queries/pic.js';
|
import { CREATE as SQLITE_CREATE_PIC } from '../../providers/sqlite3/queries/pic.js';
|
||||||
|
import { TOUCH_UPDATED_AT as MSSQL_TOUCH } from '../../providers/mssql/queries/article.js';
|
||||||
|
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js';
|
||||||
import processAvifImage from '../../util/processAvifImage.js';
|
import processAvifImage from '../../util/processAvifImage.js';
|
||||||
import newFullHash from '../../util/newFullHash.js';
|
import newFullHash from '../../util/newFullHash.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
@@ -21,6 +23,7 @@ export default function addImage(provider) {
|
|||||||
const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT;
|
const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT;
|
||||||
const createPicSql = isSqlite ? SQLITE_CREATE_PIC : MSSQL_CREATE_PIC;
|
const createPicSql = isSqlite ? SQLITE_CREATE_PIC : MSSQL_CREATE_PIC;
|
||||||
const addArticlePicSql = isSqlite ? SQLITE_ADD_ARTICLEPIC : MSSQL_ADD_ARTICLEPIC;
|
const addArticlePicSql = isSqlite ? SQLITE_ADD_ARTICLEPIC : MSSQL_ADD_ARTICLEPIC;
|
||||||
|
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an image to an article by its SKU, processing full, thumb, and 32px-edge small-thumb AVIF variants.
|
* Adds an image to an article by its SKU, processing full, thumb, and 32px-edge small-thumb AVIF variants.
|
||||||
@@ -107,6 +110,8 @@ export default function addImage(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
picId,
|
picId,
|
||||||
sort: effectiveSort,
|
sort: effectiveSort,
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ export default function del(provider) {
|
|||||||
|
|
||||||
await runner.query(queries.DELETE_PICS_BY_ARTICLE, { articleId: id });
|
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 });
|
const deleted = await runner.query(queries.DELETE_BY_ID, { articleId: id });
|
||||||
if (!deleted.rowsAffected) {
|
if (!deleted.rowsAffected) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import sharp from 'sharp';
|
|||||||
import { encodeRGBA } from 'skelehash-wasm';
|
import { encodeRGBA } from 'skelehash-wasm';
|
||||||
import { DELETE_BY_ARTICLE_AND_HASH as MSSQL_QUERY } from '../../providers/mssql/queries/pic.js';
|
import { DELETE_BY_ARTICLE_AND_HASH as MSSQL_QUERY } from '../../providers/mssql/queries/pic.js';
|
||||||
import { DELETE_BY_ARTICLE_AND_HASH as SQLITE_QUERY } from '../../providers/sqlite3/queries/pic.js';
|
import { DELETE_BY_ARTICLE_AND_HASH as SQLITE_QUERY } from '../../providers/sqlite3/queries/pic.js';
|
||||||
|
import { TOUCH_UPDATED_AT as MSSQL_TOUCH } from '../../providers/mssql/queries/article.js';
|
||||||
|
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
import { recordImageDeleteHistory } from '../articlehistory/record.js';
|
import { recordImageDeleteHistory } from '../articlehistory/record.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -9,6 +11,7 @@ import makeRunner from '../runner.js';
|
|||||||
export default function deleteImage(provider) {
|
export default function deleteImage(provider) {
|
||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_QUERY : MSSQL_QUERY;
|
const sql = provider?.dialect === 'sqlite3' ? SQLITE_QUERY : MSSQL_QUERY;
|
||||||
|
const touchArticleSql = provider?.dialect === 'sqlite3' ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an image from an article by its SKU and full image hash.
|
* Deletes an image from an article by its SKU and full image hash.
|
||||||
@@ -95,6 +98,8 @@ export default function deleteImage(provider) {
|
|||||||
fullHash,
|
fullHash,
|
||||||
skelehash,
|
skelehash,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tx) {
|
if (tx) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as mssqlPic from '../../providers/mssql/queries/articlepic.js';
|
import * as mssqlPic from '../../providers/mssql/queries/articlepic.js';
|
||||||
import * as sqlitePic from '../../providers/sqlite3/queries/articlepic.js';
|
import * as sqlitePic from '../../providers/sqlite3/queries/articlepic.js';
|
||||||
|
import { TOUCH_UPDATED_AT as MSSQL_TOUCH } from '../../providers/mssql/queries/article.js';
|
||||||
|
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
import { recordImageOrderHistory } from '../articlehistory/record.js';
|
import { recordImageOrderHistory } from '../articlehistory/record.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -8,6 +10,7 @@ export default function orderImages(provider) {
|
|||||||
const resolve = resolveArticleBySku(provider);
|
const resolve = resolveArticleBySku(provider);
|
||||||
const isSqlite = provider?.dialect === 'sqlite3';
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
const queries = isSqlite ? sqlitePic : mssqlPic;
|
const queries = isSqlite ? sqlitePic : mssqlPic;
|
||||||
|
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reorders the images of an article by its SKU.
|
* Reorders the images of an article by its SKU.
|
||||||
@@ -69,6 +72,7 @@ export default function orderImages(provider) {
|
|||||||
companyuserId,
|
companyuserId,
|
||||||
fullHashes,
|
fullHashes,
|
||||||
});
|
});
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { fullHashes };
|
return { fullHashes };
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ export default function removeChild(provider) {
|
|||||||
childSku,
|
childSku,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Touch parent article updated_at so POS and other consumers refresh parent options
|
||||||
|
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
parentSku,
|
parentSku,
|
||||||
@@ -131,6 +134,9 @@ export default function removeChild(provider) {
|
|||||||
parentSku,
|
parentSku,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Touch parent article updated_at so POS and other consumers refresh parent options
|
||||||
|
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
parentSku,
|
parentSku,
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
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,
|
||||||
} 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,
|
||||||
} from '../../providers/sqlite3/queries/article.js';
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
|
import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
@@ -20,6 +28,10 @@ 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 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.
|
||||||
@@ -74,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) {
|
||||||
@@ -83,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,
|
||||||
@@ -91,6 +148,9 @@ export default function setCategoryIdsBySku(provider) {
|
|||||||
newCategoryIds: normalized,
|
newCategoryIds: normalized,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Touch article updated_at so POS and other consumers pick up category assignment changes
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import {
|
|||||||
ADD_EAN as MSSQL_ADD_EAN,
|
ADD_EAN as MSSQL_ADD_EAN,
|
||||||
DELETE_EANS_BY_ARTICLE as MSSQL_DELETE_EANS,
|
DELETE_EANS_BY_ARTICLE as MSSQL_DELETE_EANS,
|
||||||
EAN_EXISTS_FOR_OTHER_ARTICLE as MSSQL_CHECK_CONFLICT,
|
EAN_EXISTS_FOR_OTHER_ARTICLE as MSSQL_CHECK_CONFLICT,
|
||||||
|
TOUCH_UPDATED_AT as MSSQL_TOUCH,
|
||||||
} from '../../providers/mssql/queries/article.js';
|
} from '../../providers/mssql/queries/article.js';
|
||||||
import {
|
import {
|
||||||
ADD_EAN as SQLITE_ADD_EAN,
|
ADD_EAN as SQLITE_ADD_EAN,
|
||||||
DELETE_EANS_BY_ARTICLE as SQLITE_DELETE_EANS,
|
DELETE_EANS_BY_ARTICLE as SQLITE_DELETE_EANS,
|
||||||
EAN_EXISTS_FOR_OTHER_ARTICLE as SQLITE_CHECK_CONFLICT,
|
EAN_EXISTS_FOR_OTHER_ARTICLE as SQLITE_CHECK_CONFLICT,
|
||||||
|
TOUCH_UPDATED_AT as SQLITE_TOUCH,
|
||||||
} from '../../providers/sqlite3/queries/article.js';
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
import { isUniqueConstraintError } from '../../providers/provider.js';
|
import { isUniqueConstraintError } from '../../providers/provider.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
@@ -22,6 +24,7 @@ export default function setEansBySku(provider) {
|
|||||||
const addEanSql = isSqlite ? SQLITE_ADD_EAN : MSSQL_ADD_EAN;
|
const addEanSql = isSqlite ? SQLITE_ADD_EAN : MSSQL_ADD_EAN;
|
||||||
const deleteEansSql = isSqlite ? SQLITE_DELETE_EANS : MSSQL_DELETE_EANS;
|
const deleteEansSql = isSqlite ? SQLITE_DELETE_EANS : MSSQL_DELETE_EANS;
|
||||||
const checkConflictSql = isSqlite ? SQLITE_CHECK_CONFLICT : MSSQL_CHECK_CONFLICT;
|
const checkConflictSql = isSqlite ? SQLITE_CHECK_CONFLICT : MSSQL_CHECK_CONFLICT;
|
||||||
|
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the EAN list of an article by its SKU.
|
* Replaces the EAN list of an article by its SKU.
|
||||||
@@ -98,6 +101,9 @@ export default function setEansBySku(provider) {
|
|||||||
newEans: normalized,
|
newEans: normalized,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Touch article updated_at so POS and other consumers pick up the barcode change
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ import {
|
|||||||
CREATE as SQLITE_CREATE_PRICE,
|
CREATE as SQLITE_CREATE_PRICE,
|
||||||
DELETE_BY_ARTICLE_AND_CUSTOMERGROUP as SQLITE_DELETE_PRICE,
|
DELETE_BY_ARTICLE_AND_CUSTOMERGROUP as SQLITE_DELETE_PRICE,
|
||||||
} from '../../providers/sqlite3/queries/price.js';
|
} from '../../providers/sqlite3/queries/price.js';
|
||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as MSSQL_TOUCH,
|
||||||
|
TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT as MSSQL_TOUCH_OPTION_PARENTS,
|
||||||
|
} from '../../providers/mssql/queries/article.js';
|
||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as SQLITE_TOUCH,
|
||||||
|
TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT as SQLITE_TOUCH_OPTION_PARENTS,
|
||||||
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
import taxEnsure from '../tax/ensure.js';
|
import taxEnsure from '../tax/ensure.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
@@ -65,6 +73,8 @@ export default function setPricesBySku(provider) {
|
|||||||
|
|
||||||
const createPriceSql = isSqlite ? SQLITE_CREATE_PRICE : MSSQL_CREATE_PRICE;
|
const createPriceSql = isSqlite ? SQLITE_CREATE_PRICE : MSSQL_CREATE_PRICE;
|
||||||
const deletePriceSql = isSqlite ? SQLITE_DELETE_PRICE : MSSQL_DELETE_PRICE;
|
const deletePriceSql = isSqlite ? SQLITE_DELETE_PRICE : MSSQL_DELETE_PRICE;
|
||||||
|
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
const touchOptionParentsSql = isSqlite ? SQLITE_TOUCH_OPTION_PARENTS : MSSQL_TOUCH_OPTION_PARENTS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the price list of an article by its SKU.
|
* Replaces the price list of an article by its SKU.
|
||||||
@@ -141,6 +151,12 @@ export default function setPricesBySku(provider) {
|
|||||||
newPrices: normalized,
|
newPrices: normalized,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Touch article updated_at so POS and other consumers pick up the price change
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
|
|
||||||
|
// Touch any parent articles using this article as an option so their deposit price updates
|
||||||
|
await runner.query(touchOptionParentsSql, { articleId });
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -232,6 +232,12 @@ export default function updateBySku(provider) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Touch article updated_at so all consumers pick up changes
|
||||||
|
await runner.query(articleQueries.TOUCH_UPDATED_AT, { articleId });
|
||||||
|
|
||||||
|
// Touch any parent articles that use this article as an option so their deposit name/details update on POS
|
||||||
|
await runner.query(articleQueries.TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT, { articleId });
|
||||||
|
|
||||||
const article = await load(articleId, languageId, runner);
|
const article = await load(articleId, languageId, runner);
|
||||||
if (!article) {
|
if (!article) {
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as MSSQL_TOUCH,
|
||||||
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE,
|
||||||
|
} from '../../providers/mssql/queries/article.js';
|
||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as SQLITE_TOUCH,
|
||||||
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE,
|
||||||
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centralized sync and rebuild logic for the materialized `articlewarehousesummary` table.
|
* Centralized sync and rebuild logic for the materialized `articlewarehousesummary` table.
|
||||||
*
|
*
|
||||||
@@ -142,6 +151,11 @@ export async function syncArticleWarehouseSummaryForLedger(runner, { companyId,
|
|||||||
warehouseId,
|
warehouseId,
|
||||||
stock,
|
stock,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
|
||||||
|
await runner.query(touchSql, { articleId });
|
||||||
|
await runner.query(touchStuecklisteSql, { articleId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ import {
|
|||||||
recordWarehouseHistory,
|
recordWarehouseHistory,
|
||||||
} from '../articlehistory/record.js';
|
} from '../articlehistory/record.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as MSSQL_TOUCH,
|
||||||
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE,
|
||||||
|
} from '../../providers/mssql/queries/article.js';
|
||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as SQLITE_TOUCH,
|
||||||
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE,
|
||||||
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
|
|
||||||
export const INIT_HASH = 'TODO_TSE_BASED_INITHASH';
|
export const INIT_HASH = 'TODO_TSE_BASED_INITHASH';
|
||||||
|
|
||||||
@@ -202,6 +210,10 @@ export default function add(provider) {
|
|||||||
|
|
||||||
// Record history for acceptance
|
// Record history for acceptance
|
||||||
for (const match of articleMatches) {
|
for (const match of articleMatches) {
|
||||||
|
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
|
||||||
|
await runner.query(touchSql, { articleId: match.id });
|
||||||
|
await runner.query(touchStuecklisteSql, { articleId: match.id });
|
||||||
await recordAcceptanceHistory(runner, {
|
await recordAcceptanceHistory(runner, {
|
||||||
articleId: match.id,
|
articleId: match.id,
|
||||||
companyuserId,
|
companyuserId,
|
||||||
@@ -287,6 +299,10 @@ export default function add(provider) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const match of articleMatches) {
|
for (const match of articleMatches) {
|
||||||
|
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
|
||||||
|
await runner.query(touchSql, { articleId: match.id });
|
||||||
|
await runner.query(touchStuecklisteSql, { articleId: match.id });
|
||||||
await recordShipmentHistory(runner, {
|
await recordShipmentHistory(runner, {
|
||||||
articleId: match.id,
|
articleId: match.id,
|
||||||
companyuserId,
|
companyuserId,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as mssqlQueries from '../../providers/mssql/queries/optionslist.js';
|
import * as mssqlQueries from '../../providers/mssql/queries/optionslist.js';
|
||||||
import * as sqliteQueries from '../../providers/sqlite3/queries/optionslist.js';
|
import * as sqliteQueries from '../../providers/sqlite3/queries/optionslist.js';
|
||||||
|
import { TOUCH_UPDATED_AT as MSSQL_TOUCH } from '../../providers/mssql/queries/article.js';
|
||||||
|
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js';
|
||||||
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
import resolveArticleBySku from '../article/resolveArticleBySku.js';
|
||||||
import getOptionsByArticleSku from './getOptionsByArticleSku.js';
|
import getOptionsByArticleSku from './getOptionsByArticleSku.js';
|
||||||
import {
|
import {
|
||||||
@@ -46,6 +48,7 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
const getOptions = getOptionsByArticleSku(provider);
|
const getOptions = getOptionsByArticleSku(provider);
|
||||||
const isSqlite = provider?.dialect === 'sqlite3';
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
const queries = isSqlite ? sqliteQueries : mssqlQueries;
|
||||||
|
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets or clears the options list for an article.
|
* Sets or clears the options list for an article.
|
||||||
@@ -108,6 +111,7 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
type: 'optionslist_cleared',
|
type: 'optionslist_cleared',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -236,6 +240,9 @@ export default function setOptionsByArticleSku(provider) {
|
|||||||
options: result,
|
options: result,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Touch article updated_at so POS and other consumers pick up options/deposit changes
|
||||||
|
await runner.query(touchArticleSql, { articleId });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import makeRunner from '../runner.js';
|
|||||||
function normalizeCursor(cursor) {
|
function normalizeCursor(cursor) {
|
||||||
const n = Number(cursor) || 0;
|
const n = Number(cursor) || 0;
|
||||||
if (n <= 0) return 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) {
|
export default function getCompositeProductCount(provider) {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import makeRunner from '../runner.js';
|
|||||||
function normalizeCursor(cursor) {
|
function normalizeCursor(cursor) {
|
||||||
const n = Number(cursor) || 0;
|
const n = Number(cursor) || 0;
|
||||||
if (n <= 0) return 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) {
|
export default function getProductCount(provider) {
|
||||||
|
|||||||
@@ -3,19 +3,25 @@ import {
|
|||||||
LIST_POS_PRODUCT_CATEGORIES as MSSQL_CATEGORIES,
|
LIST_POS_PRODUCT_CATEGORIES as MSSQL_CATEGORIES,
|
||||||
LIST_POS_CUSTOMER_GROUP_IDS as MSSQL_CG_IDS,
|
LIST_POS_CUSTOMER_GROUP_IDS as MSSQL_CG_IDS,
|
||||||
LIST_POS_PRODUCT_PRICES as MSSQL_PRICES,
|
LIST_POS_PRODUCT_PRICES as MSSQL_PRICES,
|
||||||
|
LIST_POS_MANDATED_OPTIONS as MSSQL_MANDATED_OPTIONS,
|
||||||
|
LIST_POS_PARENT_OPTIONS as MSSQL_PARENT_OPTIONS,
|
||||||
} from '../../providers/mssql/queries/posterminal.js';
|
} from '../../providers/mssql/queries/posterminal.js';
|
||||||
import {
|
import {
|
||||||
LIST_POS_PRODUCTS as SQLITE_PRODUCTS,
|
LIST_POS_PRODUCTS as SQLITE_PRODUCTS,
|
||||||
LIST_POS_PRODUCT_CATEGORIES as SQLITE_CATEGORIES,
|
LIST_POS_PRODUCT_CATEGORIES as SQLITE_CATEGORIES,
|
||||||
LIST_POS_CUSTOMER_GROUP_IDS as SQLITE_CG_IDS,
|
LIST_POS_CUSTOMER_GROUP_IDS as SQLITE_CG_IDS,
|
||||||
LIST_POS_PRODUCT_PRICES as SQLITE_PRICES,
|
LIST_POS_PRODUCT_PRICES as SQLITE_PRICES,
|
||||||
|
LIST_POS_MANDATED_OPTIONS as SQLITE_MANDATED_OPTIONS,
|
||||||
|
LIST_POS_PARENT_OPTIONS as SQLITE_PARENT_OPTIONS,
|
||||||
} from '../../providers/sqlite3/queries/posterminal.js';
|
} from '../../providers/sqlite3/queries/posterminal.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
|
|
||||||
function normalizeCursor(cursor) {
|
function normalizeCursor(cursor) {
|
||||||
const n = Number(cursor) || 0;
|
const n = Number(cursor) || 0;
|
||||||
if (n <= 0) return 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) {
|
function formatDateTime(value) {
|
||||||
@@ -36,6 +42,8 @@ export default function getProductList(provider) {
|
|||||||
const categoriesSql = isSqlite ? SQLITE_CATEGORIES : MSSQL_CATEGORIES;
|
const categoriesSql = isSqlite ? SQLITE_CATEGORIES : MSSQL_CATEGORIES;
|
||||||
const cgIdsSql = isSqlite ? SQLITE_CG_IDS : MSSQL_CG_IDS;
|
const cgIdsSql = isSqlite ? SQLITE_CG_IDS : MSSQL_CG_IDS;
|
||||||
const pricesSql = isSqlite ? SQLITE_PRICES : MSSQL_PRICES;
|
const pricesSql = isSqlite ? SQLITE_PRICES : MSSQL_PRICES;
|
||||||
|
const mandatedOptionsSql = isSqlite ? SQLITE_MANDATED_OPTIONS : MSSQL_MANDATED_OPTIONS;
|
||||||
|
const parentOptionsSql = isSqlite ? SQLITE_PARENT_OPTIONS : MSSQL_PARENT_OPTIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists products for a POS terminal modified after the cursor.
|
* Lists products for a POS terminal modified after the cursor.
|
||||||
@@ -52,7 +60,7 @@ export default function getProductList(provider) {
|
|||||||
const runner = makeRunner(provider, tx);
|
const runner = makeRunner(provider, tx);
|
||||||
const pid = Number(posterminalId);
|
const pid = Number(posterminalId);
|
||||||
|
|
||||||
const [prodRes, catRes, cgRes, priceRes] = await Promise.all([
|
const [prodRes, catRes, cgRes, priceRes, optRes, parentOptRes] = await Promise.all([
|
||||||
runner.query(productsSql, {
|
runner.query(productsSql, {
|
||||||
posterminalId: pid,
|
posterminalId: pid,
|
||||||
cursor: normalizeCursor(cursor),
|
cursor: normalizeCursor(cursor),
|
||||||
@@ -61,6 +69,8 @@ export default function getProductList(provider) {
|
|||||||
runner.query(categoriesSql, { posterminalId: pid }),
|
runner.query(categoriesSql, { posterminalId: pid }),
|
||||||
runner.query(cgIdsSql, { posterminalId: pid }),
|
runner.query(cgIdsSql, { posterminalId: pid }),
|
||||||
runner.query(pricesSql, { posterminalId: pid }),
|
runner.query(pricesSql, { posterminalId: pid }),
|
||||||
|
runner.query(mandatedOptionsSql, { posterminalId: pid }),
|
||||||
|
runner.query(parentOptionsSql, { posterminalId: pid }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const categoriesByArticle = new Map();
|
const categoriesByArticle = new Map();
|
||||||
@@ -79,6 +89,39 @@ export default function getProductList(provider) {
|
|||||||
overridesByArticle.get(row.articleId).set(row.customerGroupId, row.netPrice);
|
overridesByArticle.get(row.articleId).set(row.customerGroupId, row.netPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mandatedOptionsByArticle = new Map();
|
||||||
|
for (const row of optRes.rows) {
|
||||||
|
const gross =
|
||||||
|
Number(row.count || 1) *
|
||||||
|
Number(row.depositNetPrice || 0) *
|
||||||
|
(1 + Number(row.depositTaxRate || 0) / 100);
|
||||||
|
if (!mandatedOptionsByArticle.has(row.articleId)) {
|
||||||
|
mandatedOptionsByArticle.set(row.articleId, {
|
||||||
|
names: [row.depositName],
|
||||||
|
totalPrice: gross,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const existing = mandatedOptionsByArticle.get(row.articleId);
|
||||||
|
existing.names.push(row.depositName);
|
||||||
|
existing.totalPrice += gross;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentOptionsMap = new Map();
|
||||||
|
for (const row of parentOptRes.rows) {
|
||||||
|
if (!row.variantValue) continue;
|
||||||
|
if (!parentOptionsMap.has(row.parentArticleId)) {
|
||||||
|
parentOptionsMap.set(row.parentArticleId, {
|
||||||
|
name: row.attributeName || 'Variante',
|
||||||
|
values: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const opt = parentOptionsMap.get(row.parentArticleId);
|
||||||
|
if (!opt.values.includes(row.variantValue)) {
|
||||||
|
opt.values.push(row.variantValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const customerGroupIds = cgRes.rows.map((row) => row.id);
|
const customerGroupIds = cgRes.rows.map((row) => row.id);
|
||||||
|
|
||||||
return prodRes.rows.map((product) => {
|
return prodRes.rows.map((product) => {
|
||||||
@@ -100,6 +143,28 @@ export default function getProductList(provider) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const categoryIds = categoriesByArticle.get(product.id) ?? [];
|
const categoryIds = categoriesByArticle.get(product.id) ?? [];
|
||||||
|
const mandatedOpt = mandatedOptionsByArticle.get(product.id);
|
||||||
|
const hasDeposit = Boolean(mandatedOpt);
|
||||||
|
const depositName = mandatedOpt ? mandatedOpt.names.join(' + ') : '';
|
||||||
|
const depositPrice = mandatedOpt ? mandatedOpt.totalPrice.toFixed(2) : '0.0';
|
||||||
|
|
||||||
|
const isParent = Boolean(product.isParent);
|
||||||
|
const parentArticleId = Number(product.parentArticleId || 0);
|
||||||
|
const isChild = parentArticleId > 0;
|
||||||
|
|
||||||
|
const parentOpt = parentOptionsMap.get(product.id);
|
||||||
|
const options = isParent && parentOpt && parentOpt.values.length > 0
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: parentOpt.name,
|
||||||
|
value: parentOpt.values.join(','),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const sortValue = isChild
|
||||||
|
? String(product.parentsort ?? 0)
|
||||||
|
: String(product.sort ?? 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: String(product.id),
|
_id: String(product.id),
|
||||||
@@ -112,17 +177,21 @@ export default function getProductList(provider) {
|
|||||||
price: basePrice,
|
price: basePrice,
|
||||||
created_at: formatDateTime(product.createdAt),
|
created_at: formatDateTime(product.createdAt),
|
||||||
lastChanged: String(product.lastChanged),
|
lastChanged: String(product.lastChanged),
|
||||||
sort: String(product.sort ?? 0),
|
sort: sortValue,
|
||||||
categories_id: categoryIds[0] ?? '0',
|
categories_id: categoryIds[0] ?? '0',
|
||||||
categories: categoryIds.map((categoryId) => ({ categoryId })),
|
categories: categoryIds.map((categoryId) => ({ categoryId })),
|
||||||
prices,
|
prices,
|
||||||
is_parent: product.isParent ? '1' : '0',
|
is_parent: isParent ? '1' : '0',
|
||||||
parent: product.parentArticleId > 0 ? String(product.parentArticleId) : '0',
|
parent: isChild ? String(parentArticleId) : '0',
|
||||||
variants: product.variantName ?? '',
|
variants: isChild ? (product.variantName ?? '') : '',
|
||||||
isCompositeProduct: product.isCompositeProduct,
|
isCompositeProduct: product.isCompositeProduct,
|
||||||
attributes: [],
|
attributes: [],
|
||||||
|
options,
|
||||||
use_stock: product.trackStock ? '1' : '0',
|
use_stock: product.trackStock ? '1' : '0',
|
||||||
quantity: String(product.fBestand ?? 0),
|
quantity: String(product.fBestand ?? 0),
|
||||||
|
deposit: hasDeposit ? '1' : '0',
|
||||||
|
d_price: depositPrice,
|
||||||
|
deposit_name: depositName,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,25 +21,121 @@ export default function setOff(provider) {
|
|||||||
? SQLITE_LIST_CATEGORY_IDS
|
? SQLITE_LIST_CATEGORY_IDS
|
||||||
: MSSQL_LIST_CATEGORY_IDS;
|
: MSSQL_LIST_CATEGORY_IDS;
|
||||||
|
|
||||||
const addCategoryObituarySql = `
|
const touchSql = isSqlite
|
||||||
INSERT INTO posterminalobituary (posterminal_id, entity_type, entity_id)
|
? `WITH RECURSIVE cats AS (
|
||||||
VALUES (@posterminalId, 'category', @categoryId)
|
SELECT id FROM category WHERE id = @categoryId
|
||||||
`;
|
UNION ALL
|
||||||
|
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
|
||||||
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
|
|
||||||
)
|
)
|
||||||
`;
|
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).
|
* Removes a category assignment from a posterminal (turns it off).
|
||||||
@@ -84,6 +180,9 @@ export default function setOff(provider) {
|
|||||||
categoryId,
|
categoryId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await runner.query(touchSql, { categoryId });
|
||||||
|
await runner.query(touchArticlesSql, { categoryId });
|
||||||
|
|
||||||
const result = await runner.query(listCatIdsSql, { posterminalId });
|
const result = await runner.query(listCatIdsSql, { posterminalId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -21,30 +21,110 @@ export default function setOn(provider) {
|
|||||||
? SQLITE_LIST_CATEGORY_IDS
|
? SQLITE_LIST_CATEGORY_IDS
|
||||||
: MSSQL_LIST_CATEGORY_IDS;
|
: MSSQL_LIST_CATEGORY_IDS;
|
||||||
const touchSql = isSqlite
|
const touchSql = isSqlite
|
||||||
? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId"
|
? `WITH RECURSIVE cats AS (
|
||||||
: 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId';
|
SELECT id FROM category WHERE id = @categoryId
|
||||||
const touchArticlesSql = isSqlite
|
UNION ALL
|
||||||
? `UPDATE article
|
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
|
||||||
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
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
`;
|
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).
|
* Assigns a category to a posterminal (turns it on for that category).
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ import ledgerAdd from '../ledger/add.js';
|
|||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
import { parseDate } from '../manifestLines.js';
|
import { parseDate } from '../manifestLines.js';
|
||||||
import { syncArticleWarehouseSummaryForLedger } from '../articlewarehousesummary/syncSummary.js';
|
import { syncArticleWarehouseSummaryForLedger } from '../articlewarehousesummary/syncSummary.js';
|
||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as MSSQL_TOUCH,
|
||||||
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE,
|
||||||
|
} from '../../providers/mssql/queries/article.js';
|
||||||
|
import {
|
||||||
|
TOUCH_UPDATED_AT as SQLITE_TOUCH,
|
||||||
|
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE,
|
||||||
|
} from '../../providers/sqlite3/queries/article.js';
|
||||||
|
|
||||||
const ADJUSTMENT_TYPE = 'adjustment';
|
const ADJUSTMENT_TYPE = 'adjustment';
|
||||||
|
|
||||||
@@ -160,6 +168,11 @@ export default function adjust(provider) {
|
|||||||
warehouseId,
|
warehouseId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
|
||||||
|
await runner.query(touchSql, { articleId: art.id });
|
||||||
|
await runner.query(touchStuecklisteSql, { articleId: art.id });
|
||||||
|
|
||||||
// Write warehouse history for tracked article
|
// Write warehouse history for tracked article
|
||||||
if (manifestArticle.manifest_type === 'delivery') {
|
if (manifestArticle.manifest_type === 'delivery') {
|
||||||
await recordWarehouseHistory(runner, {
|
await recordWarehouseHistory(runner, {
|
||||||
@@ -369,6 +382,10 @@ export default function adjust(provider) {
|
|||||||
|
|
||||||
// Write article history for the adjusted article
|
// Write article history for the adjusted article
|
||||||
if (item.articleId) {
|
if (item.articleId) {
|
||||||
|
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
|
||||||
|
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
|
||||||
|
await runner.query(touchSql, { articleId: item.articleId });
|
||||||
|
await runner.query(touchStuecklisteSql, { articleId: item.articleId });
|
||||||
await recordStockAdjustmentHistory(runner, {
|
await recordStockAdjustmentHistory(runner, {
|
||||||
articleId: item.articleId,
|
articleId: item.articleId,
|
||||||
companyuserId,
|
companyuserId,
|
||||||
|
|||||||
Reference in New Issue
Block a user