This commit is contained in:
seb
2026-10-27 06:48:35 +00:00
parent bab5c151d0
commit ccb81cdb26
84 changed files with 5072 additions and 3625 deletions

2
API.md
View File

@@ -66,6 +66,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- **posterminal**: `id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), maxknownobituaryid?:i, root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt` - **posterminal**: `id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), maxknownobituaryid?:i, root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt`
- **posterminalcategory**: `id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)` - **posterminalcategory**: `id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)`
- **posterminalobituary**: `id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i, created_at:dt` - **posterminalobituary**: `id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i, created_at:dt`
- **posterminalsyncqueue**: `id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i`
- **price**: `id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f` - **price**: `id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f`
- **printer**: `id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt` - **printer**: `id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt`
- **propertyname**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)` - **propertyname**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)`
@@ -272,6 +273,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, maxknownobituaryid: number|null, rootCategoryId: number|null, syncRootArticles: boolean|number, lastseenAt: string|null, updatedAt: string}>>` - `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, maxknownobituaryid: number|null, rootCategoryId: number|null, syncRootArticles: boolean|number, lastseenAt: string|null, updatedAt: string}>>`
- `pair(id: number, input: {token: string, clientserial?: string, pairedAt?: string}, tx?: object): Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null}>` - `pair(id: number, input: {token: string, clientserial?: string, pairedAt?: string}, tx?: object): Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null}>`
- `recordObituary(input: {companyId: number, entityType: string, entityId: number}, tx?: object): Promise<void>` - `recordObituary(input: {companyId: number, entityType: string, entityId: number}, tx?: object): Promise<void>`
- `recordSyncQueue(input: {companyId: number, entityType: string, entityId: number}, tx?: object): Promise<void>`
- `setMaxKnownObituaryId(id: number, maxknownobituaryid: number|null, tx?: object): Promise<{id: number, maxknownobituaryid: number|null}>` - `setMaxKnownObituaryId(id: number, maxknownobituaryid: number|null, tx?: object): Promise<{id: number, maxknownobituaryid: number|null}>`
- `setOff(googleAuthId: number, posterminalId: number, categoryId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>` - `setOff(googleAuthId: number, posterminalId: number, categoryId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>`
- `setOffAll(googleAuthId: number, posterminalId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>` - `setOffAll(googleAuthId: number, posterminalId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>`

View File

@@ -68,7 +68,7 @@ Finds a posterminal by its auth token.
*Source: `src/store/posterminal/getCategoryCount.js`* *Source: `src/store/posterminal/getCategoryCount.js`*
Counts categories assigned to the POS terminal updated after the given unix timestamp cursor. Counts categories assigned to the POS terminal queued after the given sync queue cursor ID.
**Parameters** **Parameters**
@@ -76,7 +76,7 @@ Counts categories assigned to the POS terminal updated after the given unix time
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor of last update. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[tx]` | `object` | Optional transaction to run within. | | `[tx]` | `object` | Optional transaction to run within. |
**Returns** **Returns**
@@ -87,7 +87,7 @@ Counts categories assigned to the POS terminal updated after the given unix time
*Source: `src/store/posterminal/getCategoryList.js`* *Source: `src/store/posterminal/getCategoryList.js`*
Lists categories for a POS terminal modified after the cursor. Lists categories for a POS terminal queued after the sync queue cursor ID.
**Parameters** **Parameters**
@@ -95,7 +95,7 @@ Lists categories for a POS terminal modified after the cursor.
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[input.limit=20]` | `number` | Max items to return. | | `[input.limit=20]` | `number` | Max items to return. |
| `[tx]` | `object` | Optional transaction. | | `[tx]` | `object` | Optional transaction. |
@@ -107,7 +107,7 @@ Lists categories for a POS terminal modified after the cursor.
*Source: `src/store/posterminal/getCompositeProductCount.js`* *Source: `src/store/posterminal/getCompositeProductCount.js`*
Counts composite products (Stücklisten) assigned to the POS terminal updated after the given unix timestamp cursor. Counts composite products (Stücklisten) assigned to the POS terminal queued after the given sync queue cursor ID.
**Parameters** **Parameters**
@@ -115,7 +115,7 @@ Counts composite products (Stücklisten) assigned to the POS terminal updated af
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor of last update. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[tx]` | `object` | Optional transaction to run within. | | `[tx]` | `object` | Optional transaction to run within. |
**Returns** **Returns**
@@ -126,7 +126,7 @@ Counts composite products (Stücklisten) assigned to the POS terminal updated af
*Source: `src/store/posterminal/getCustomerCount.js`* *Source: `src/store/posterminal/getCustomerCount.js`*
Counts customers of the POS terminal company updated after the given unix timestamp cursor. Counts customers of the POS terminal company queued after the given sync queue cursor ID.
**Parameters** **Parameters**
@@ -134,7 +134,7 @@ Counts customers of the POS terminal company updated after the given unix timest
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor of last update. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[tx]` | `object` | Optional transaction to run within. | | `[tx]` | `object` | Optional transaction to run within. |
**Returns** **Returns**
@@ -145,7 +145,7 @@ Counts customers of the POS terminal company updated after the given unix timest
*Source: `src/store/posterminal/getCustomerGroupCount.js`* *Source: `src/store/posterminal/getCustomerGroupCount.js`*
Counts customer groups of the POS terminal company updated after the given unix timestamp cursor. Counts customer groups of the POS terminal company queued after the given sync queue cursor ID.
**Parameters** **Parameters**
@@ -153,7 +153,7 @@ Counts customer groups of the POS terminal company updated after the given unix
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor of last update. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[tx]` | `object` | Optional transaction to run within. | | `[tx]` | `object` | Optional transaction to run within. |
**Returns** **Returns**
@@ -164,7 +164,7 @@ Counts customer groups of the POS terminal company updated after the given unix
*Source: `src/store/posterminal/getCustomerGroupList.js`* *Source: `src/store/posterminal/getCustomerGroupList.js`*
Lists customer groups for a POS terminal modified after the cursor, excluding 'Lieferanten'. Lists customer groups for a POS terminal queued after the sync queue cursor ID, excluding 'Lieferanten'.
**Parameters** **Parameters**
@@ -172,7 +172,7 @@ Lists customer groups for a POS terminal modified after the cursor, excluding 'L
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[input.limit=50]` | `number` | Max items to return. | | `[input.limit=50]` | `number` | Max items to return. |
| `[tx]` | `object` | Optional transaction. | | `[tx]` | `object` | Optional transaction. |
@@ -184,7 +184,7 @@ Lists customer groups for a POS terminal modified after the cursor, excluding 'L
*Source: `src/store/posterminal/getCustomerList.js`* *Source: `src/store/posterminal/getCustomerList.js`*
Lists customers for a POS terminal modified after the cursor, excluding 'Lieferanten'. Lists customers for a POS terminal queued after the sync queue cursor ID, excluding 'Lieferanten'.
**Parameters** **Parameters**
@@ -192,7 +192,7 @@ Lists customers for a POS terminal modified after the cursor, excluding 'Liefera
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[input.limit=20]` | `number` | Max items to return. | | `[input.limit=20]` | `number` | Max items to return. |
| `[tx]` | `object` | Optional transaction. | | `[tx]` | `object` | Optional transaction. |
@@ -261,7 +261,7 @@ Returns the maximum order manifest id for the POS terminal company.
*Source: `src/store/posterminal/getProductCount.js`* *Source: `src/store/posterminal/getProductCount.js`*
Counts products assigned to the POS terminal updated after the given unix timestamp cursor. Counts products assigned to the POS terminal queued after the given sync queue cursor ID.
**Parameters** **Parameters**
@@ -269,7 +269,7 @@ Counts products assigned to the POS terminal updated after the given unix timest
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor of last update. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[tx]` | `object` | Optional transaction to run within. | | `[tx]` | `object` | Optional transaction to run within. |
**Returns** **Returns**
@@ -280,7 +280,7 @@ Counts products assigned to the POS terminal updated after the given unix timest
*Source: `src/store/posterminal/getProductList.js`* *Source: `src/store/posterminal/getProductList.js`*
Lists products for a POS terminal modified after the cursor. Lists products for a POS terminal queued after the sync queue cursor ID.
**Parameters** **Parameters**
@@ -288,7 +288,7 @@ Lists products for a POS terminal modified after the cursor.
| --- | --- | --- | | --- | --- | --- |
| `input` | `object` | | | `input` | `object` | |
| `input.posterminalId` | `number` | The POS terminal id. | | `input.posterminalId` | `number` | The POS terminal id. |
| `[input.cursor=0]` | `number` | Unix timestamp cursor. | | `[input.cursor=0]` | `number` | Sequential ID cursor of last synced posterminalsyncqueue entry. |
| `[input.limit=20]` | `number` | Max items to return. | | `[input.limit=20]` | `number` | Max items to return. |
| `[tx]` | `object` | Optional transaction. | | `[tx]` | `object` | Optional transaction. |
@@ -361,6 +361,26 @@ Records an obituary for all active paired POS terminals of a company.
`Promise<void>` `Promise<void>`
## `posterminal.recordSyncQueue`
*Source: `src/store/posterminal/recordSyncQueue.js`*
Enqueues a sync notification for all POS terminals of a company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | |
| `input.companyId` | `number` | The company id. |
| `input.entityType` | `string` | 'category' | 'article' | 'customer' | 'customergroup' |
| `input.entityId` | `number` | The entity's id. |
| `[tx]` | `object` | Optional transaction/runner. |
**Returns**
`Promise<void>`
## `posterminal.setMaxKnownObituaryId` ## `posterminal.setMaxKnownObituaryId`
*Source: `src/store/posterminal/setMaxKnownObituaryId.js`* *Source: `src/store/posterminal/setMaxKnownObituaryId.js`*

View File

@@ -382,6 +382,7 @@ const SCHEMA_TABLES = [
['posterminal', 'id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), maxknownobituaryid?:i, root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt'], ['posterminal', 'id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), maxknownobituaryid?:i, root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt'],
['posterminalcategory', 'id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)'], ['posterminalcategory', 'id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)'],
['posterminalobituary', 'id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i, created_at:dt'], ['posterminalobituary', 'id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i, created_at:dt'],
['posterminalsyncqueue', 'id PK, posterminal_id->posterminal, entity_type:s(50), entity_id:i'],
['price', 'id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f'], ['price', 'id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f'],
['printer', 'id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt'], ['printer', 'id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt'],
['propertyname', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'], ['propertyname', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'],

View File

@@ -170,8 +170,8 @@ function addInferredEdges(tables, edges) {
const match = column.name.match(/^(.+)_id$/i); const match = column.name.match(/^(.+)_id$/i);
if (!match) continue; if (!match) continue;
// Obituary tables store references to deleted entities, not active foreign keys. // Obituary and sync queue tables store polymorphic entity_id references, not active foreign keys to entity.
if (tableName.includes('obituary') || tableName.includes('tombstone')) continue; if (tableName.includes('obituary') || tableName.includes('tombstone') || tableName.includes('syncqueue')) continue;
const candidate = match[1]; const candidate = match[1];
if (!tables.has(candidate) || candidate === tableName) continue; if (!tables.has(candidate) || candidate === tableName) continue;
@@ -390,7 +390,7 @@ const CLUSTERS = [
tables: ['tax', 'price', 'account', 'payment'], tables: ['tax', 'price', 'account', 'payment'],
}, },
{ id: 'units', label: 'Units', tables: ['unit'] }, { id: 'units', label: 'Units', tables: ['unit'] },
{ id: 'pos', label: 'POS', tables: ['posterminal', 'posterminalcategory', 'posterminalobituary', 'printer', 'labeltemplate'] }, { id: 'pos', label: 'POS', tables: ['posterminal', 'posterminalcategory', 'posterminalobituary', 'posterminalsyncqueue', 'printer', 'labeltemplate'] },
{ {
id: 'ai', id: 'ai',
label: 'AI & Models', label: 'AI & Models',

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 408 KiB

After

Width:  |  Height:  |  Size: 441 KiB

436
scripts/test-syncqueue.js Normal file
View File

@@ -0,0 +1,436 @@
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 posterminalsyncqueue table and sync queue flow ---');
const tempDir = mkdtempSync(join(tmpdir(), 'test-syncqueue-'));
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);
const clearQueue = () => {
provider.db.prepare('DELETE FROM posterminalsyncqueue').run();
};
const getQueue = () =>
provider.db
.prepare('SELECT posterminal_id, entity_type, entity_id FROM posterminalsyncqueue ORDER BY id ASC')
.all();
try {
// 1. Verify posterminalsyncqueue table exists and has expected columns
console.log('1. Checking posterminalsyncqueue schema...');
const tableInfo = provider.db.pragma('table_info("posterminalsyncqueue")');
const colNames = tableInfo.map((c) => c.name);
console.log('posterminalsyncqueue columns:', colNames);
assert.deepEqual(colNames, [
'id',
'posterminal_id',
'entity_type',
'entity_id',
]);
// 2. Setup company & 2 POS terminals + another company with 1 terminal (to test company scoping)
console.log('2. Setting up base data with 2 terminals in company 1 and 1 terminal in company 2...');
const googleAuthId = await store.google_auth.add('syncqueue-tester@example.com');
const company1 = await store.company.create({
name: 'SyncQueue Corp 1',
iso639: 'de',
taxrate: 19,
reducedtaxrate: 7,
googleAuthId,
});
const company2 = await store.company.create({
name: 'SyncQueue Corp 2',
iso639: 'de',
taxrate: 19,
reducedtaxrate: 7,
googleAuthId,
});
await store.google_auth.setSelectedCompany(googleAuthId, company2.id);
const otherTerminal = await store.posterminal.upsert(googleAuthId, {
name: 'Other Corp Terminal',
pairingpin: '9999',
});
await store.google_auth.setSelectedCompany(googleAuthId, company1.id);
const term1 = await store.posterminal.upsert(googleAuthId, {
name: 'POS Terminal 1',
pairingpin: '1111',
});
const term2 = await store.posterminal.upsert(googleAuthId, {
name: 'POS Terminal 2',
pairingpin: '2222',
});
// Helper to assert that both term1 and term2 received the expected entity_type + entity_id, and otherTerminal did not
const assertQueuedForBothTerminals = (entityType, entityId, label) => {
const rows = getQueue();
const matching = rows.filter((r) => r.entity_type === entityType && r.entity_id === entityId);
const termIds = matching.map((r) => r.posterminal_id).sort((a, b) => a - b);
assert.deepEqual(
termIds,
[term1.id, term2.id],
`Expected [${term1.id}, ${term2.id}] for ${label} (${entityType}:${entityId}), got ${JSON.stringify(rows)}`,
);
const otherRows = rows.filter((r) => r.posterminal_id === otherTerminal.id);
assert.equal(otherRows.length, 0, `Other company terminal should not receive rows (${label})`);
};
// 3. Test Category operations
console.log('3. Testing category operations...');
clearQueue();
const catA = await store.category.createUnderParent({
googleAuthId,
parentId: company1.rootCategoryId,
name: 'Category A',
});
assertQueuedForBothTerminals('category', catA.id, 'category.createUnderParent');
clearQueue();
await store.category.rename({
googleAuthId,
id: catA.id,
name: 'Category A Renamed',
});
assertQueuedForBothTerminals('category', catA.id, 'category.rename');
clearQueue();
const catLeafId = await store.category.ensureByPath({
googleAuthId,
parentId: company1.rootCategoryId,
path: 'Category B | Subcategory B1',
});
assertQueuedForBothTerminals('category', catLeafId, 'category.ensureByPath');
const catBRow = provider.db
.prepare('SELECT c.id FROM category c WHERE c.parent_id = ? AND c.id != ?')
.get(company1.rootCategoryId, catA.id);
clearQueue();
await store.category.order({
googleAuthId,
parentId: company1.rootCategoryId,
categoryIds: [catBRow.id, catA.id],
});
assertQueuedForBothTerminals('category', catA.id, 'category.order');
assertQueuedForBothTerminals('category', catBRow.id, 'category.order (sibling)');
clearQueue();
await store.category.move({
googleAuthId,
id: catLeafId,
parentId: catA.id,
});
assertQueuedForBothTerminals('category', catLeafId, 'category.move');
const prop = await store.propertyname.create(googleAuthId, { name: 'Color', exclusive: false });
clearQueue();
await store.categoryproperty.setByCategoryId(googleAuthId, catA.id, [
{ propertyId: prop.id, value: 'Red' },
]);
assertQueuedForBothTerminals('category', catA.id, 'categoryproperty.setByCategoryId');
clearQueue();
await store.categoryproperty.delete(googleAuthId, catA.id, prop.id);
assertQueuedForBothTerminals('category', catA.id, 'categoryproperty.delete');
const attr = await store.attributename.create(googleAuthId, { name: 'Size', exclusive: true });
const attrVal = await store.attributevalue.create(googleAuthId, attr.id, { value: 'XL' });
clearQueue();
await store.categoryattributevalue.setByCategoryId(googleAuthId, catA.id, [attrVal.id]);
assertQueuedForBothTerminals('category', catA.id, 'categoryattributevalue.setByCategoryId');
// 4. Test Article operations
console.log('4. Testing article operations...');
clearQueue();
const art1 = await store.article.createUnderParent({
googleAuthId,
parentId: catA.id,
name: 'Cola 0.33L',
});
assertQueuedForBothTerminals('article', art1.id, 'article.createUnderParent');
clearQueue();
await store.article.updateBySku(googleAuthId, art1.sku, { name: 'Cola Classic 0.33L' });
assertQueuedForBothTerminals('article', art1.id, 'article.updateBySku');
clearQueue();
await store.article.setPricesBySku(googleAuthId, art1.sku, [{ net: 1.5, taxRate: 19 }]);
assertQueuedForBothTerminals('article', art1.id, 'article.setPricesBySku');
clearQueue();
await store.article.setEansBySku(googleAuthId, art1.sku, ['4001234567890']);
assertQueuedForBothTerminals('article', art1.id, 'article.setEansBySku');
const brand = await store.brand.create(googleAuthId, { name: 'ColaBrand' });
clearQueue();
await store.article.setBrandIdBySku(googleAuthId, art1.sku, brand.id);
assertQueuedForBothTerminals('article', art1.id, 'article.setBrandIdBySku');
clearQueue();
await store.brand.update(googleAuthId, brand.id, { name: 'ColaBrand Updated' });
assertQueuedForBothTerminals('article', art1.id, 'brand.update');
clearQueue();
await store.article.setWeightBySku(googleAuthId, art1.sku, 0.35);
assertQueuedForBothTerminals('article', art1.id, 'article.setWeightBySku');
const wh = await store.warehouse.create(googleAuthId, { name: 'Main Warehouse' });
clearQueue();
await store.article.setTrackStockBySku(googleAuthId, art1.sku, true);
assertQueuedForBothTerminals('article', art1.id, 'article.setTrackStockBySku');
clearQueue();
await store.stock.adjust(googleAuthId, {
sku: art1.sku,
warehouseId: wh.id,
amount: 10,
text: 'Initial stock',
});
assertQueuedForBothTerminals('article', art1.id, 'stock.adjust');
clearQueue();
await store.article.setCategoryIdsBySku(googleAuthId, art1.sku, [catA.id, catLeafId]);
assertQueuedForBothTerminals('article', art1.id, 'article.setCategoryIdsBySku');
clearQueue();
await store.articleproperty.setByArticleSku(googleAuthId, art1.sku, [
{ propertyId: prop.id, value: 'Dark' },
]);
assertQueuedForBothTerminals('article', art1.id, 'articleproperty.setByArticleSku');
clearQueue();
await store.articleproperty.delete(googleAuthId, art1.sku, prop.id);
assertQueuedForBothTerminals('article', art1.id, 'articleproperty.delete');
clearQueue();
await store.articleattributevalue.setByArticleSku(googleAuthId, art1.sku, [attrVal.id]);
assertQueuedForBothTerminals('article', art1.id, 'articleattributevalue.setByArticleSku');
clearQueue();
await store.attributevalue.update(googleAuthId, attrVal.id, { value: 'XXL' });
assertQueuedForBothTerminals('article', art1.id, 'attributevalue.update');
// Optionslist & Stückliste
const pfand = await store.article.createUnderParent({
googleAuthId,
parentId: catA.id,
name: 'Pfand 0.25',
});
clearQueue();
await store.optionslist.setOptionsByArticleSku(googleAuthId, art1.sku, [
{ sku: pfand.sku, count: 1, mandated: true },
]);
assertQueuedForBothTerminals('article', art1.sku ? art1.id : 0, 'optionslist.setOptionsByArticleSku');
// Updating option article price should also enqueue parent article!
clearQueue();
await store.article.setPricesBySku(googleAuthId, pfand.sku, [{ net: 0.25, taxRate: 19 }]);
assertQueuedForBothTerminals('article', pfand.id, 'setPricesBySku (option itself)');
assertQueuedForBothTerminals('article', art1.id, 'setPricesBySku (option parent)');
const bundle = await store.article.createUnderParent({
googleAuthId,
parentId: catA.id,
name: 'Cola Crate',
});
clearQueue();
await store.articlelist.setItemsByArticleSku(googleAuthId, bundle.sku, [
{ sku: art1.sku, count: 6 },
]);
assertQueuedForBothTerminals('article', bundle.id, 'articlelist.setItemsByArticleSku');
// Adjusting component stock should also enqueue Stückliste parent!
clearQueue();
await store.stock.adjust(googleAuthId, {
sku: art1.sku,
warehouseId: wh.id,
amount: 2,
text: 'Restock component',
});
assertQueuedForBothTerminals('article', art1.id, 'stock.adjust (component)');
assertQueuedForBothTerminals('article', bundle.id, 'stock.adjust (Stückliste parent)');
// 5. Test Customer Group operations
console.log('5. Testing customergroup operations...');
clearQueue();
const cg1 = await store.customergroup.create(googleAuthId, { name: 'B2B' });
assertQueuedForBothTerminals('customergroup', cg1.id, 'customergroup.create');
clearQueue();
await store.customergroup.update(googleAuthId, cg1.id, { name: 'B2B Gold' });
assertQueuedForBothTerminals('customergroup', cg1.id, 'customergroup.update');
const allGroups = await store.customergroup.list(googleAuthId);
clearQueue();
await store.customergroup.order(googleAuthId, {
customergroupIds: allGroups.map((g) => g.id).reverse(),
});
assertQueuedForBothTerminals('customergroup', cg1.id, 'customergroup.order');
// 6. Test Customer (Entity + Address + Contact) operations
console.log('6. Testing customer (entity, entityaddress, entitycontact) operations...');
clearQueue();
const cust = await store.entity.create(googleAuthId, {
customergroupId: cg1.id,
name: 'Doe',
firstName: 'John',
});
assertQueuedForBothTerminals('customer', cust.id, 'entity.create');
clearQueue();
await store.entity.update(googleAuthId, cust.id, { firstName: 'Johnny' });
assertQueuedForBothTerminals('customer', cust.id, 'entity.update');
clearQueue();
const addr = await store.entityaddress.create(googleAuthId, cust.id, {
addressType: 'billing',
street: 'Main St 1',
city: 'Berlin',
});
assertQueuedForBothTerminals('customer', cust.id, 'entityaddress.create');
clearQueue();
await store.entityaddress.update(googleAuthId, cust.id, addr.id, {
city: 'Munich',
});
assertQueuedForBothTerminals('customer', cust.id, 'entityaddress.update');
clearQueue();
await store.entityaddress.delete(googleAuthId, cust.id, addr.id);
assertQueuedForBothTerminals('customer', cust.id, 'entityaddress.delete');
clearQueue();
const contact = await store.entitycontact.create(googleAuthId, cust.id, {
contactType: 'email',
contact: 'john@example.com',
});
assertQueuedForBothTerminals('customer', cust.id, 'entitycontact.create');
clearQueue();
await store.entitycontact.update(googleAuthId, cust.id, contact.id, {
contact: 'johnny@example.com',
});
assertQueuedForBothTerminals('customer', cust.id, 'entitycontact.update');
clearQueue();
await store.entitycontact.delete(googleAuthId, cust.id, contact.id);
assertQueuedForBothTerminals('customer', cust.id, 'entitycontact.delete');
// 7. Test CHECK constraint on entity_type
console.log('7. Testing entity_type CHECK constraint...');
assert.throws(() => {
provider.db
.prepare('INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id) VALUES (?, ?, ?)')
.run(term1.id, 'invalid_type', 1);
}, /CHECK constraint failed/);
// 8. Test POS Sync Cursor (posterminalsyncqueue.id) across counts and lists
console.log('8. Testing POS Sync Cursor (posterminalsyncqueue.id) for counts & lists...');
// Create a fresh terminal (term3) and enable all categories on it to verify full initial sync + delta cursor
const term3 = await store.posterminal.upsert(googleAuthId, {
name: 'POS Terminal 3',
pairingpin: '3333',
});
await store.posterminal.setOnAll(googleAuthId, term3.id);
// Initial sync with cursor = 0
const initProductCount = await store.posterminal.getProductCount({ posterminalId: term3.id, cursor: 0 });
const initCategoryCount = await store.posterminal.getCategoryCount({ posterminalId: term3.id, cursor: 0 });
const initCustomerCount = await store.posterminal.getCustomerCount({ posterminalId: term3.id, cursor: 0 });
const initCustomerGroupCount = await store.posterminal.getCustomerGroupCount({ posterminalId: term3.id, cursor: 0 });
const initCompositeCount = await store.posterminal.getCompositeProductCount({ posterminalId: term3.id, cursor: 0 });
assert.ok(initProductCount > 0, `Expected initProductCount > 0, got ${initProductCount}`);
assert.ok(initCategoryCount > 0, `Expected initCategoryCount > 0, got ${initCategoryCount}`);
assert.ok(initCustomerCount > 0, `Expected initCustomerCount > 0, got ${initCustomerCount}`);
assert.ok(initCustomerGroupCount > 0, `Expected initCustomerGroupCount > 0, got ${initCustomerGroupCount}`);
assert.ok(initCompositeCount > 0, `Expected initCompositeCount > 0, got ${initCompositeCount}`);
const products = await store.posterminal.getProductList({ posterminalId: term3.id, cursor: 0, limit: 100 });
const categories = await store.posterminal.getCategoryList({ posterminalId: term3.id, cursor: 0, limit: 100 });
const customers = await store.posterminal.getCustomerList({ posterminalId: term3.id, cursor: 0, limit: 100 });
const customerGroups = await store.posterminal.getCustomerGroupList({ posterminalId: term3.id, cursor: 0, limit: 100 });
assert.equal(products.length, initProductCount);
assert.equal(categories.length, initCategoryCount);
assert.equal(customers.length, initCustomerCount);
assert.equal(customerGroups.length, initCustomerGroupCount);
// Advance cursors to max lastChanged
const maxProductCursor = Math.max(...products.map((p) => Number(p.lastChanged)));
const maxCategoryCursor = Math.max(...categories.map((c) => Number(c.lastChanged)));
const maxCustomerCursor = Math.max(...customers.map((c) => Number(c.lastChanged)));
const maxCustomerGroupCursor = Math.max(...customerGroups.map((cg) => Number(cg.lastChanged)));
// Verify all counts and lists are 0 at the current cursors
assert.equal(await store.posterminal.getProductCount({ posterminalId: term3.id, cursor: maxProductCursor }), 0);
assert.equal(await store.posterminal.getCategoryCount({ posterminalId: term3.id, cursor: maxCategoryCursor }), 0);
assert.equal(await store.posterminal.getCustomerCount({ posterminalId: term3.id, cursor: maxCustomerCursor }), 0);
assert.equal(await store.posterminal.getCustomerGroupCount({ posterminalId: term3.id, cursor: maxCustomerGroupCursor }), 0);
assert.equal(await store.posterminal.getCompositeProductCount({ posterminalId: term3.id, cursor: maxProductCursor }), 0);
assert.equal((await store.posterminal.getProductList({ posterminalId: term3.id, cursor: maxProductCursor })).length, 0);
assert.equal((await store.posterminal.getCategoryList({ posterminalId: term3.id, cursor: maxCategoryCursor })).length, 0);
assert.equal((await store.posterminal.getCustomerList({ posterminalId: term3.id, cursor: maxCustomerCursor })).length, 0);
assert.equal((await store.posterminal.getCustomerGroupList({ posterminalId: term3.id, cursor: maxCustomerGroupCursor })).length, 0);
// Now touch one article twice, one category, one customer, one customergroup
await store.article.updateBySku(googleAuthId, art1.sku, { name: 'Cola Classic v2' });
await store.article.updateBySku(googleAuthId, art1.sku, { name: 'Cola Classic v3' });
await store.category.rename({ googleAuthId, id: catA.id, name: 'Category A v2' });
await store.entity.update(googleAuthId, cust.id, { firstName: 'John v2' });
await store.customergroup.update(googleAuthId, cg1.id, { name: 'B2B Platinum' });
// Even though art1 was updated twice, count should be 1 (deduplicated by entity_id with MAX(id) > cursor)
assert.equal(await store.posterminal.getProductCount({ posterminalId: term3.id, cursor: maxProductCursor }), 1);
assert.equal(await store.posterminal.getCategoryCount({ posterminalId: term3.id, cursor: maxCategoryCursor }), 1);
assert.equal(await store.posterminal.getCustomerCount({ posterminalId: term3.id, cursor: maxCustomerCursor }), 1);
assert.equal(await store.posterminal.getCustomerGroupCount({ posterminalId: term3.id, cursor: maxCustomerGroupCursor }), 1);
const deltaProducts = await store.posterminal.getProductList({ posterminalId: term3.id, cursor: maxProductCursor });
const deltaCategories = await store.posterminal.getCategoryList({ posterminalId: term3.id, cursor: maxCategoryCursor });
const deltaCustomers = await store.posterminal.getCustomerList({ posterminalId: term3.id, cursor: maxCustomerCursor });
const deltaCustomerGroups = await store.posterminal.getCustomerGroupList({ posterminalId: term3.id, cursor: maxCustomerGroupCursor });
assert.equal(deltaProducts.length, 1);
assert.equal(deltaProducts[0]._id, String(art1.id));
assert.ok(Number(deltaProducts[0].lastChanged) > maxProductCursor);
assert.equal(deltaCategories.length, 1);
assert.equal(deltaCategories[0]._id, String(catA.id));
assert.ok(Number(deltaCategories[0].lastChanged) > maxCategoryCursor);
assert.equal(deltaCustomers.length, 1);
assert.equal(deltaCustomers[0].id, String(cust.id));
assert.ok(Number(deltaCustomers[0].lastChanged) > maxCustomerCursor);
assert.equal(deltaCustomerGroups.length, 1);
assert.equal(deltaCustomerGroups[0].customerGroupId, String(cg1.id));
assert.ok(Number(deltaCustomerGroups[0].lastChanged) > maxCustomerGroupCursor);
console.log('✅ ALL POSTERMINALSYNCQUEUE TESTS PASSED');
} finally {
try {
rmSync(tempDir, { recursive: true, force: true });
} catch {}
}
}
run().catch((err) => {
console.error('Test failed:', err);
process.exit(1);
});

View File

@@ -143,6 +143,7 @@ import posterminalGetCompositeProductCount from './store/posterminal/getComposit
import posterminalGetDeletedEntityCount from './store/posterminal/getDeletedEntityCount.js'; import posterminalGetDeletedEntityCount from './store/posterminal/getDeletedEntityCount.js';
import posterminalGetDeletedEntityList from './store/posterminal/getDeletedEntityList.js'; import posterminalGetDeletedEntityList from './store/posterminal/getDeletedEntityList.js';
import posterminalRecordObituary from './store/posterminal/recordObituary.js'; import posterminalRecordObituary from './store/posterminal/recordObituary.js';
import posterminalRecordSyncQueue from './store/posterminal/recordSyncQueue.js';
import posterminalGetMaxOrderIdCount from './store/posterminal/getMaxOrderIdCount.js'; import posterminalGetMaxOrderIdCount from './store/posterminal/getMaxOrderIdCount.js';
import posterminalGetCategoryList from './store/posterminal/getCategoryList.js'; import posterminalGetCategoryList from './store/posterminal/getCategoryList.js';
import posterminalGetProductList from './store/posterminal/getProductList.js'; import posterminalGetProductList from './store/posterminal/getProductList.js';
@@ -528,6 +529,7 @@ export default class LibDashStore {
getDeletedEntityCount: posterminalGetDeletedEntityCount(provider), getDeletedEntityCount: posterminalGetDeletedEntityCount(provider),
getDeletedEntityList: posterminalGetDeletedEntityList(provider), getDeletedEntityList: posterminalGetDeletedEntityList(provider),
recordObituary: posterminalRecordObituary(provider), recordObituary: posterminalRecordObituary(provider),
recordSyncQueue: posterminalRecordSyncQueue(provider),
getMaxOrderIdCount: posterminalGetMaxOrderIdCount(provider), getMaxOrderIdCount: posterminalGetMaxOrderIdCount(provider),
getCategoryList: posterminalGetCategoryList(provider), getCategoryList: posterminalGetCategoryList(provider),
getProductList: posterminalGetProductList(provider), getProductList: posterminalGetProductList(provider),

View File

@@ -24,6 +24,8 @@ export const TABLES = [
'modelconversation', 'modelconversation',
'companyuser', 'companyuser',
'companysetting', 'companysetting',
'posterminalsyncqueue',
'posterminalobituary',
'posterminalcategory', 'posterminalcategory',
'posterminal', 'posterminal',
'printer', 'printer',

View File

@@ -249,6 +249,38 @@ WHERE articlelist_id IN (
) )
`; `;
export const ENQUEUE_SYNC_BY_ARTICLE = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', a.id
FROM article a
INNER JOIN posterminal pt ON pt.company_id = a.company_id
WHERE a.id = @articleId
`;
export const ENQUEUE_OPTION_PARENT_ARTICLES_SYNC = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', a.id
FROM article a
INNER JOIN posterminal pt ON pt.company_id = a.company_id
WHERE a.optionslist_id IN (
SELECT ola.optionslist_id
FROM optionslistarticle ola
WHERE ola.article_id = @articleId
)
`;
export const ENQUEUE_STUECKLISTE_ARTICLES_SYNC = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', a.id
FROM article a
INNER JOIN posterminal pt ON pt.company_id = a.company_id
WHERE a.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)

View File

@@ -240,3 +240,40 @@ WHERE id IN (
) )
`; `;
export const ENQUEUE_SUBTREE_CATEGORIES_SYNC = `
;WITH cats AS (
SELECT id FROM category WHERE id = @id
UNION ALL
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
)
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'category', cats.id
FROM cats
INNER JOIN posterminal pt ON pt.company_id = @companyId
`;
export const ENQUEUE_SUBTREE_ARTICLES_SYNC = `
;WITH cats AS (
SELECT id FROM category WHERE id = @id
UNION ALL
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
),
arts AS (
SELECT ca.article_id AS 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 AS 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
)
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', arts.id
FROM arts
INNER JOIN posterminal pt ON pt.company_id = @companyId
`;

View File

@@ -136,6 +136,12 @@ WHERE id = @id
AND customergroup_id = @customergroupId AND customergroup_id = @customergroupId
`; `;
export const TOUCH_UPDATED_AT = `
UPDATE entity
SET updated_at = SYSUTCDATETIME()
WHERE id = @id
`;
export const DELETE_BY_COMPANY_AND_ID = ` export const DELETE_BY_COMPANY_AND_ID = `
DELETE e DELETE e
FROM entity e FROM entity e

View File

@@ -172,53 +172,85 @@ WHERE token = @token
`; `;
export const COUNT_PRODUCTS = ` export const COUNT_PRODUCTS = `
SELECT COUNT(DISTINCT a.id) AS count SELECT COUNT(*) AS count
FROM article a FROM (
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id SELECT MAX(q.id) AS syncId, a.id
WHERE EXISTS ( FROM posterminalsyncqueue q
INNER JOIN article a ON a.id = q.entity_id
INNER JOIN posterminal pt ON pt.id = q.posterminal_id AND pt.company_id = a.company_id
WHERE q.posterminal_id = @posterminalId
AND q.entity_type = 'article'
AND EXISTS (
SELECT 1 SELECT 1
FROM categoryarticle ca FROM categoryarticle ca
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
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) * 1000000 + (a.id % 1000000)) > @cursor GROUP BY a.id
HAVING MAX(q.id) > @cursor
) t
`; `;
export const COUNT_CATEGORIES = ` export const COUNT_CATEGORIES = `
SELECT COUNT(*) AS count SELECT COUNT(*) AS count
FROM category c FROM (
INNER JOIN posterminalcategory pc ON pc.category_id = c.id SELECT MAX(q.id) AS syncId, c.id
FROM posterminalsyncqueue q
INNER JOIN category c ON c.id = q.entity_id
INNER JOIN posterminalcategory pc ON pc.category_id = c.id AND pc.posterminal_id = q.posterminal_id
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
INNER JOIN company co ON co.id = pt.company_id INNER JOIN company co ON co.id = pt.company_id
WHERE pc.posterminal_id = @posterminalId WHERE q.posterminal_id = @posterminalId
AND q.entity_type = 'category'
AND (co.root_category_id IS NULL OR c.id <> co.root_category_id) AND (co.root_category_id IS NULL OR c.id <> co.root_category_id)
AND DATEDIFF_BIG(second, '1970-01-01', c.updated_at) > @cursor GROUP BY c.id
HAVING MAX(q.id) > @cursor
) t
`; `;
export const COUNT_CUSTOMERS = ` export const COUNT_CUSTOMERS = `
SELECT COUNT(*) AS count SELECT COUNT(*) AS count
FROM entity e FROM (
SELECT MAX(q.id) AS syncId, e.id
FROM posterminalsyncqueue q
INNER JOIN entity e ON e.id = q.entity_id
INNER JOIN customergroup cg ON cg.id = e.customergroup_id INNER JOIN customergroup cg ON cg.id = e.customergroup_id
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id INNER JOIN posterminal pt ON pt.id = q.posterminal_id AND pt.company_id = cg.company_id
WHERE cg.name <> 'Lieferanten' WHERE q.posterminal_id = @posterminalId
AND DATEDIFF_BIG(second, '1970-01-01', e.updated_at) > @cursor AND q.entity_type = 'customer'
AND cg.name <> 'Lieferanten'
GROUP BY e.id
HAVING MAX(q.id) > @cursor
) t
`; `;
export const COUNT_CUSTOMER_GROUPS = ` export const COUNT_CUSTOMER_GROUPS = `
SELECT COUNT(*) AS count SELECT COUNT(*) AS count
FROM customergroup cg FROM (
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id SELECT MAX(q.id) AS syncId, cg.id
WHERE cg.name <> 'Lieferanten' FROM posterminalsyncqueue q
AND DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) > @cursor INNER JOIN customergroup cg ON cg.id = q.entity_id
INNER JOIN posterminal pt ON pt.id = q.posterminal_id AND pt.company_id = cg.company_id
WHERE q.posterminal_id = @posterminalId
AND q.entity_type = 'customergroup'
AND cg.name <> 'Lieferanten'
GROUP BY cg.id
HAVING MAX(q.id) > @cursor
) t
`; `;
export const COUNT_COMPOSITE_PRODUCTS = ` export const COUNT_COMPOSITE_PRODUCTS = `
SELECT COUNT(DISTINCT a.id) AS count SELECT COUNT(*) AS count
FROM article a FROM (
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id SELECT MAX(q.id) AS syncId, a.id
WHERE a.articlelist_id IS NOT NULL FROM posterminalsyncqueue q
INNER JOIN article a ON a.id = q.entity_id
INNER JOIN posterminal pt ON pt.id = q.posterminal_id AND pt.company_id = a.company_id
WHERE q.posterminal_id = @posterminalId
AND q.entity_type = 'article'
AND a.articlelist_id IS NOT NULL
AND EXISTS ( AND EXISTS (
SELECT 1 SELECT 1
FROM articlelistarticle ala FROM articlelistarticle ala
@@ -231,7 +263,9 @@ 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) * 1000000 + (a.id % 1000000)) > @cursor GROUP BY a.id
HAVING MAX(q.id) > @cursor
) t
`; `;
export const MAX_ORDER_ID = ` export const MAX_ORDER_ID = `
@@ -255,16 +289,21 @@ SELECT TOP (@limit)
WHERE cp.category_id = c.id WHERE cp.category_id = c.id
ORDER BY cp.sort ASC, cp.id ASC ORDER BY cp.sort ASC, cp.id ASC
) AS imgHash, ) AS imgHash,
DATEDIFF_BIG(second, '1970-01-01', c.updated_at) AS lastChanged sq.syncId AS lastChanged
FROM category c FROM (
INNER JOIN posterminalcategory pc ON pc.category_id = c.id SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'category'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN category c ON c.id = sq.entity_id
INNER JOIN posterminalcategory pc ON pc.category_id = c.id AND pc.posterminal_id = @posterminalId
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
INNER JOIN company co ON co.id = pt.company_id INNER JOIN company co ON co.id = pt.company_id
LEFT JOIN categorytext ct ON ct.category_id = c.id AND ct.language_id = co.default_language_id LEFT JOIN categorytext ct ON ct.category_id = c.id AND ct.language_id = co.default_language_id
WHERE pc.posterminal_id = @posterminalId WHERE (co.root_category_id IS NULL OR c.id <> co.root_category_id)
AND (co.root_category_id IS NULL OR c.id <> co.root_category_id) ORDER BY sq.syncId ASC
AND DATEDIFF_BIG(second, '1970-01-01', c.updated_at) > @cursor
ORDER BY DATEDIFF_BIG(second, '1970-01-01', c.updated_at) ASC, c.id ASC
`; `;
export const LIST_POS_PRODUCTS = ` export const LIST_POS_PRODUCTS = `
@@ -275,7 +314,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) * 1000000 + (a.id % 1000000)) AS lastChanged, sq.syncId AS lastChanged,
COALESCE( COALESCE(
( (
SELECT TOP 1 p.hash SELECT TOP 1 p.hash
@@ -374,7 +413,14 @@ SELECT TOP (@limit)
co.id AS companyId, co.id AS companyId,
co.root_category_id AS rootCategoryId, co.root_category_id AS rootCategoryId,
co.poscustomergroup_id AS posCustomerGroupId co.poscustomergroup_id AS posCustomerGroupId
FROM article a FROM (
SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'article'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN article a ON a.id = sq.entity_id
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
INNER JOIN company co ON co.id = a.company_id INNER JOIN company co ON co.id = a.company_id
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = co.default_language_id LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = co.default_language_id
@@ -388,8 +434,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) * 1000000 + (a.id % 1000000)) > @cursor ORDER BY sq.syncId 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 = `
@@ -411,13 +456,19 @@ SELECT TOP (@limit)
cg.name, cg.name,
CASE WHEN cg.id = co.poscustomergroup_id THEN '1' ELSE '0' END AS standard, CASE WHEN cg.id = co.poscustomergroup_id THEN '1' ELSE '0' END AS standard,
'0.000000' AS discountPercent, '0.000000' AS discountPercent,
DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) AS lastChanged sq.syncId AS lastChanged
FROM customergroup cg FROM (
SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'customergroup'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN customergroup cg ON cg.id = sq.entity_id
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
INNER JOIN company co ON co.id = pt.company_id INNER JOIN company co ON co.id = pt.company_id
WHERE cg.name <> 'Lieferanten' WHERE cg.name <> 'Lieferanten'
AND DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) > @cursor ORDER BY sq.syncId ASC
ORDER BY DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) ASC, cg.id ASC
`; `;
export const LIST_POS_CUSTOMERS = ` export const LIST_POS_CUSTOMERS = `
@@ -469,13 +520,19 @@ SELECT TOP (@limit)
WHERE ec.entity_id = e.id AND ec.contacttype IN ('email', 'mail') WHERE ec.entity_id = e.id AND ec.contacttype IN ('email', 'mail')
ORDER BY ec.sort ASC, ec.id ASC ORDER BY ec.sort ASC, ec.id ASC
) AS email, ) AS email,
DATEDIFF_BIG(second, '1970-01-01', e.updated_at) AS lastChanged sq.syncId AS lastChanged
FROM entity e FROM (
SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'customer'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN entity e ON e.id = sq.entity_id
INNER JOIN customergroup cg ON cg.id = e.customergroup_id INNER JOIN customergroup cg ON cg.id = e.customergroup_id
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
WHERE cg.name <> 'Lieferanten' WHERE cg.name <> 'Lieferanten'
AND DATEDIFF_BIG(second, '1970-01-01', e.updated_at) > @cursor ORDER BY sq.syncId ASC
ORDER BY DATEDIFF_BIG(second, '1970-01-01', e.updated_at) ASC, e.id ASC
`; `;
@@ -541,6 +598,34 @@ FROM posterminal pt
WHERE pt.company_id = @companyId AND pt.token IS NOT NULL WHERE pt.company_id = @companyId AND pt.token IS NOT NULL
`; `;
export const RECORD_SYNC_QUEUE = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, @entityType, @entityId
FROM posterminal pt
WHERE pt.company_id = @companyId
`;
export const SEED_SYNC_QUEUE_FOR_TERMINAL = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT @posterminalId, 'customergroup', cg.id
FROM customergroup cg
WHERE cg.company_id = @companyId
UNION ALL
SELECT @posterminalId, 'customer', e.id
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
UNION ALL
SELECT @posterminalId, 'category', c.id
FROM category c
INNER JOIN posterminalcategory pc ON pc.category_id = c.id
WHERE pc.posterminal_id = @posterminalId
UNION ALL
SELECT @posterminalId, 'article', a.id
FROM article a
WHERE a.company_id = @companyId
`;
export const COUNT_DELETED_ENTITIES = ` export const COUNT_DELETED_ENTITIES = `
SELECT COUNT(*) AS count SELECT COUNT(*) AS count
FROM posterminalobituary FROM posterminalobituary

View File

@@ -417,6 +417,23 @@ BEGIN
END END
`; `;
export const INIT_POSTERMINALSYNCQUEUE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'posterminalsyncqueue')
BEGIN
CREATE TABLE posterminalsyncqueue (
id INT IDENTITY(1,1) PRIMARY KEY,
posterminal_id INT NOT NULL,
entity_type NVARCHAR(50) NOT NULL,
entity_id INT NOT NULL,
CONSTRAINT CK_posterminalsyncqueue_entity_type CHECK (entity_type IN ('article', 'category', 'customer', 'customergroup')),
CONSTRAINT FK_posterminalsyncqueue_posterminal FOREIGN KEY (posterminal_id) REFERENCES posterminal(id) ON DELETE CASCADE
);
CREATE INDEX IX_posterminalsyncqueue_posterminal_id
ON posterminalsyncqueue (posterminal_id, id);
END
`;
export const INIT_ARTICLEEAN = ` export const INIT_ARTICLEEAN = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articleean') IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articleean')
@@ -1642,6 +1659,7 @@ export const SCHEMA_SCRIPTS = [
INIT_POSTERMINAL, INIT_POSTERMINAL,
INIT_POSTERMINALCATEGORY, INIT_POSTERMINALCATEGORY,
INIT_POSTERMINALOBITUARY, INIT_POSTERMINALOBITUARY,
INIT_POSTERMINALSYNCQUEUE,
INIT_PRINTER, INIT_PRINTER,
INIT_LABELTEMPLATE, INIT_LABELTEMPLATE,
INIT_FILE, INIT_FILE,

View File

@@ -13,6 +13,7 @@ export const {
SET_PARENT_SORT, SET_PARENT_SORT,
SET_IS_PARENT, TOGGLE_PARENT, TOUCH_UPDATED_AT, TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT, SET_IS_PARENT, TOGGLE_PARENT, TOUCH_UPDATED_AT, TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT,
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT, TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT,
ENQUEUE_SYNC_BY_ARTICLE, ENQUEUE_OPTION_PARENT_ARTICLES_SYNC, ENQUEUE_STUECKLISTE_ARTICLES_SYNC,
UNLINK_CHILD, UPDATE_BRAND, UPDATE_PARENT_BY_SKU, UPDATE_TRACK_STOCK, UPDATE_UNIT, 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;

View File

@@ -164,3 +164,40 @@ WHERE id IN (
) AND parent.parentarticle_id IS NOT NULL ) AND parent.parentarticle_id IS NOT NULL
) )
`; `;
export const ENQUEUE_SUBTREE_CATEGORIES_SYNC = `
WITH RECURSIVE cats AS (
SELECT id FROM category WHERE id = @id
UNION ALL
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
)
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'category', cats.id
FROM cats
INNER JOIN posterminal pt ON pt.company_id = @companyId
`;
export const ENQUEUE_SUBTREE_ARTICLES_SYNC = `
WITH RECURSIVE cats AS (
SELECT id FROM category WHERE id = @id
UNION ALL
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
),
arts AS (
SELECT ca.article_id AS 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 AS 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
)
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', arts.id
FROM arts
INNER JOIN posterminal pt ON pt.company_id = @companyId
`;

View File

@@ -7,8 +7,8 @@ export const {
GET_BY_COMPANY_AND_ID, GET_CUSTOMER_BY_ID, GET_GROUP_ID_FOR_SUPPLIER, GET_BY_COMPANY_AND_ID, GET_CUSTOMER_BY_ID, GET_GROUP_ID_FOR_SUPPLIER,
GET_SUPPLIER_BY_ID, LIST_BY_COMPANY, LIST_BY_COMPANY_AND_GROUP, GET_SUPPLIER_BY_ID, LIST_BY_COMPANY, LIST_BY_COMPANY_AND_GROUP,
LIST_CUSTOMERS_BY_COMPANY, LIST_SUPPLIERS_BY_COMPANY, LIST_CUSTOMERS_BY_COMPANY, LIST_SUPPLIERS_BY_COMPANY,
NUMBER_TAKEN_IN_COMPANY, POS_GROUP_NAME, SUPPLIER_GROUP_NAME, UPDATE, NUMBER_TAKEN_IN_COMPANY, POS_GROUP_NAME, SUPPLIER_GROUP_NAME,
UPDATE_NAME, TOUCH_UPDATED_AT, UPDATE, UPDATE_NAME,
} = queries; } = queries;
export const DELETE_BY_COMPANY_AND_ID = ` export const DELETE_BY_COMPANY_AND_ID = `

View File

@@ -10,7 +10,8 @@ export const {
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, LIST_POS_MANDATED_OPTIONS, LIST_POS_PARENT_OPTIONS,
TOUCH_LASTSEEN, RECORD_OBITUARY, COUNT_DELETED_ENTITIES, TOUCH_LASTSEEN, RECORD_OBITUARY, RECORD_SYNC_QUEUE, SEED_SYNC_QUEUE_FOR_TERMINAL,
COUNT_DELETED_ENTITIES,
} = queries; } = queries;
export const SET_ON = ` export const SET_ON = `
@@ -50,13 +51,19 @@ SELECT
cg.name, cg.name,
CASE WHEN cg.id = co.poscustomergroup_id THEN '1' ELSE '0' END AS standard, CASE WHEN cg.id = co.poscustomergroup_id THEN '1' ELSE '0' END AS standard,
'0.000000' AS discountPercent, '0.000000' AS discountPercent,
CAST(strftime('%s', cg.updated_at) AS INTEGER) AS lastChanged sq.syncId AS lastChanged
FROM customergroup cg FROM (
SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'customergroup'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN customergroup cg ON cg.id = sq.entity_id
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
INNER JOIN company co ON co.id = pt.company_id INNER JOIN company co ON co.id = pt.company_id
WHERE cg.name <> 'Lieferanten' WHERE cg.name <> 'Lieferanten'
AND CAST(strftime('%s', cg.updated_at) AS INTEGER) > @cursor ORDER BY sq.syncId ASC
ORDER BY lastChanged ASC, cg.id ASC
LIMIT @limit LIMIT @limit
`; `;
@@ -116,13 +123,19 @@ SELECT
ORDER BY ec.sort ASC, ec.id ASC ORDER BY ec.sort ASC, ec.id ASC
LIMIT 1 LIMIT 1
) AS email, ) AS email,
CAST(strftime('%s', e.updated_at) AS INTEGER) AS lastChanged sq.syncId AS lastChanged
FROM entity e FROM (
SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'customer'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN entity e ON e.id = sq.entity_id
INNER JOIN customergroup cg ON cg.id = e.customergroup_id INNER JOIN customergroup cg ON cg.id = e.customergroup_id
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
WHERE cg.name <> 'Lieferanten' WHERE cg.name <> 'Lieferanten'
AND CAST(strftime('%s', e.updated_at) AS INTEGER) > @cursor ORDER BY sq.syncId ASC
ORDER BY lastChanged ASC, e.id ASC
LIMIT @limit LIMIT @limit
`; `;
@@ -141,16 +154,21 @@ SELECT
ORDER BY cp.sort ASC, cp.id ASC ORDER BY cp.sort ASC, cp.id ASC
LIMIT 1 LIMIT 1
) AS imgHash, ) AS imgHash,
CAST(strftime('%s', c.updated_at) AS INTEGER) AS lastChanged sq.syncId AS lastChanged
FROM category c FROM (
INNER JOIN posterminalcategory pc ON pc.category_id = c.id SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'category'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN category c ON c.id = sq.entity_id
INNER JOIN posterminalcategory pc ON pc.category_id = c.id AND pc.posterminal_id = @posterminalId
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
INNER JOIN company co ON co.id = pt.company_id INNER JOIN company co ON co.id = pt.company_id
LEFT JOIN categorytext ct ON ct.category_id = c.id AND ct.language_id = co.default_language_id LEFT JOIN categorytext ct ON ct.category_id = c.id AND ct.language_id = co.default_language_id
WHERE pc.posterminal_id = @posterminalId WHERE (co.root_category_id IS NULL OR c.id <> co.root_category_id)
AND (co.root_category_id IS NULL OR c.id <> co.root_category_id) ORDER BY sq.syncId ASC
AND CAST(strftime('%s', c.updated_at) AS INTEGER) > @cursor
ORDER BY lastChanged ASC, c.id ASC
LIMIT @limit LIMIT @limit
`; `;
@@ -162,7 +180,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) * 1000000 + (a.id % 1000000)) AS lastChanged, sq.syncId AS lastChanged,
COALESCE( COALESCE(
( (
SELECT p.hash SELECT p.hash
@@ -265,7 +283,14 @@ SELECT
co.id AS companyId, co.id AS companyId,
co.root_category_id AS rootCategoryId, co.root_category_id AS rootCategoryId,
co.poscustomergroup_id AS posCustomerGroupId co.poscustomergroup_id AS posCustomerGroupId
FROM article a FROM (
SELECT entity_id, MAX(id) AS syncId
FROM posterminalsyncqueue
WHERE posterminal_id = @posterminalId AND entity_type = 'article'
GROUP BY entity_id
HAVING MAX(id) > @cursor
) sq
INNER JOIN article a ON a.id = sq.entity_id
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
INNER JOIN company co ON co.id = a.company_id INNER JOIN company co ON co.id = a.company_id
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = co.default_language_id LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = co.default_language_id
@@ -279,8 +304,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 (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) > @cursor ORDER BY sq.syncId ASC
ORDER BY (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) ASC
LIMIT @limit LIMIT @limit
`; `;

View File

@@ -660,6 +660,18 @@ CREATE INDEX IF NOT EXISTS IX_posterminalobituary_posterminal_id
ON posterminalobituary (posterminal_id, id); ON posterminalobituary (posterminal_id, id);
`; `;
export const INIT_POSTERMINALSYNCQUEUE = `
CREATE TABLE IF NOT EXISTS posterminalsyncqueue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
posterminal_id INTEGER NOT NULL,
entity_type TEXT NOT NULL CHECK (entity_type IN ('article', 'category', 'customer', 'customergroup')),
entity_id INTEGER NOT NULL,
FOREIGN KEY (posterminal_id) REFERENCES posterminal(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS IX_posterminalsyncqueue_posterminal_id
ON posterminalsyncqueue (posterminal_id, id);
`;
export const INIT_PRINTER = ` export const INIT_PRINTER = `
CREATE TABLE IF NOT EXISTS printer ( CREATE TABLE IF NOT EXISTS printer (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1206,6 +1218,7 @@ export const SCHEMA_SCRIPTS = [
INIT_POSTERMINAL, INIT_POSTERMINAL,
INIT_POSTERMINALCATEGORY, INIT_POSTERMINALCATEGORY,
INIT_POSTERMINALOBITUARY, INIT_POSTERMINALOBITUARY,
INIT_POSTERMINALSYNCQUEUE,
INIT_PRINTER, INIT_PRINTER,
INIT_LABELTEMPLATE, INIT_LABELTEMPLATE,
INIT_FILE, INIT_FILE,

View File

@@ -273,6 +273,8 @@ export default function addChild(provider) {
// Touch parent article updated_at so POS and other consumers refresh parent options // Touch parent article updated_at so POS and other consumers refresh parent options
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId }); await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: childId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: parentId });
return load(googleAuthId, childSku, runner); return load(googleAuthId, childSku, runner);
}; };

View File

@@ -8,8 +8,14 @@ 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 {
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js'; TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} 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';
@@ -24,6 +30,7 @@ export default function addImage(provider) {
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; const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* 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 +114,7 @@ export default function addImage(provider) {
} }
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
return { return {
picId, picId,

View File

@@ -173,6 +173,8 @@ export default function attachChild(provider) {
}); });
} }
} }
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: prevParentId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: prevParentId });
} }
// Assign parentsort if not already child of this parent // Assign parentsort if not already child of this parent
@@ -305,6 +307,11 @@ export default function attachChild(provider) {
}); });
} }
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: child.id });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: child.id });
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: parentId });
const updated = await load(googleAuthId, childSku, runner); const updated = await load(googleAuthId, childSku, runner);
const children = await list(googleAuthId, parentSku, runner); const children = await list(googleAuthId, parentSku, runner);

View File

@@ -1,10 +1,12 @@
import { import {
CREATE as MSSQL_CREATE_ARTICLE, CREATE as MSSQL_CREATE_ARTICLE,
CHECK_HAS_WAREHOUSE as MSSQL_CHECK_HAS_WAREHOUSE, CHECK_HAS_WAREHOUSE as MSSQL_CHECK_HAS_WAREHOUSE,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js'; } from '../../providers/mssql/queries/article.js';
import { import {
CREATE as SQLITE_CREATE_ARTICLE, CREATE as SQLITE_CREATE_ARTICLE,
CHECK_HAS_WAREHOUSE as SQLITE_CHECK_HAS_WAREHOUSE, CHECK_HAS_WAREHOUSE as SQLITE_CHECK_HAS_WAREHOUSE,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js'; } from '../../providers/sqlite3/queries/article.js';
import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/categoryarticle.js'; import { ADD as MSSQL_ADD_CATEGORYARTICLE } from '../../providers/mssql/queries/categoryarticle.js';
import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js'; import { ADD as SQLITE_ADD_CATEGORYARTICLE } from '../../providers/sqlite3/queries/categoryarticle.js';
@@ -30,6 +32,7 @@ export default function createUnderParent(provider) {
const checkHasWarehouseSql = isSqlite ? SQLITE_CHECK_HAS_WAREHOUSE : MSSQL_CHECK_HAS_WAREHOUSE; const checkHasWarehouseSql = isSqlite ? SQLITE_CHECK_HAS_WAREHOUSE : MSSQL_CHECK_HAS_WAREHOUSE;
const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE; const addCategoryArticleSql = isSqlite ? SQLITE_ADD_CATEGORYARTICLE : MSSQL_ADD_CATEGORYARTICLE;
const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT; const createArticleTextSql = isSqlite ? SQLITE_CREATE_ARTICLETEXT : MSSQL_CREATE_ARTICLETEXT;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Creates a new article under a given parent category, allocating a SKU and * Creates a new article under a given parent category, allocating a SKU and
@@ -118,6 +121,8 @@ export default function createUnderParent(provider) {
await initArticleWarehouseSummary(runner, { companyId, articleId: id }); await initArticleWarehouseSummary(runner, { companyId, articleId: id });
} }
await runner.query(enqueueSyncSql, { articleId: id });
return { return {
id, id,
parentId, parentId,

View File

@@ -51,6 +51,7 @@ export default function del(provider) {
// Touch any parent articles that used this article as an option before deleting it // 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 }); await runner.query(articleQueries.TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT, { articleId: id });
await runner.query(articleQueries.ENQUEUE_OPTION_PARENT_ARTICLES_SYNC, { 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) {

View File

@@ -2,16 +2,24 @@ 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 {
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js'; TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} 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';
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 isSqlite = provider?.dialect === 'sqlite3';
const touchArticleSql = provider?.dialect === 'sqlite3' ? SQLITE_TOUCH : MSSQL_TOUCH; const sql = isSqlite ? SQLITE_QUERY : MSSQL_QUERY;
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* 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.
@@ -86,6 +94,7 @@ export default function deleteImage(provider) {
}); });
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
}; };
if (tx) { if (tx) {

View File

@@ -1,7 +1,13 @@
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 {
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js'; TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} 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';
@@ -11,6 +17,7 @@ export default function orderImages(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; const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Reorders the images of an article by its SKU. * Reorders the images of an article by its SKU.
@@ -73,6 +80,7 @@ export default function orderImages(provider) {
fullHashes, fullHashes,
}); });
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
} }
return { fullHashes }; return { fullHashes };

View File

@@ -88,6 +88,7 @@ export default function removeChild(provider) {
// Touch parent article updated_at so POS and other consumers refresh parent options // Touch parent article updated_at so POS and other consumers refresh parent options
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId }); await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: parentId });
return { return {
success: true, success: true,
@@ -134,8 +135,11 @@ export default function removeChild(provider) {
parentSku, parentSku,
}); });
// Touch parent article updated_at so POS and other consumers refresh parent options // Touch child and parent article updated_at so POS and other consumers refresh parent options
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: childId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: childId });
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId }); await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: parentId });
return { return {
success: true, success: true,

View File

@@ -1,10 +1,12 @@
import { import {
GET_BRAND_BY_COMPANY_AND_ID as MSSQL_GET_BRAND, GET_BRAND_BY_COMPANY_AND_ID as MSSQL_GET_BRAND,
UPDATE_BRAND as MSSQL_UPDATE_BRAND, UPDATE_BRAND as MSSQL_UPDATE_BRAND,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js'; } from '../../providers/mssql/queries/article.js';
import { import {
GET_BRAND_BY_COMPANY_AND_ID as SQLITE_GET_BRAND, GET_BRAND_BY_COMPANY_AND_ID as SQLITE_GET_BRAND,
UPDATE_BRAND as SQLITE_UPDATE_BRAND, UPDATE_BRAND as SQLITE_UPDATE_BRAND,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js'; } from '../../providers/sqlite3/queries/article.js';
import queryBySku from './queryBySku.js'; import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js';
@@ -18,6 +20,7 @@ export default function setBrandIdBySku(provider) {
const getBrandSql = isSqlite ? SQLITE_GET_BRAND : MSSQL_GET_BRAND; const getBrandSql = isSqlite ? SQLITE_GET_BRAND : MSSQL_GET_BRAND;
const updateBrandSql = isSqlite ? SQLITE_UPDATE_BRAND : MSSQL_UPDATE_BRAND; const updateBrandSql = isSqlite ? SQLITE_UPDATE_BRAND : MSSQL_UPDATE_BRAND;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Sets (or clears) the brand of an article by its SKU. * Sets (or clears) the brand of an article by its SKU.
@@ -79,6 +82,8 @@ export default function setBrandIdBySku(provider) {
throw new Error('Article not found'); throw new Error('Article not found');
} }
await runner.query(enqueueSyncSql, { articleId });
// null = no brand; treat as distinct sentinel so first assignment is logged // null = no brand; treat as distinct sentinel so first assignment is logged
await recordBrandChangeHistory(runner, { await recordBrandChangeHistory(runner, {
articleId, articleId,

View File

@@ -5,6 +5,7 @@ import {
INSERT_POSTERMINAL_OBITUARY as MSSQL_INSERT_OBITUARY, INSERT_POSTERMINAL_OBITUARY as MSSQL_INSERT_OBITUARY,
LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE as MSSQL_LIST_TERMINALS, LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE as MSSQL_LIST_TERMINALS,
TOUCH_UPDATED_AT as MSSQL_TOUCH, TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} 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,
@@ -13,6 +14,7 @@ import {
INSERT_POSTERMINAL_OBITUARY as SQLITE_INSERT_OBITUARY, INSERT_POSTERMINAL_OBITUARY as SQLITE_INSERT_OBITUARY,
LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE as SQLITE_LIST_TERMINALS, LIST_PAIRED_POSTERMINAL_IDS_BY_ARTICLE as SQLITE_LIST_TERMINALS,
TOUCH_UPDATED_AT as SQLITE_TOUCH, TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} 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';
@@ -32,6 +34,7 @@ export default function setCategoryIdsBySku(provider) {
const listTerminalsSql = isSqlite ? SQLITE_LIST_TERMINALS : MSSQL_LIST_TERMINALS; const listTerminalsSql = isSqlite ? SQLITE_LIST_TERMINALS : MSSQL_LIST_TERMINALS;
const insertObituarySql = isSqlite ? SQLITE_INSERT_OBITUARY : MSSQL_INSERT_OBITUARY; const insertObituarySql = isSqlite ? SQLITE_INSERT_OBITUARY : MSSQL_INSERT_OBITUARY;
const clearObituarySql = isSqlite ? SQLITE_CLEAR_OBITUARY : MSSQL_CLEAR_OBITUARY; const clearObituarySql = isSqlite ? SQLITE_CLEAR_OBITUARY : MSSQL_CLEAR_OBITUARY;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Replaces the category membership of an article by its SKU. * Replaces the category membership of an article by its SKU.
@@ -150,6 +153,7 @@ export default function setCategoryIdsBySku(provider) {
// Touch article updated_at so POS and other consumers pick up category assignment changes // Touch article updated_at so POS and other consumers pick up category assignment changes
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
const article = await load(articleId, languageId, runner); const article = await load(articleId, languageId, runner);
if (!article) { if (!article) {

View File

@@ -3,12 +3,14 @@ import {
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, TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} 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, TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} 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';
@@ -25,6 +27,7 @@ export default function setEansBySku(provider) {
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; const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Replaces the EAN list of an article by its SKU. * Replaces the EAN list of an article by its SKU.
@@ -103,6 +106,7 @@ export default function setEansBySku(provider) {
// Touch article updated_at so POS and other consumers pick up the barcode change // Touch article updated_at so POS and other consumers pick up the barcode change
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
const article = await load(articleId, languageId, runner); const article = await load(articleId, languageId, runner);
if (!article) { if (!article) {

View File

@@ -56,6 +56,9 @@ export default function setParentBySku(provider) {
parentArticleId: null, parentArticleId: null,
}); });
await runner.query(queries.TOUCH_UPDATED_AT, { articleId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId });
if (prevParentId) { if (prevParentId) {
const prevParentTextRes = await runner.query( const prevParentTextRes = await runner.query(
'SELECT sku FROM article WHERE id = @prevParentId', 'SELECT sku FROM article WHERE id = @prevParentId',
@@ -74,6 +77,9 @@ export default function setParentBySku(provider) {
companyuserId, companyuserId,
text: `Variante "${childName}" (${sku}) getrennt`, text: `Variante "${childName}" (${sku}) getrennt`,
}); });
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: prevParentId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: prevParentId });
} }
const unlinked = await load(articleId, languageId, runner); const unlinked = await load(articleId, languageId, runner);
@@ -141,6 +147,11 @@ export default function setParentBySku(provider) {
parentSku, parentSku,
}); });
await runner.query(queries.TOUCH_UPDATED_AT, { articleId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId });
await runner.query(queries.TOUCH_UPDATED_AT, { articleId: parentId });
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: parentId });
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');

View File

@@ -9,10 +9,14 @@ import {
import { import {
TOUCH_UPDATED_AT as MSSQL_TOUCH, TOUCH_UPDATED_AT as MSSQL_TOUCH,
TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT as MSSQL_TOUCH_OPTION_PARENTS, TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT as MSSQL_TOUCH_OPTION_PARENTS,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
ENQUEUE_OPTION_PARENT_ARTICLES_SYNC as MSSQL_ENQUEUE_OPTION_PARENTS,
} from '../../providers/mssql/queries/article.js'; } from '../../providers/mssql/queries/article.js';
import { import {
TOUCH_UPDATED_AT as SQLITE_TOUCH, TOUCH_UPDATED_AT as SQLITE_TOUCH,
TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT as SQLITE_TOUCH_OPTION_PARENTS, TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT as SQLITE_TOUCH_OPTION_PARENTS,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
ENQUEUE_OPTION_PARENT_ARTICLES_SYNC as SQLITE_ENQUEUE_OPTION_PARENTS,
} from '../../providers/sqlite3/queries/article.js'; } 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';
@@ -75,6 +79,8 @@ export default function setPricesBySku(provider) {
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 touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const touchOptionParentsSql = isSqlite ? SQLITE_TOUCH_OPTION_PARENTS : MSSQL_TOUCH_OPTION_PARENTS; const touchOptionParentsSql = isSqlite ? SQLITE_TOUCH_OPTION_PARENTS : MSSQL_TOUCH_OPTION_PARENTS;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
const enqueueOptionParentsSql = isSqlite ? SQLITE_ENQUEUE_OPTION_PARENTS : MSSQL_ENQUEUE_OPTION_PARENTS;
/** /**
* Replaces the price list of an article by its SKU. * Replaces the price list of an article by its SKU.
@@ -153,9 +159,11 @@ export default function setPricesBySku(provider) {
// Touch article updated_at so POS and other consumers pick up the price change // Touch article updated_at so POS and other consumers pick up the price change
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
// Touch any parent articles using this article as an option so their deposit price updates // Touch any parent articles using this article as an option so their deposit price updates
await runner.query(touchOptionParentsSql, { articleId }); await runner.query(touchOptionParentsSql, { articleId });
await runner.query(enqueueOptionParentsSql, { articleId });
const article = await load(articleId, languageId, runner); const article = await load(articleId, languageId, runner);
if (!article) { if (!article) {

View File

@@ -2,11 +2,13 @@ import {
UPDATE_TRACK_STOCK as MSSQL_UPDATE_TRACK_STOCK, UPDATE_TRACK_STOCK as MSSQL_UPDATE_TRACK_STOCK,
CHECK_HAS_WAREHOUSE as MSSQL_CHECK_HAS_WAREHOUSE, CHECK_HAS_WAREHOUSE as MSSQL_CHECK_HAS_WAREHOUSE,
CHECK_ARTICLE_WAREHOUSE_STOCKS as MSSQL_CHECK_ARTICLE_WAREHOUSE_STOCKS, CHECK_ARTICLE_WAREHOUSE_STOCKS as MSSQL_CHECK_ARTICLE_WAREHOUSE_STOCKS,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js'; } from '../../providers/mssql/queries/article.js';
import { import {
UPDATE_TRACK_STOCK as SQLITE_UPDATE_TRACK_STOCK, UPDATE_TRACK_STOCK as SQLITE_UPDATE_TRACK_STOCK,
CHECK_HAS_WAREHOUSE as SQLITE_CHECK_HAS_WAREHOUSE, CHECK_HAS_WAREHOUSE as SQLITE_CHECK_HAS_WAREHOUSE,
CHECK_ARTICLE_WAREHOUSE_STOCKS as SQLITE_CHECK_ARTICLE_WAREHOUSE_STOCKS, CHECK_ARTICLE_WAREHOUSE_STOCKS as SQLITE_CHECK_ARTICLE_WAREHOUSE_STOCKS,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js'; } from '../../providers/sqlite3/queries/article.js';
import queryBySku from './queryBySku.js'; import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js';
@@ -26,6 +28,7 @@ export default function setTrackStockBySku(provider) {
const sql = isSqlite ? SQLITE_UPDATE_TRACK_STOCK : MSSQL_UPDATE_TRACK_STOCK; const sql = isSqlite ? SQLITE_UPDATE_TRACK_STOCK : MSSQL_UPDATE_TRACK_STOCK;
const checkHasWarehouseSql = isSqlite ? SQLITE_CHECK_HAS_WAREHOUSE : MSSQL_CHECK_HAS_WAREHOUSE; const checkHasWarehouseSql = isSqlite ? SQLITE_CHECK_HAS_WAREHOUSE : MSSQL_CHECK_HAS_WAREHOUSE;
const checkStocksSql = isSqlite ? SQLITE_CHECK_ARTICLE_WAREHOUSE_STOCKS : MSSQL_CHECK_ARTICLE_WAREHOUSE_STOCKS; const checkStocksSql = isSqlite ? SQLITE_CHECK_ARTICLE_WAREHOUSE_STOCKS : MSSQL_CHECK_ARTICLE_WAREHOUSE_STOCKS;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Sets the track_stock flag of an article by its SKU. * Sets the track_stock flag of an article by its SKU.
@@ -96,6 +99,8 @@ export default function setTrackStockBySku(provider) {
throw new Error('Article not found'); throw new Error('Article not found');
} }
await runner.query(enqueueSyncSql, { articleId });
// Write history if track_stock changed // Write history if track_stock changed
if (oldTrack !== newTrack) { if (oldTrack !== newTrack) {
if (!newTrack) { if (!newTrack) {

View File

@@ -1,5 +1,11 @@
import { UPDATE_WEIGHT as MSSQL_UPDATE_WEIGHT } from '../../providers/mssql/queries/article.js'; import {
import { UPDATE_WEIGHT as SQLITE_UPDATE_WEIGHT } from '../../providers/sqlite3/queries/article.js'; UPDATE_WEIGHT as MSSQL_UPDATE_WEIGHT,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
UPDATE_WEIGHT as SQLITE_UPDATE_WEIGHT,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import queryBySku from './queryBySku.js'; import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js'; import resolveArticleBySku from './resolveArticleBySku.js';
import { recordWeightChangeHistory } from '../articlehistory/record.js'; import { recordWeightChangeHistory } from '../articlehistory/record.js';
@@ -8,7 +14,9 @@ import makeRunner from '../runner.js';
export default function setWeightBySku(provider) { export default function setWeightBySku(provider) {
const resolve = resolveArticleBySku(provider); const resolve = resolveArticleBySku(provider);
const load = queryBySku(provider); const load = queryBySku(provider);
const sql = provider?.dialect === 'sqlite3' ? SQLITE_UPDATE_WEIGHT : MSSQL_UPDATE_WEIGHT; const isSqlite = provider?.dialect === 'sqlite3';
const sql = isSqlite ? SQLITE_UPDATE_WEIGHT : MSSQL_UPDATE_WEIGHT;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Sets the weight of an article by its SKU. * Sets the weight of an article by its SKU.
@@ -48,6 +56,8 @@ export default function setWeightBySku(provider) {
throw new Error('Article not found'); throw new Error('Article not found');
} }
await runner.query(enqueueSyncSql, { articleId });
// Write history if weight changed // Write history if weight changed
await recordWeightChangeHistory(runner, { await recordWeightChangeHistory(runner, {
articleId, articleId,

View File

@@ -171,6 +171,8 @@ export default function toggleParent(provider) {
}); });
} }
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId });
return load(googleAuthId, sku, runner); return load(googleAuthId, sku, runner);
}; };

View File

@@ -234,9 +234,11 @@ export default function updateBySku(provider) {
// Touch article updated_at so all consumers pick up changes // Touch article updated_at so all consumers pick up changes
await runner.query(articleQueries.TOUCH_UPDATED_AT, { articleId }); await runner.query(articleQueries.TOUCH_UPDATED_AT, { articleId });
await runner.query(articleQueries.ENQUEUE_SYNC_BY_ARTICLE, { articleId });
// Touch any parent articles that use this article as an option so their deposit name/details update on POS // 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 }); await runner.query(articleQueries.TOUCH_OPTION_PARENT_ARTICLES_UPDATED_AT, { articleId });
await runner.query(articleQueries.ENQUEUE_OPTION_PARENT_ARTICLES_SYNC, { articleId });
const article = await load(articleId, languageId, runner); const article = await load(articleId, languageId, runner);
if (!article) { if (!article) {

View File

@@ -8,6 +8,14 @@ import {
INSERT_ROW as SQLITE_INSERT, INSERT_ROW as SQLITE_INSERT,
GET_VALUE_DETAIL as SQLITE_GET_DETAIL, GET_VALUE_DETAIL as SQLITE_GET_DETAIL,
} from '../../providers/sqlite3/queries/articleattributevalue.js'; } from '../../providers/sqlite3/queries/articleattributevalue.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import { recordAttributeChangeHistory } from '../articlehistory/record.js'; import { recordAttributeChangeHistory } from '../articlehistory/record.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -18,6 +26,8 @@ export default function setByArticleSku(provider) {
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
const getDetailSql = isSqlite ? SQLITE_GET_DETAIL : MSSQL_GET_DETAIL; const getDetailSql = isSqlite ? SQLITE_GET_DETAIL : MSSQL_GET_DETAIL;
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Replaces all attribute values for an article by its SKU. * Replaces all attribute values for an article by its SKU.
@@ -187,6 +197,13 @@ export default function setByArticleSku(provider) {
newAttrs: validated, newAttrs: validated,
}); });
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
if (article?.parentarticle_id != null) {
await runner.query(touchArticleSql, { articleId: article.parentarticle_id });
await runner.query(enqueueSyncSql, { articleId: article.parentarticle_id });
}
return inserted; return inserted;
}; };

View File

@@ -1,5 +1,13 @@
import * as mssqlQueries from '../../providers/mssql/queries/articlelist.js'; import * as mssqlQueries from '../../providers/mssql/queries/articlelist.js';
import * as sqliteQueries from '../../providers/sqlite3/queries/articlelist.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlelist.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import getItemsByArticleSku from './getItemsByArticleSku.js'; import getItemsByArticleSku from './getItemsByArticleSku.js';
import { import {
@@ -46,6 +54,8 @@ export default function setItemsByArticleSku(provider) {
const getItems = getItemsByArticleSku(provider); const getItems = getItemsByArticleSku(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;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Sets or clears the Stückliste (bundle items) for an article. * Sets or clears the Stückliste (bundle items) for an article.
@@ -108,6 +118,8 @@ export default function setItemsByArticleSku(provider) {
type: 'articlelist_cleared', type: 'articlelist_cleared',
}); });
} }
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
} }
return []; return [];
} }
@@ -229,6 +241,9 @@ export default function setItemsByArticleSku(provider) {
items: result, items: result,
}); });
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
return result; return result;
}; };

View File

@@ -4,6 +4,14 @@ import {
import { import {
DELETE_BY_ARTICLE_AND_PROPERTY_ID as SQLITE_DELETE, DELETE_BY_ARTICLE_AND_PROPERTY_ID as SQLITE_DELETE,
} from '../../providers/sqlite3/queries/articleproperty.js'; } from '../../providers/sqlite3/queries/articleproperty.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import { recordPropertyDeleteHistory } from '../articlehistory/record.js'; import { recordPropertyDeleteHistory } from '../articlehistory/record.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -12,6 +20,8 @@ export default function deleteArticleProperty(provider) {
const resolve = resolveArticleBySku(provider); const resolve = resolveArticleBySku(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Deletes a property from an article by SKU and propertyId. * Deletes a property from an article by SKU and propertyId.
@@ -45,6 +55,8 @@ export default function deleteArticleProperty(provider) {
name: prop.name, name: prop.name,
value: prop.value, value: prop.value,
}); });
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
} }
}; };
} }

View File

@@ -12,6 +12,14 @@ import {
import { import {
GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP, GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP,
} from '../../providers/sqlite3/queries/propertyname.js'; } from '../../providers/sqlite3/queries/propertyname.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import { recordPropertyChangeHistory } from '../articlehistory/record.js'; import { recordPropertyChangeHistory } from '../articlehistory/record.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -22,6 +30,8 @@ export default function setByArticleSku(provider) {
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
const getPropSql = isSqlite ? SQLITE_GET_PROP : MSSQL_GET_PROP; const getPropSql = isSqlite ? SQLITE_GET_PROP : MSSQL_GET_PROP;
const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Replaces all properties for an article by its SKU. * Replaces all properties for an article by its SKU.
@@ -124,6 +134,9 @@ export default function setByArticleSku(provider) {
newProps: validated, newProps: validated,
}); });
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
return inserted; return inserted;
}; };

View File

@@ -1,5 +1,13 @@
import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js'; import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js';
import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import taxEnsure from '../tax/ensure.js'; import taxEnsure from '../tax/ensure.js';
import { import {
@@ -17,6 +25,8 @@ export default function create(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;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Assigns a supplier to an article, optionally creating a purchase price. * Assigns a supplier to an article, optionally creating a purchase price.
@@ -161,6 +171,9 @@ export default function create(provider) {
pu: actualPu, pu: actualPu,
}); });
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
return shapeArticleSupplier(supplierRow); return shapeArticleSupplier(supplierRow);
}; };

View File

@@ -1,5 +1,13 @@
import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js'; import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js';
import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import { recordSupplierDeleteHistory } from '../articlehistory/record.js'; import { recordSupplierDeleteHistory } from '../articlehistory/record.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -8,6 +16,8 @@ export default function del(provider) {
const resolve = resolveArticleBySku(provider); const resolve = resolveArticleBySku(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;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Deletes a supplier assignment and cleans up its associated purchase price. * Deletes a supplier assignment and cleans up its associated purchase price.
@@ -59,6 +69,9 @@ export default function del(provider) {
supplierName, supplierName,
supplierSku: existing.sku, supplierSku: existing.sku,
}); });
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
}; };
if (tx) { if (tx) {

View File

@@ -1,5 +1,13 @@
import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js'; import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js';
import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -7,6 +15,8 @@ export default function order(provider) {
const resolve = resolveArticleBySku(provider); const resolve = resolveArticleBySku(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;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Reorders the supplier assignments of an article by its SKU. * Reorders the supplier assignments of an article by its SKU.
@@ -65,6 +75,9 @@ export default function order(provider) {
} }
} }
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
return { ids }; return { ids };
}; };

View File

@@ -1,5 +1,13 @@
import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js'; import * as mssqlQueries from '../../providers/mssql/queries/articlesupplier.js';
import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js'; import * as sqliteQueries from '../../providers/sqlite3/queries/articlesupplier.js';
import {
TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} from '../../providers/sqlite3/queries/article.js';
import resolveArticleBySku from '../article/resolveArticleBySku.js'; import resolveArticleBySku from '../article/resolveArticleBySku.js';
import taxEnsure from '../tax/ensure.js'; import taxEnsure from '../tax/ensure.js';
import { import {
@@ -16,6 +24,8 @@ export default function update(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;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Updates an existing supplier assignment and its associated purchase price. * Updates an existing supplier assignment and its associated purchase price.
@@ -218,6 +228,9 @@ export default function update(provider) {
supplierName, supplierName,
}); });
await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
const loadRes = await runner.query(queries.GET_BY_ID_AND_ARTICLE_ID, { const loadRes = await runner.query(queries.GET_BY_ID_AND_ARTICLE_ID, {
id, id,
articleId, articleId,

View File

@@ -1,10 +1,14 @@
import { import {
TOUCH_UPDATED_AT as MSSQL_TOUCH, TOUCH_UPDATED_AT as MSSQL_TOUCH,
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE, TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
ENQUEUE_STUECKLISTE_ARTICLES_SYNC as MSSQL_ENQUEUE_STUECKLISTE_SYNC,
} from '../../providers/mssql/queries/article.js'; } from '../../providers/mssql/queries/article.js';
import { import {
TOUCH_UPDATED_AT as SQLITE_TOUCH, TOUCH_UPDATED_AT as SQLITE_TOUCH,
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE, TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
ENQUEUE_STUECKLISTE_ARTICLES_SYNC as SQLITE_ENQUEUE_STUECKLISTE_SYNC,
} from '../../providers/sqlite3/queries/article.js'; } from '../../providers/sqlite3/queries/article.js';
/** /**
@@ -154,8 +158,12 @@ export async function syncArticleWarehouseSummaryForLedger(runner, { companyId,
const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH; const touchSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE; const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
const enqueueStuecklisteSyncSql = isSqlite ? SQLITE_ENQUEUE_STUECKLISTE_SYNC : MSSQL_ENQUEUE_STUECKLISTE_SYNC;
await runner.query(touchSql, { articleId }); await runner.query(touchSql, { articleId });
await runner.query(touchStuecklisteSql, { articleId }); await runner.query(touchStuecklisteSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
await runner.query(enqueueStuecklisteSyncSql, { articleId });
} }
} }

View File

@@ -62,6 +62,51 @@ export default function update(provider) {
throw err; throw err;
} }
if (value !== existing.rows[0].value || sort !== existing.rows[0].sort) {
const touchSql = isSqlite
? `UPDATE article SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
WHERE company_id = @companyId
AND (
id IN (SELECT article_id FROM articleattributevalue WHERE attributevalue_id = @valId)
OR id IN (
SELECT a.parentarticle_id
FROM article a
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
WHERE aav.attributevalue_id = @valId AND a.parentarticle_id IS NOT NULL
)
)`
: `UPDATE article SET updated_at = SYSUTCDATETIME()
WHERE company_id = @companyId
AND (
id IN (SELECT article_id FROM articleattributevalue WHERE attributevalue_id = @valId)
OR id IN (
SELECT a.parentarticle_id
FROM article a
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
WHERE aav.attributevalue_id = @valId AND a.parentarticle_id IS NOT NULL
)
)`;
await runner.query(touchSql, { valId, companyId });
const enqueueSql = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', a.id
FROM article a
INNER JOIN posterminal pt ON pt.company_id = a.company_id
WHERE a.company_id = @companyId
AND (
a.id IN (SELECT article_id FROM articleattributevalue WHERE attributevalue_id = @valId)
OR a.id IN (
SELECT child.parentarticle_id
FROM article child
INNER JOIN articleattributevalue aav ON aav.article_id = child.id
WHERE aav.attributevalue_id = @valId AND child.parentarticle_id IS NOT NULL
)
)
`;
await runner.query(enqueueSql, { valId, companyId });
}
return { return {
id: valId, id: valId,
attributeId: existing.rows[0].attributeId, attributeId: existing.rows[0].attributeId,

View File

@@ -78,6 +78,15 @@ export default function update(provider) {
: 'UPDATE article SET updated_at = SYSUTCDATETIME() WHERE brand_id = @brandId AND company_id = @companyId'; : 'UPDATE article SET updated_at = SYSUTCDATETIME() WHERE brand_id = @brandId AND company_id = @companyId';
const touched = await runner.query(touchSql, { brandId: numericId, companyId }); const touched = await runner.query(touchSql, { brandId: numericId, companyId });
touchedArticleCount = touched.rowsAffected ?? 0; touchedArticleCount = touched.rowsAffected ?? 0;
const enqueueSql = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', a.id
FROM article a
INNER JOIN posterminal pt ON pt.company_id = a.company_id
WHERE a.brand_id = @brandId AND a.company_id = @companyId
`;
await runner.query(enqueueSql, { brandId: numericId, companyId });
} }
return { return {

View File

@@ -11,10 +11,12 @@ import { CREATE as SQLITE_CREATE_PIC } from '../../providers/sqlite3/queries/pic
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 resolveCategoryAccess from './resolveCategoryAccess.js'; import resolveCategoryAccess from './resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function addImage(provider) { export default function addImage(provider) {
const access = resolveCategoryAccess(provider); const access = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT; const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT;
@@ -42,7 +44,7 @@ export default function addImage(provider) {
const fullHash = newFullHash(processed.full.buffer); const fullHash = newFullHash(processed.full.buffer);
const runAdd = async (runner) => { const runAdd = async (runner) => {
await access(googleAuthId, categoryId, runner); const { companyId } = await access(googleAuthId, categoryId, runner);
const sortResult = await runner.query(getSortSql, { categoryId }); const sortResult = await runner.query(getSortSql, { categoryId });
const sort = sortResult.rows[0].sort; const sort = sortResult.rows[0].sort;
@@ -95,6 +97,7 @@ export default function addImage(provider) {
} }
await runner.query(touchSql, { categoryId }); await runner.query(touchSql, { categoryId });
await recordSync({ companyId, entityType: 'category', entityId: categoryId }, runner);
return { return {
id: categoryId, id: categoryId,

View File

@@ -9,11 +9,13 @@ import {
import { CREATE as MSSQL_CREATE_CATEGORYTEXT } from '../../providers/mssql/queries/categorytext.js'; import { CREATE as MSSQL_CREATE_CATEGORYTEXT } from '../../providers/mssql/queries/categorytext.js';
import { CREATE as SQLITE_CREATE_CATEGORYTEXT } from '../../providers/sqlite3/queries/categorytext.js'; import { CREATE as SQLITE_CREATE_CATEGORYTEXT } from '../../providers/sqlite3/queries/categorytext.js';
import resolveCategoryAccess from './resolveCategoryAccess.js'; import resolveCategoryAccess from './resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import slugify from '../slugify.js'; import slugify from '../slugify.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function createUnderParent(provider) { export default function createUnderParent(provider) {
const access = resolveCategoryAccess(provider); const access = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSortSql = isSqlite const getSortSql = isSqlite
@@ -40,7 +42,7 @@ export default function createUnderParent(provider) {
*/ */
return async ({ googleAuthId, parentId, name }, tx) => { return async ({ googleAuthId, parentId, name }, tx) => {
const runCreate = async (runner) => { const runCreate = async (runner) => {
const { languageId } = await access(googleAuthId, parentId, runner); const { languageId, companyId } = await access(googleAuthId, parentId, runner);
const sortResult = await runner.query(getSortSql, { parentId }); const sortResult = await runner.query(getSortSql, { parentId });
const sort = sortResult.rows[0].sort; const sort = sortResult.rows[0].sort;
@@ -60,6 +62,8 @@ export default function createUnderParent(provider) {
slug, slug,
}); });
await recordSync({ companyId, entityType: 'category', entityId: id }, runner);
return { return {
id, id,
parentId, parentId,

View File

@@ -1,10 +1,12 @@
import { DELETE_BY_CATEGORY as MSSQL_DELETE } from '../../providers/mssql/queries/categorypic.js'; import { DELETE_BY_CATEGORY as MSSQL_DELETE } from '../../providers/mssql/queries/categorypic.js';
import { DELETE_BY_CATEGORY as SQLITE_DELETE } from '../../providers/sqlite3/queries/categorypic.js'; import { DELETE_BY_CATEGORY as SQLITE_DELETE } from '../../providers/sqlite3/queries/categorypic.js';
import resolveCategoryAccess from './resolveCategoryAccess.js'; import resolveCategoryAccess from './resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function deleteImage(provider) { export default function deleteImage(provider) {
const access = resolveCategoryAccess(provider); const access = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE; const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const touchSql = isSqlite const touchSql = isSqlite
@@ -26,7 +28,7 @@ export default function deleteImage(provider) {
*/ */
return async (googleAuthId, categoryId, tx) => { return async (googleAuthId, categoryId, tx) => {
const runner = makeRunner(provider, tx); const runner = makeRunner(provider, tx);
await access(googleAuthId, categoryId, runner); const { companyId } = await access(googleAuthId, categoryId, runner);
const result = await runner.query(sql, { categoryId }); const result = await runner.query(sql, { categoryId });
@@ -35,6 +37,7 @@ export default function deleteImage(provider) {
} }
await runner.query(touchSql, { categoryId }); await runner.query(touchSql, { categoryId });
await recordSync({ companyId, entityType: 'category', entityId: categoryId }, runner);
return { return {
id: categoryId, id: categoryId,

View File

@@ -11,6 +11,7 @@ import {
import { CREATE as MSSQL_CREATE_CATEGORYTEXT } from '../../providers/mssql/queries/categorytext.js'; import { CREATE as MSSQL_CREATE_CATEGORYTEXT } from '../../providers/mssql/queries/categorytext.js';
import { CREATE as SQLITE_CREATE_CATEGORYTEXT } from '../../providers/sqlite3/queries/categorytext.js'; import { CREATE as SQLITE_CREATE_CATEGORYTEXT } from '../../providers/sqlite3/queries/categorytext.js';
import resolveCategoryAccess from './resolveCategoryAccess.js'; import resolveCategoryAccess from './resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import slugify from '../slugify.js'; import slugify from '../slugify.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -25,6 +26,7 @@ export function splitPath(path) {
export default function ensureByPath(provider) { export default function ensureByPath(provider) {
const access = resolveCategoryAccess(provider); const access = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSortSql = isSqlite const getSortSql = isSqlite
@@ -61,7 +63,7 @@ export default function ensureByPath(provider) {
let currentId = parentId; let currentId = parentId;
const createCategory = async (runner, parentId, name, languageId) => { const createCategory = async (runner, parentId, name, languageId, companyId) => {
const sortResult = await runner.query(getSortSql, { parentId }); const sortResult = await runner.query(getSortSql, { parentId });
const sort = sortResult.rows[0].sort; const sort = sortResult.rows[0].sort;
const slug = slugify(name); const slug = slugify(name);
@@ -79,11 +81,13 @@ export default function ensureByPath(provider) {
slug, slug,
}); });
await recordSync({ companyId, entityType: 'category', entityId: id }, runner);
return id; return id;
}; };
for (const name of names) { for (const name of names) {
const { languageId } = await access(googleAuthId, currentId, tx); const { languageId, companyId } = await access(googleAuthId, currentId, tx);
const existing = await makeRunner(provider, tx).query(getByNameSql, { const existing = await makeRunner(provider, tx).query(getByNameSql, {
parentId: currentId, parentId: currentId,
@@ -98,17 +102,17 @@ export default function ensureByPath(provider) {
if (tx) { if (tx) {
// Run within the caller's transaction (e.g. article import row). // Run within the caller's transaction (e.g. article import row).
currentId = await createCategory(makeRunner(provider, tx), currentId, name, languageId); currentId = await createCategory(makeRunner(provider, tx), currentId, name, languageId, companyId);
continue; continue;
} }
// Standalone use: own transaction per created category. // Standalone use: own transaction per created category.
if (typeof provider?.transaction === 'function') { if (typeof provider?.transaction === 'function') {
currentId = await provider.transaction((runner) => currentId = await provider.transaction((runner) =>
createCategory(runner, currentId, name, languageId), createCategory(runner, currentId, name, languageId, companyId),
); );
} else { } else {
currentId = await createCategory(makeRunner(provider), currentId, name, languageId); currentId = await createCategory(makeRunner(provider), currentId, name, languageId, companyId);
} }
} }

View File

@@ -4,6 +4,8 @@ import {
MOVE as MSSQL_MOVE, MOVE as MSSQL_MOVE,
TOUCH_SUBTREE_CATEGORIES as MSSQL_TOUCH_SUBTREE_CATEGORIES, TOUCH_SUBTREE_CATEGORIES as MSSQL_TOUCH_SUBTREE_CATEGORIES,
TOUCH_SUBTREE_ARTICLES as MSSQL_TOUCH_SUBTREE_ARTICLES, TOUCH_SUBTREE_ARTICLES as MSSQL_TOUCH_SUBTREE_ARTICLES,
ENQUEUE_SUBTREE_CATEGORIES_SYNC as MSSQL_ENQUEUE_SUBTREE_CATEGORIES,
ENQUEUE_SUBTREE_ARTICLES_SYNC as MSSQL_ENQUEUE_SUBTREE_ARTICLES,
} from '../../providers/mssql/queries/category.js'; } from '../../providers/mssql/queries/category.js';
import { import {
GET_NEXT_SIBLING_SORT as SQLITE_GET_NEXT_SIBLING_SORT, GET_NEXT_SIBLING_SORT as SQLITE_GET_NEXT_SIBLING_SORT,
@@ -11,6 +13,8 @@ import {
MOVE as SQLITE_MOVE, MOVE as SQLITE_MOVE,
TOUCH_SUBTREE_CATEGORIES as SQLITE_TOUCH_SUBTREE_CATEGORIES, TOUCH_SUBTREE_CATEGORIES as SQLITE_TOUCH_SUBTREE_CATEGORIES,
TOUCH_SUBTREE_ARTICLES as SQLITE_TOUCH_SUBTREE_ARTICLES, TOUCH_SUBTREE_ARTICLES as SQLITE_TOUCH_SUBTREE_ARTICLES,
ENQUEUE_SUBTREE_CATEGORIES_SYNC as SQLITE_ENQUEUE_SUBTREE_CATEGORIES,
ENQUEUE_SUBTREE_ARTICLES_SYNC as SQLITE_ENQUEUE_SUBTREE_ARTICLES,
} from '../../providers/sqlite3/queries/category.js'; } from '../../providers/sqlite3/queries/category.js';
import resolveCategoryAccess from './resolveCategoryAccess.js'; import resolveCategoryAccess from './resolveCategoryAccess.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -30,6 +34,12 @@ export default function move(provider) {
const touchArticlesSql = isSqlite const touchArticlesSql = isSqlite
? SQLITE_TOUCH_SUBTREE_ARTICLES ? SQLITE_TOUCH_SUBTREE_ARTICLES
: MSSQL_TOUCH_SUBTREE_ARTICLES; : MSSQL_TOUCH_SUBTREE_ARTICLES;
const enqueueCategoriesSql = isSqlite
? SQLITE_ENQUEUE_SUBTREE_CATEGORIES
: MSSQL_ENQUEUE_SUBTREE_CATEGORIES;
const enqueueArticlesSql = isSqlite
? SQLITE_ENQUEUE_SUBTREE_ARTICLES
: MSSQL_ENQUEUE_SUBTREE_ARTICLES;
/** /**
* Moves a category to a new parent, preventing cycles and cross-company moves. * Moves a category to a new parent, preventing cycles and cross-company moves.
@@ -82,6 +92,8 @@ export default function move(provider) {
await runner.query(touchCategoriesSql, { id }); await runner.query(touchCategoriesSql, { id });
await runner.query(touchArticlesSql, { id }); await runner.query(touchArticlesSql, { id });
await runner.query(enqueueCategoriesSql, { id, companyId: source.companyId });
await runner.query(enqueueArticlesSql, { id, companyId: source.companyId });
return { id, parentId, sort }; return { id, parentId, sort };
}; };

View File

@@ -7,10 +7,12 @@ import {
UPDATE_SORT as SQLITE_UPDATE_SORT, UPDATE_SORT as SQLITE_UPDATE_SORT,
} from '../../providers/sqlite3/queries/category.js'; } from '../../providers/sqlite3/queries/category.js';
import resolveCategoryAccess from './resolveCategoryAccess.js'; import resolveCategoryAccess from './resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function order(provider) { export default function order(provider) {
const access = resolveCategoryAccess(provider); const access = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const listChildIdsSql = isSqlite const listChildIdsSql = isSqlite
? SQLITE_LIST_CHILD_IDS ? SQLITE_LIST_CHILD_IDS
@@ -39,7 +41,7 @@ export default function order(provider) {
const runOrder = async (runner) => { const runOrder = async (runner) => {
// Auth + ensures parentId is a real category of the user's company. // Auth + ensures parentId is a real category of the user's company.
await access(googleAuthId, parentId, runner); const { companyId } = await access(googleAuthId, parentId, runner);
const children = await runner.query(listChildIdsSql, { parentId }); const children = await runner.query(listChildIdsSql, { parentId });
@@ -63,6 +65,7 @@ export default function order(provider) {
parentId, parentId,
sort, sort,
}); });
await recordSync({ companyId, entityType: 'category', entityId: categoryIds[sort] }, runner);
} }
return { categoryIds }; return { categoryIds };

View File

@@ -1,11 +1,13 @@
import { UPDATE_BY_CATEGORY_AND_LANGUAGE as MSSQL_UPDATE } from '../../providers/mssql/queries/categorytext.js'; import { UPDATE_BY_CATEGORY_AND_LANGUAGE as MSSQL_UPDATE } from '../../providers/mssql/queries/categorytext.js';
import { UPDATE_BY_CATEGORY_AND_LANGUAGE as SQLITE_UPDATE } from '../../providers/sqlite3/queries/categorytext.js'; import { UPDATE_BY_CATEGORY_AND_LANGUAGE as SQLITE_UPDATE } from '../../providers/sqlite3/queries/categorytext.js';
import resolveCategoryAccess from './resolveCategoryAccess.js'; import resolveCategoryAccess from './resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import slugify from '../slugify.js'; import slugify from '../slugify.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function rename(provider) { export default function rename(provider) {
const access = resolveCategoryAccess(provider); const access = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const sql = provider?.dialect === 'sqlite3' ? SQLITE_UPDATE : MSSQL_UPDATE; const sql = provider?.dialect === 'sqlite3' ? SQLITE_UPDATE : MSSQL_UPDATE;
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const touchSql = isSqlite const touchSql = isSqlite
@@ -33,7 +35,7 @@ export default function rename(provider) {
} }
const cleaned = name.trim(); const cleaned = name.trim();
const { languageId } = await access(googleAuthId, id, tx); const { languageId, companyId } = await access(googleAuthId, id, tx);
const slug = slugify(cleaned); const slug = slugify(cleaned);
@@ -50,6 +52,7 @@ export default function rename(provider) {
} }
await runner.query(touchSql, { categoryId: id }); await runner.query(touchSql, { categoryId: id });
await recordSync({ companyId, entityType: 'category', entityId: id }, runner);
return { id, name: cleaned, slug }; return { id, name: cleaned, slug };
}; };

View File

@@ -9,10 +9,12 @@ import {
GET_VALUE_DETAIL as SQLITE_GET_DETAIL, GET_VALUE_DETAIL as SQLITE_GET_DETAIL,
} from '../../providers/sqlite3/queries/categoryattributevalue.js'; } from '../../providers/sqlite3/queries/categoryattributevalue.js';
import resolveCategoryAccess from '../category/resolveCategoryAccess.js'; import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function setByCategoryId(provider) { export default function setByCategoryId(provider) {
const resolve = resolveCategoryAccess(provider); const resolve = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
@@ -140,6 +142,7 @@ export default function setByCategoryId(provider) {
? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId" ? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId"
: 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId'; : 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId';
await runner.query(touchSql, { categoryId }); await runner.query(touchSql, { categoryId });
await recordSync({ companyId, entityType: 'category', entityId: categoryId }, runner);
return inserted; return inserted;
}; };

View File

@@ -5,10 +5,12 @@ import {
DELETE_BY_CATEGORY_AND_PROPERTY_ID as SQLITE_DELETE, DELETE_BY_CATEGORY_AND_PROPERTY_ID as SQLITE_DELETE,
} from '../../providers/sqlite3/queries/categoryproperty.js'; } from '../../providers/sqlite3/queries/categoryproperty.js';
import resolveCategoryAccess from '../category/resolveCategoryAccess.js'; import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function deleteCategoryProperty(provider) { export default function deleteCategoryProperty(provider) {
const resolve = resolveCategoryAccess(provider); const resolve = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
@@ -23,7 +25,7 @@ export default function deleteCategoryProperty(provider) {
*/ */
return async (googleAuthId, categoryId, propertyId, tx) => { return async (googleAuthId, categoryId, propertyId, tx) => {
const runner = makeRunner(provider, tx); const runner = makeRunner(provider, tx);
await resolve(googleAuthId, categoryId, runner); const { companyId } = await resolve(googleAuthId, categoryId, runner);
await runner.query(deleteSql, { categoryId, propertyId }); await runner.query(deleteSql, { categoryId, propertyId });
@@ -32,5 +34,6 @@ export default function deleteCategoryProperty(provider) {
? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId" ? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId"
: 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId'; : 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId';
await runner.query(touchSql, { categoryId }); await runner.query(touchSql, { categoryId });
await recordSync({ companyId, entityType: 'category', entityId: categoryId }, runner);
}; };
} }

View File

@@ -13,10 +13,12 @@ import {
GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP, GET_BY_COMPANY_AND_ID as SQLITE_GET_PROP,
} from '../../providers/sqlite3/queries/propertyname.js'; } from '../../providers/sqlite3/queries/propertyname.js';
import resolveCategoryAccess from '../category/resolveCategoryAccess.js'; import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function setByCategoryId(provider) { export default function setByCategoryId(provider) {
const resolve = resolveCategoryAccess(provider); const resolve = resolveCategoryAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE; const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT; const insertSql = isSqlite ? SQLITE_INSERT : MSSQL_INSERT;
@@ -102,6 +104,7 @@ export default function setByCategoryId(provider) {
? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId" ? "UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = @categoryId"
: 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId'; : 'UPDATE category SET updated_at = SYSUTCDATETIME() WHERE id = @categoryId';
await runner.query(touchSql, { categoryId }); await runner.query(touchSql, { categoryId });
await recordSync({ companyId, entityType: 'category', entityId: categoryId }, runner);
return inserted; return inserted;
}; };

View File

@@ -8,6 +8,7 @@ import {
} from '../../providers/sqlite3/queries/customergroup.js'; } from '../../providers/sqlite3/queries/customergroup.js';
import { POS_GROUP_NAME, SUPPLIER_GROUP_NAME } from '../../providers/mssql/queries/entity.js'; import { POS_GROUP_NAME, SUPPLIER_GROUP_NAME } from '../../providers/mssql/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
const RESERVED_NAMES = new Set([POS_GROUP_NAME, SUPPLIER_GROUP_NAME]); const RESERVED_NAMES = new Set([POS_GROUP_NAME, SUPPLIER_GROUP_NAME]);
@@ -18,6 +19,7 @@ function shape(row) {
export default function create(provider) { export default function create(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT; const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT;
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE; const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
@@ -58,6 +60,7 @@ export default function create(provider) {
}); });
const row = result.rows[0] ?? { id: result.insertId, name: cleaned, sort }; const row = result.rows[0] ?? { id: result.insertId, name: cleaned, sort };
await recordSync({ companyId, entityType: 'customergroup', entityId: row.id }, runner);
return shape(row); return shape(row);
}; };
} }

View File

@@ -7,10 +7,12 @@ import {
UPDATE_SORT as SQLITE_UPDATE_SORT, UPDATE_SORT as SQLITE_UPDATE_SORT,
} from '../../providers/sqlite3/queries/customergroup.js'; } from '../../providers/sqlite3/queries/customergroup.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function order(provider) { export default function order(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const listIdsSql = isSqlite const listIdsSql = isSqlite
? SQLITE_LIST_IDS_BY_COMPANY ? SQLITE_LIST_IDS_BY_COMPANY
@@ -65,6 +67,7 @@ export default function order(provider) {
companyId, companyId,
sort, sort,
}); });
await recordSync({ companyId, entityType: 'customergroup', entityId: customergroupIds[sort] }, runner);
} }
return { customergroupIds }; return { customergroupIds };

View File

@@ -8,6 +8,7 @@ import {
} from '../../providers/sqlite3/queries/customergroup.js'; } from '../../providers/sqlite3/queries/customergroup.js';
import { POS_GROUP_NAME, SUPPLIER_GROUP_NAME } from '../../providers/mssql/queries/entity.js'; import { POS_GROUP_NAME, SUPPLIER_GROUP_NAME } from '../../providers/mssql/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
const LOCKED_NAMES = new Set([POS_GROUP_NAME, SUPPLIER_GROUP_NAME]); const LOCKED_NAMES = new Set([POS_GROUP_NAME, SUPPLIER_GROUP_NAME]);
@@ -18,6 +19,7 @@ function shape(row) {
export default function update(provider) { export default function update(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSql = isSqlite const getSql = isSqlite
? SQLITE_GET_BY_COMPANY_AND_ID ? SQLITE_GET_BY_COMPANY_AND_ID
@@ -70,6 +72,7 @@ export default function update(provider) {
}); });
const row = result.rows[0] ?? { id, name: cleaned, sort: current.sort }; const row = result.rows[0] ?? { id, name: cleaned, sort: current.sort };
await recordSync({ companyId, entityType: 'customergroup', entityId: id }, runner);
return shape(row); return shape(row);
}; };
} }

View File

@@ -5,6 +5,7 @@ import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries
import { isUniqueConstraintError } from '../../providers/provider.js'; import { isUniqueConstraintError } from '../../providers/provider.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js'; import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import nextNumber from '../numbers/nextNumber.js'; import nextNumber from '../numbers/nextNumber.js';
import shape from './shape.js'; import shape from './shape.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -12,6 +13,7 @@ import makeRunner from '../runner.js';
export default function create(provider) { export default function create(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const groupAccess = resolveCustomerGroupAccess(provider); const groupAccess = resolveCustomerGroupAccess(provider);
const recordSync = recordSyncQueue(provider);
const allocateNumber = nextNumber(provider); const allocateNumber = nextNumber(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
@@ -82,6 +84,8 @@ export default function create(provider) {
text: 'Erstellt', text: 'Erstellt',
}); });
await recordSync({ companyId, entityType: 'customer', entityId: row.id }, runner);
return shape(row); return shape(row);
} catch (err) { } catch (err) {
if (isUniqueConstraintError(err)) { if (isUniqueConstraintError(err)) {

View File

@@ -10,11 +10,13 @@ import { INSERT as MSSQL_INSERT_HISTORY } from '../../providers/mssql/queries/en
import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/entityhistory.js'; import { INSERT as SQLITE_INSERT_HISTORY } from '../../providers/sqlite3/queries/entityhistory.js';
import { isUniqueConstraintError } from '../../providers/provider.js'; import { isUniqueConstraintError } from '../../providers/provider.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import shape from './shape.js'; import shape from './shape.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function update(provider) { export default function update(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSql = isSqlite const getSql = isSqlite
? SQLITE_GET_BY_COMPANY_AND_ID ? SQLITE_GET_BY_COMPANY_AND_ID
@@ -134,6 +136,8 @@ export default function update(provider) {
text: historyLines.join('\n'), text: historyLines.join('\n'),
}); });
await recordSync({ companyId, entityType: 'customer', entityId: id }, runner);
return shape(row); return shape(row);
} catch (err) { } catch (err) {
if (isUniqueConstraintError(err)) { if (isUniqueConstraintError(err)) {

View File

@@ -6,8 +6,11 @@ import {
CREATE as SQLITE_CREATE, CREATE as SQLITE_CREATE,
GET_NEXT_SORT as SQLITE_GET_NEXT_SORT, GET_NEXT_SORT as SQLITE_GET_NEXT_SORT,
} from '../../providers/sqlite3/queries/entityaddress.js'; } from '../../providers/sqlite3/queries/entityaddress.js';
import { TOUCH_UPDATED_AT as MSSQL_TOUCH_ENTITY } from '../../providers/mssql/queries/entity.js';
import { TOUCH_UPDATED_AT as SQLITE_TOUCH_ENTITY } from '../../providers/sqlite3/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js'; import resolveEntityAccess from '../entity/resolveEntityAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
function shape(row) { function shape(row) {
@@ -35,9 +38,11 @@ function optionalString(value) {
export default function create(provider) { export default function create(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const entityAccess = resolveEntityAccess(provider); const entityAccess = resolveEntityAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT; const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT;
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE; const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
const touchEntitySql = isSqlite ? SQLITE_TOUCH_ENTITY : MSSQL_TOUCH_ENTITY;
/** /**
* Creates a new address for an entity. * Creates a new address for an entity.
@@ -109,6 +114,9 @@ export default function create(provider) {
sort, sort,
}; };
await runner.query(touchEntitySql, { id: entityId });
await recordSync({ companyId, entityType: 'customer', entityId }, runner);
return shape(row); return shape(row);
}; };
} }

View File

@@ -1,13 +1,19 @@
import { DELETE_BY_ENTITY_AND_ID as MSSQL_DELETE } from '../../providers/mssql/queries/entityaddress.js'; import { DELETE_BY_ENTITY_AND_ID as MSSQL_DELETE } from '../../providers/mssql/queries/entityaddress.js';
import { DELETE_BY_ENTITY_AND_ID as SQLITE_DELETE } from '../../providers/sqlite3/queries/entityaddress.js'; import { DELETE_BY_ENTITY_AND_ID as SQLITE_DELETE } from '../../providers/sqlite3/queries/entityaddress.js';
import { TOUCH_UPDATED_AT as MSSQL_TOUCH_ENTITY } from '../../providers/mssql/queries/entity.js';
import { TOUCH_UPDATED_AT as SQLITE_TOUCH_ENTITY } from '../../providers/sqlite3/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js'; import resolveEntityAccess from '../entity/resolveEntityAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function del(provider) { export default function del(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const entityAccess = resolveEntityAccess(provider); const entityAccess = resolveEntityAccess(provider);
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE; const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const sql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
const touchEntitySql = isSqlite ? SQLITE_TOUCH_ENTITY : MSSQL_TOUCH_ENTITY;
/** /**
* Deletes an address of an entity. * Deletes an address of an entity.
@@ -39,5 +45,8 @@ export default function del(provider) {
if (!result.rowsAffected) { if (!result.rowsAffected) {
throw new Error('Address not found'); throw new Error('Address not found');
} }
await runner.query(touchEntitySql, { id: entityId });
await recordSync({ companyId, entityType: 'customer', entityId }, runner);
}; };
} }

View File

@@ -6,8 +6,11 @@ import {
GET_BY_ENTITY_AND_ID as SQLITE_GET_BY_ENTITY_AND_ID, GET_BY_ENTITY_AND_ID as SQLITE_GET_BY_ENTITY_AND_ID,
UPDATE as SQLITE_UPDATE, UPDATE as SQLITE_UPDATE,
} from '../../providers/sqlite3/queries/entityaddress.js'; } from '../../providers/sqlite3/queries/entityaddress.js';
import { TOUCH_UPDATED_AT as MSSQL_TOUCH_ENTITY } from '../../providers/mssql/queries/entity.js';
import { TOUCH_UPDATED_AT as SQLITE_TOUCH_ENTITY } from '../../providers/sqlite3/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js'; import resolveEntityAccess from '../entity/resolveEntityAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
function shape(row) { function shape(row) {
@@ -35,11 +38,13 @@ function optionalString(value) {
export default function update(provider) { export default function update(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const entityAccess = resolveEntityAccess(provider); const entityAccess = resolveEntityAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSql = isSqlite const getSql = isSqlite
? SQLITE_GET_BY_ENTITY_AND_ID ? SQLITE_GET_BY_ENTITY_AND_ID
: MSSQL_GET_BY_ENTITY_AND_ID; : MSSQL_GET_BY_ENTITY_AND_ID;
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE; const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
const touchEntitySql = isSqlite ? SQLITE_TOUCH_ENTITY : MSSQL_TOUCH_ENTITY;
/** /**
* Updates an address of an entity. * Updates an address of an entity.
@@ -152,6 +157,9 @@ export default function update(provider) {
sort, sort,
}; };
await runner.query(touchEntitySql, { id: entityId });
await recordSync({ companyId, entityType: 'customer', entityId }, runner);
return shape(row); return shape(row);
}; };
} }

View File

@@ -6,8 +6,11 @@ import {
CREATE as SQLITE_CREATE, CREATE as SQLITE_CREATE,
GET_NEXT_SORT as SQLITE_GET_NEXT_SORT, GET_NEXT_SORT as SQLITE_GET_NEXT_SORT,
} from '../../providers/sqlite3/queries/entitycontact.js'; } from '../../providers/sqlite3/queries/entitycontact.js';
import { TOUCH_UPDATED_AT as MSSQL_TOUCH_ENTITY } from '../../providers/mssql/queries/entity.js';
import { TOUCH_UPDATED_AT as SQLITE_TOUCH_ENTITY } from '../../providers/sqlite3/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js'; import resolveEntityAccess from '../entity/resolveEntityAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
function shape(row) { function shape(row) {
@@ -23,9 +26,11 @@ function shape(row) {
export default function create(provider) { export default function create(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const entityAccess = resolveEntityAccess(provider); const entityAccess = resolveEntityAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT; const getSortSql = isSqlite ? SQLITE_GET_NEXT_SORT : MSSQL_GET_NEXT_SORT;
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE; const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
const touchEntitySql = isSqlite ? SQLITE_TOUCH_ENTITY : MSSQL_TOUCH_ENTITY;
/** /**
* Creates a new contact for an entity. * Creates a new contact for an entity.
@@ -80,6 +85,9 @@ export default function create(provider) {
sort, sort,
}; };
await runner.query(touchEntitySql, { id: entityId });
await recordSync({ companyId, entityType: 'customer', entityId }, runner);
return shape(row); return shape(row);
}; };
} }

View File

@@ -1,13 +1,19 @@
import { DELETE_BY_ENTITY_AND_ID as MSSQL_DELETE } from '../../providers/mssql/queries/entitycontact.js'; import { DELETE_BY_ENTITY_AND_ID as MSSQL_DELETE } from '../../providers/mssql/queries/entitycontact.js';
import { DELETE_BY_ENTITY_AND_ID as SQLITE_DELETE } from '../../providers/sqlite3/queries/entitycontact.js'; import { DELETE_BY_ENTITY_AND_ID as SQLITE_DELETE } from '../../providers/sqlite3/queries/entitycontact.js';
import { TOUCH_UPDATED_AT as MSSQL_TOUCH_ENTITY } from '../../providers/mssql/queries/entity.js';
import { TOUCH_UPDATED_AT as SQLITE_TOUCH_ENTITY } from '../../providers/sqlite3/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js'; import resolveEntityAccess from '../entity/resolveEntityAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
export default function del(provider) { export default function del(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const entityAccess = resolveEntityAccess(provider); const entityAccess = resolveEntityAccess(provider);
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE; const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3';
const sql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
const touchEntitySql = isSqlite ? SQLITE_TOUCH_ENTITY : MSSQL_TOUCH_ENTITY;
/** /**
* Deletes a contact of an entity. * Deletes a contact of an entity.
@@ -39,5 +45,8 @@ export default function del(provider) {
if (!result.rowsAffected) { if (!result.rowsAffected) {
throw new Error('Contact not found'); throw new Error('Contact not found');
} }
await runner.query(touchEntitySql, { id: entityId });
await recordSync({ companyId, entityType: 'customer', entityId }, runner);
}; };
} }

View File

@@ -6,8 +6,11 @@ import {
GET_BY_ENTITY_AND_ID as SQLITE_GET_BY_ENTITY_AND_ID, GET_BY_ENTITY_AND_ID as SQLITE_GET_BY_ENTITY_AND_ID,
UPDATE as SQLITE_UPDATE, UPDATE as SQLITE_UPDATE,
} from '../../providers/sqlite3/queries/entitycontact.js'; } from '../../providers/sqlite3/queries/entitycontact.js';
import { TOUCH_UPDATED_AT as MSSQL_TOUCH_ENTITY } from '../../providers/mssql/queries/entity.js';
import { TOUCH_UPDATED_AT as SQLITE_TOUCH_ENTITY } from '../../providers/sqlite3/queries/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js'; import resolveEntityAccess from '../entity/resolveEntityAccess.js';
import recordSyncQueue from '../posterminal/recordSyncQueue.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
function shape(row) { function shape(row) {
@@ -23,11 +26,13 @@ function shape(row) {
export default function update(provider) { export default function update(provider) {
const access = resolveSelectedCompanyAccess(provider); const access = resolveSelectedCompanyAccess(provider);
const entityAccess = resolveEntityAccess(provider); const entityAccess = resolveEntityAccess(provider);
const recordSync = recordSyncQueue(provider);
const isSqlite = provider?.dialect === 'sqlite3'; const isSqlite = provider?.dialect === 'sqlite3';
const getSql = isSqlite const getSql = isSqlite
? SQLITE_GET_BY_ENTITY_AND_ID ? SQLITE_GET_BY_ENTITY_AND_ID
: MSSQL_GET_BY_ENTITY_AND_ID; : MSSQL_GET_BY_ENTITY_AND_ID;
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE; const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
const touchEntitySql = isSqlite ? SQLITE_TOUCH_ENTITY : MSSQL_TOUCH_ENTITY;
/** /**
* Updates a contact of an entity. * Updates a contact of an entity.
@@ -100,6 +105,9 @@ export default function update(provider) {
sort, sort,
}; };
await runner.query(touchEntitySql, { id: entityId });
await recordSync({ companyId, entityType: 'customer', entityId }, runner);
return shape(row); return shape(row);
}; };
} }

View File

@@ -14,10 +14,14 @@ import makeRunner from '../runner.js';
import { import {
TOUCH_UPDATED_AT as MSSQL_TOUCH, TOUCH_UPDATED_AT as MSSQL_TOUCH,
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE, TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as MSSQL_TOUCH_STUECKLISTE,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
ENQUEUE_STUECKLISTE_ARTICLES_SYNC as MSSQL_ENQUEUE_STUECKLISTE_SYNC,
} from '../../providers/mssql/queries/article.js'; } from '../../providers/mssql/queries/article.js';
import { import {
TOUCH_UPDATED_AT as SQLITE_TOUCH, TOUCH_UPDATED_AT as SQLITE_TOUCH,
TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE, TOUCH_STUECKLISTE_ARTICLES_UPDATED_AT as SQLITE_TOUCH_STUECKLISTE,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
ENQUEUE_STUECKLISTE_ARTICLES_SYNC as SQLITE_ENQUEUE_STUECKLISTE_SYNC,
} from '../../providers/sqlite3/queries/article.js'; } from '../../providers/sqlite3/queries/article.js';
export const INIT_HASH = 'TODO_TSE_BASED_INITHASH'; export const INIT_HASH = 'TODO_TSE_BASED_INITHASH';
@@ -218,6 +222,12 @@ export default function add(provider) {
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE; const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
await runner.query(touchSql, { articleId: match.id }); await runner.query(touchSql, { articleId: match.id });
await runner.query(touchStuecklisteSql, { articleId: match.id }); await runner.query(touchStuecklisteSql, { articleId: match.id });
if (!isTracked) {
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
const enqueueStuecklisteSyncSql = isSqlite ? SQLITE_ENQUEUE_STUECKLISTE_SYNC : MSSQL_ENQUEUE_STUECKLISTE_SYNC;
await runner.query(enqueueSyncSql, { articleId: match.id });
await runner.query(enqueueStuecklisteSyncSql, { articleId: match.id });
}
await recordAcceptanceHistory(runner, { await recordAcceptanceHistory(runner, {
articleId: match.id, articleId: match.id,
companyuserId, companyuserId,
@@ -312,6 +322,12 @@ export default function add(provider) {
const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE; const touchStuecklisteSql = isSqlite ? SQLITE_TOUCH_STUECKLISTE : MSSQL_TOUCH_STUECKLISTE;
await runner.query(touchSql, { articleId: match.id }); await runner.query(touchSql, { articleId: match.id });
await runner.query(touchStuecklisteSql, { articleId: match.id }); await runner.query(touchStuecklisteSql, { articleId: match.id });
if (!isTracked) {
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
const enqueueStuecklisteSyncSql = isSqlite ? SQLITE_ENQUEUE_STUECKLISTE_SYNC : MSSQL_ENQUEUE_STUECKLISTE_SYNC;
await runner.query(enqueueSyncSql, { articleId: match.id });
await runner.query(enqueueStuecklisteSyncSql, { articleId: match.id });
}
await recordShipmentHistory(runner, { await recordShipmentHistory(runner, {
articleId: match.id, articleId: match.id,
companyuserId, companyuserId,

View File

@@ -1,7 +1,13 @@
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 {
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js'; TOUCH_UPDATED_AT as MSSQL_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as MSSQL_ENQUEUE_SYNC,
} from '../../providers/mssql/queries/article.js';
import {
TOUCH_UPDATED_AT as SQLITE_TOUCH,
ENQUEUE_SYNC_BY_ARTICLE as SQLITE_ENQUEUE_SYNC,
} 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 {
@@ -49,6 +55,7 @@ export default function setOptionsByArticleSku(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; const touchArticleSql = isSqlite ? SQLITE_TOUCH : MSSQL_TOUCH;
const enqueueSyncSql = isSqlite ? SQLITE_ENQUEUE_SYNC : MSSQL_ENQUEUE_SYNC;
/** /**
* Sets or clears the options list for an article. * Sets or clears the options list for an article.
@@ -112,6 +119,7 @@ export default function setOptionsByArticleSku(provider) {
}); });
} }
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
} }
return []; return [];
} }
@@ -242,6 +250,7 @@ export default function setOptionsByArticleSku(provider) {
// Touch article updated_at so POS and other consumers pick up options/deposit changes // Touch article updated_at so POS and other consumers pick up options/deposit changes
await runner.query(touchArticleSql, { articleId }); await runner.query(touchArticleSql, { articleId });
await runner.query(enqueueSyncSql, { articleId });
return result; return result;
}; };

View File

@@ -4,19 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
} }
export default function getCategoryCount(provider) { export default function getCategoryCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Counts categories assigned to the POS terminal updated after the given unix timestamp cursor. * Counts categories assigned to the POS terminal queued after the given sync queue cursor ID.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records. * @returns {Promise<number>} The count of changed records.
*/ */

View File

@@ -4,19 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
} }
export default function getCategoryList(provider) { export default function getCategoryList(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Lists categories for a POS terminal modified after the cursor. * Lists categories for a POS terminal queued after the sync queue cursor ID.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {number} [input.limit=20] - Max items to return. * @param {number} [input.limit=20] - Max items to return.
* @param {object} [tx] - Optional transaction. * @param {object} [tx] - Optional transaction.
* @returns {Promise<Array<object>>} * @returns {Promise<Array<object>>}

View File

@@ -4,21 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
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) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Counts composite products (Stücklisten) assigned to the POS terminal updated after the given unix timestamp cursor. * Counts composite products (Stücklisten) assigned to the POS terminal queued after the given sync queue cursor ID.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records. * @returns {Promise<number>} The count of changed records.
*/ */

View File

@@ -4,19 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
} }
export default function getCustomerCount(provider) { export default function getCustomerCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Counts customers of the POS terminal company updated after the given unix timestamp cursor. * Counts customers of the POS terminal company queued after the given sync queue cursor ID.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records. * @returns {Promise<number>} The count of changed records.
*/ */

View File

@@ -4,19 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
} }
export default function getCustomerGroupCount(provider) { export default function getCustomerGroupCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Counts customer groups of the POS terminal company updated after the given unix timestamp cursor. * Counts customer groups of the POS terminal company queued after the given sync queue cursor ID.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records. * @returns {Promise<number>} The count of changed records.
*/ */

View File

@@ -4,19 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
} }
export default function getCustomerGroupList(provider) { export default function getCustomerGroupList(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Lists customer groups for a POS terminal modified after the cursor, excluding 'Lieferanten'. * Lists customer groups for a POS terminal queued after the sync queue cursor ID, excluding 'Lieferanten'.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {number} [input.limit=50] - Max items to return. * @param {number} [input.limit=50] - Max items to return.
* @param {object} [tx] - Optional transaction. * @param {object} [tx] - Optional transaction.
* @returns {Promise<Array<{customerGroupId: string, name: string, standard: string, discountPercent: string, lastChanged: string}>>} * @returns {Promise<Array<{customerGroupId: string, name: string, standard: string, discountPercent: string, lastChanged: string}>>}

View File

@@ -4,19 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
} }
export default function getCustomerList(provider) { export default function getCustomerList(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Lists customers for a POS terminal modified after the cursor, excluding 'Lieferanten'. * Lists customers for a POS terminal queued after the sync queue cursor ID, excluding 'Lieferanten'.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {number} [input.limit=20] - Max items to return. * @param {number} [input.limit=20] - Max items to return.
* @param {object} [tx] - Optional transaction. * @param {object} [tx] - Optional transaction.
* @returns {Promise<Array<object>>} * @returns {Promise<Array<object>>}

View File

@@ -4,21 +4,18 @@ 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; return n > 0 ? Math.floor(n) : 0;
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) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL; const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/** /**
* Counts products assigned to the POS terminal updated after the given unix timestamp cursor. * Counts products assigned to the POS terminal queued after the given sync queue cursor ID.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {object} [tx] - Optional transaction to run within. * @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records. * @returns {Promise<number>} The count of changed records.
*/ */

View File

@@ -18,10 +18,7 @@ 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; return n > 0 ? Math.floor(n) : 0;
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) {
@@ -46,11 +43,11 @@ export default function getProductList(provider) {
const parentOptionsSql = isSqlite ? SQLITE_PARENT_OPTIONS : MSSQL_PARENT_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 queued after the sync queue cursor ID.
* *
* @param {object} input * @param {object} input
* @param {number} input.posterminalId - The POS terminal id. * @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor. * @param {number} [input.cursor=0] - Sequential ID cursor of last synced posterminalsyncqueue entry.
* @param {number} [input.limit=20] - Max items to return. * @param {number} [input.limit=20] - Max items to return.
* @param {object} [tx] - Optional transaction. * @param {object} [tx] - Optional transaction.
* @returns {Promise<Array<object>>} * @returns {Promise<Array<object>>}

View File

@@ -0,0 +1,28 @@
import { RECORD_SYNC_QUEUE as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
import { RECORD_SYNC_QUEUE as SQLITE_SQL } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
export default function recordSyncQueue(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/**
* Enqueues a sync notification for all POS terminals of a company.
*
* @param {object} input
* @param {number} input.companyId - The company id.
* @param {string} input.entityType - 'category' | 'article' | 'customer' | 'customergroup'
* @param {number} input.entityId - The entity's id.
* @param {object} [tx] - Optional transaction/runner.
* @returns {Promise<void>}
*/
return async ({ companyId, entityType, entityId }, tx) => {
if (!companyId || !entityType || !entityId) return;
const runner = makeRunner(provider, tx);
await runner.query(sql, {
companyId: Number(companyId),
entityType: String(entityType).toLowerCase().trim(),
entityId: Number(entityId),
});
};
}

View File

@@ -8,6 +8,14 @@ import {
SET_OFF as SQLITE_SET_OFF, SET_OFF as SQLITE_SET_OFF,
LIST_CATEGORY_IDS as SQLITE_LIST_CATEGORY_IDS, LIST_CATEGORY_IDS as SQLITE_LIST_CATEGORY_IDS,
} from '../../providers/sqlite3/queries/posterminal.js'; } from '../../providers/sqlite3/queries/posterminal.js';
import {
ENQUEUE_SUBTREE_CATEGORIES_SYNC as MSSQL_ENQUEUE_SUBTREE_CATEGORIES,
ENQUEUE_SUBTREE_ARTICLES_SYNC as MSSQL_ENQUEUE_SUBTREE_ARTICLES,
} from '../../providers/mssql/queries/category.js';
import {
ENQUEUE_SUBTREE_CATEGORIES_SYNC as SQLITE_ENQUEUE_SUBTREE_CATEGORIES,
ENQUEUE_SUBTREE_ARTICLES_SYNC as SQLITE_ENQUEUE_SUBTREE_ARTICLES,
} from '../../providers/sqlite3/queries/category.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -20,6 +28,12 @@ export default function setOff(provider) {
const listCatIdsSql = isSqlite const listCatIdsSql = isSqlite
? SQLITE_LIST_CATEGORY_IDS ? SQLITE_LIST_CATEGORY_IDS
: MSSQL_LIST_CATEGORY_IDS; : MSSQL_LIST_CATEGORY_IDS;
const enqueueCategoriesSql = isSqlite
? SQLITE_ENQUEUE_SUBTREE_CATEGORIES
: MSSQL_ENQUEUE_SUBTREE_CATEGORIES;
const enqueueArticlesSql = isSqlite
? SQLITE_ENQUEUE_SUBTREE_ARTICLES
: MSSQL_ENQUEUE_SUBTREE_ARTICLES;
const touchSql = isSqlite const touchSql = isSqlite
? `WITH RECURSIVE cats AS ( ? `WITH RECURSIVE cats AS (
@@ -182,6 +196,8 @@ export default function setOff(provider) {
await runner.query(touchSql, { categoryId }); await runner.query(touchSql, { categoryId });
await runner.query(touchArticlesSql, { categoryId }); await runner.query(touchArticlesSql, { categoryId });
await runner.query(enqueueCategoriesSql, { id: categoryId, companyId });
await runner.query(enqueueArticlesSql, { id: categoryId, companyId });
const result = await runner.query(listCatIdsSql, { posterminalId }); const result = await runner.query(listCatIdsSql, { posterminalId });

View File

@@ -8,6 +8,14 @@ import {
SET_ON as SQLITE_SET_ON, SET_ON as SQLITE_SET_ON,
LIST_CATEGORY_IDS as SQLITE_LIST_CATEGORY_IDS, LIST_CATEGORY_IDS as SQLITE_LIST_CATEGORY_IDS,
} from '../../providers/sqlite3/queries/posterminal.js'; } from '../../providers/sqlite3/queries/posterminal.js';
import {
ENQUEUE_SUBTREE_CATEGORIES_SYNC as MSSQL_ENQUEUE_SUBTREE_CATEGORIES,
ENQUEUE_SUBTREE_ARTICLES_SYNC as MSSQL_ENQUEUE_SUBTREE_ARTICLES,
} from '../../providers/mssql/queries/category.js';
import {
ENQUEUE_SUBTREE_CATEGORIES_SYNC as SQLITE_ENQUEUE_SUBTREE_CATEGORIES,
ENQUEUE_SUBTREE_ARTICLES_SYNC as SQLITE_ENQUEUE_SUBTREE_ARTICLES,
} from '../../providers/sqlite3/queries/category.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -20,6 +28,12 @@ export default function setOn(provider) {
const listCatIdsSql = isSqlite const listCatIdsSql = isSqlite
? SQLITE_LIST_CATEGORY_IDS ? SQLITE_LIST_CATEGORY_IDS
: MSSQL_LIST_CATEGORY_IDS; : MSSQL_LIST_CATEGORY_IDS;
const enqueueCategoriesSql = isSqlite
? SQLITE_ENQUEUE_SUBTREE_CATEGORIES
: MSSQL_ENQUEUE_SUBTREE_CATEGORIES;
const enqueueArticlesSql = isSqlite
? SQLITE_ENQUEUE_SUBTREE_ARTICLES
: MSSQL_ENQUEUE_SUBTREE_ARTICLES;
const touchSql = isSqlite const touchSql = isSqlite
? `WITH RECURSIVE cats AS ( ? `WITH RECURSIVE cats AS (
SELECT id FROM category WHERE id = @categoryId SELECT id FROM category WHERE id = @categoryId
@@ -166,6 +180,8 @@ export default function setOn(provider) {
await runner.query(touchSql, { categoryId }); await runner.query(touchSql, { categoryId });
await runner.query(touchArticlesSql, { categoryId }); await runner.query(touchArticlesSql, { categoryId });
await runner.query(enqueueCategoriesSql, { id: categoryId, companyId });
await runner.query(enqueueArticlesSql, { id: categoryId, companyId });
const result = await runner.query(listCatIdsSql, { posterminalId }); const result = await runner.query(listCatIdsSql, { posterminalId });

View File

@@ -10,6 +10,8 @@ import {
} from '../../providers/sqlite3/queries/posterminal.js'; } from '../../providers/sqlite3/queries/posterminal.js';
import { GET_COMPANY_ROOT_CATEGORY as MSSQL_ROOT } from '../../providers/mssql/queries/company.js'; import { GET_COMPANY_ROOT_CATEGORY as MSSQL_ROOT } from '../../providers/mssql/queries/company.js';
import { GET_COMPANY_ROOT_CATEGORY as SQLITE_ROOT } from '../../providers/sqlite3/queries/company.js'; import { GET_COMPANY_ROOT_CATEGORY as SQLITE_ROOT } from '../../providers/sqlite3/queries/company.js';
import { ENQUEUE_SUBTREE_CATEGORIES_SYNC as MSSQL_ENQUEUE_SUBTREE_CATEGORIES } from '../../providers/mssql/queries/category.js';
import { ENQUEUE_SUBTREE_CATEGORIES_SYNC as SQLITE_ENQUEUE_SUBTREE_CATEGORIES } from '../../providers/sqlite3/queries/category.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -23,6 +25,16 @@ export default function setOnAll(provider) {
? SQLITE_LIST_CATEGORY_IDS ? SQLITE_LIST_CATEGORY_IDS
: MSSQL_LIST_CATEGORY_IDS; : MSSQL_LIST_CATEGORY_IDS;
const rootSql = isSqlite ? SQLITE_ROOT : MSSQL_ROOT; const rootSql = isSqlite ? SQLITE_ROOT : MSSQL_ROOT;
const enqueueCategoriesSql = isSqlite
? SQLITE_ENQUEUE_SUBTREE_CATEGORIES
: MSSQL_ENQUEUE_SUBTREE_CATEGORIES;
const enqueueAllArticlesSql = `
INSERT INTO posterminalsyncqueue (posterminal_id, entity_type, entity_id)
SELECT pt.id, 'article', a.id
FROM article a
INNER JOIN posterminal pt ON pt.company_id = a.company_id
WHERE a.company_id = @companyId
`;
const touchAllSql = isSqlite const touchAllSql = isSqlite
? `WITH RECURSIVE cats AS ( ? `WITH RECURSIVE cats AS (
SELECT id FROM category WHERE id = @rootCategoryId SELECT id FROM category WHERE id = @rootCategoryId
@@ -100,6 +112,8 @@ export default function setOnAll(provider) {
await runner.query(touchAllSql, { rootCategoryId }); await runner.query(touchAllSql, { rootCategoryId });
await runner.query(touchAllArticlesSql, { companyId }); await runner.query(touchAllArticlesSql, { companyId });
await runner.query(enqueueCategoriesSql, { id: rootCategoryId, companyId });
await runner.query(enqueueAllArticlesSql, { companyId });
const result = await runner.query(listCatIdsSql, { posterminalId }); const result = await runner.query(listCatIdsSql, { posterminalId });

View File

@@ -2,11 +2,13 @@ import {
GET_BY_ID as MSSQL_GET_BY_ID, GET_BY_ID as MSSQL_GET_BY_ID,
CREATE as MSSQL_CREATE, CREATE as MSSQL_CREATE,
UPDATE as MSSQL_UPDATE, UPDATE as MSSQL_UPDATE,
SEED_SYNC_QUEUE_FOR_TERMINAL as MSSQL_SEED_SYNC_QUEUE,
} from '../../providers/mssql/queries/posterminal.js'; } from '../../providers/mssql/queries/posterminal.js';
import { import {
GET_BY_ID as SQLITE_GET_BY_ID, GET_BY_ID as SQLITE_GET_BY_ID,
CREATE as SQLITE_CREATE, CREATE as SQLITE_CREATE,
UPDATE as SQLITE_UPDATE, UPDATE as SQLITE_UPDATE,
SEED_SYNC_QUEUE_FOR_TERMINAL as SQLITE_SEED_SYNC_QUEUE,
} from '../../providers/sqlite3/queries/posterminal.js'; } from '../../providers/sqlite3/queries/posterminal.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import makeRunner from '../runner.js'; import makeRunner from '../runner.js';
@@ -18,6 +20,7 @@ export default function upsert(provider) {
const getByIdSql = isSqlite ? SQLITE_GET_BY_ID : MSSQL_GET_BY_ID; const getByIdSql = isSqlite ? SQLITE_GET_BY_ID : MSSQL_GET_BY_ID;
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE; const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE; const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
const seedSyncQueueSql = isSqlite ? SQLITE_SEED_SYNC_QUEUE : MSSQL_SEED_SYNC_QUEUE;
/** /**
* Creates or updates a posterminal for the user's selected company. * Creates or updates a posterminal for the user's selected company.
@@ -143,6 +146,11 @@ export default function upsert(provider) {
lastseenAt, lastseenAt,
}; };
await runner.query(seedSyncQueueSql, {
posterminalId: row.id,
companyId,
});
return { return {
id: row.id, id: row.id,
name: row.name, name: row.name,