u
This commit is contained in:
11
API.md
11
API.md
@@ -256,8 +256,8 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `ship(provider: object): Function`
|
||||
- [posterminal](API.posterminal.md)
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `findByPairingpin(pairingpin: string, tx?: object): Promise<{id: number, companyId: 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}|null>`
|
||||
- `findByToken(token: string, tx?: object): Promise<{id: number, companyId: 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}|null>`
|
||||
- `findByPairingpin(pairingpin: string, tx?: object): Promise<{id: number, companyId: 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, sort: number, enabled: boolean|number, clientversion: string|null}|null>`
|
||||
- `findByToken(token: string, tx?: object): Promise<{id: number, companyId: 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, sort: number, enabled: boolean|number, clientversion: string|null}|null>`
|
||||
- `getCategoryCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||
- `getCategoryList(input: {posterminalId: number, cursor?: number, limit?: number}, tx?: object): Promise<Array<object>>`
|
||||
- `getCompositeProductCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||
@@ -270,17 +270,18 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `getMaxOrderIdCount(input: {posterminalId: number}, tx?: object): Promise<number>`
|
||||
- `getProductCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||
- `getProductList(input: {posterminalId: number, cursor?: number, limit?: number}, tx?: object): Promise<Array<object>>`
|
||||
- `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}>`
|
||||
- `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, sort: number, enabled: boolean|number, clientversion: string|null}>>`
|
||||
- `pair(id: number, input: {token: string, clientserial?: string, pairedAt?: string, clientversion?: string|null}, tx?: object): Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null, clientversion: string|null}>`
|
||||
- `recordObituary(input: {companyId: number, entityType: string, entityId: number}, tx?: object): Promise<void>`
|
||||
- `recordSyncQueue(input: {companyId: number, entityType: string, entityId: number}, tx?: object): Promise<void>`
|
||||
- `setClientversion(id: number, clientversion: string|null, tx?: object): Promise<{id: number, clientversion: string|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>}>`
|
||||
- `setOffAll(googleAuthId: number, posterminalId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>`
|
||||
- `setOn(googleAuthId: number, posterminalId: number, categoryId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>`
|
||||
- `setOnAll(googleAuthId: number, posterminalId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>`
|
||||
- `touchLastseen(id: number, tx?: object): Promise<{id: number}>`
|
||||
- `upsert(googleAuthId: number, input: {name: string, pairingpin: string, token?: string, id?: number}, tx?: object): Promise<{id: number, name: string, token: string, pairingpin: string}>`
|
||||
- `upsert(googleAuthId: number, input: {name: string, pairingpin: string, token?: string, id?: number, sort?: number, enabled?: boolean}, tx?: object): Promise<{id: number, name: string, token: string, pairingpin: string, sort: number, enabled: number}>`
|
||||
- [printer](API.printer.md)
|
||||
- `create(googleAuthId: number, input: {address: string, type: string, model?: string, serial?: string, sort?: number, desc?: string}, tx?: object): Promise<{id: number, company_id: number, address: string, type: string, model: string|null, serial: string|null, sort: number, desc: string|null}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
|
||||
@@ -45,7 +45,7 @@ Finds a posterminal by its pairingpin.
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: 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}|null>` — The posterminal, or null if not found.
|
||||
`Promise<{id: number, companyId: 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, sort: number, enabled: boolean|number, clientversion: string|null}|null>` — The posterminal, or null if not found.
|
||||
|
||||
## `posterminal.findByToken`
|
||||
|
||||
@@ -62,7 +62,7 @@ Finds a posterminal by its auth token.
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: 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}|null>` — The posterminal, or null if not found.
|
||||
`Promise<{id: number, companyId: 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, sort: number, enabled: boolean|number, clientversion: string|null}|null>` — The posterminal, or null if not found.
|
||||
|
||||
## `posterminal.getCategoryCount`
|
||||
|
||||
@@ -311,13 +311,13 @@ Lists the posterminals of the user's selected company.
|
||||
|
||||
**Returns**
|
||||
|
||||
`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}>>` — The posterminals.
|
||||
`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, sort: number, enabled: boolean|number, clientversion: string|null}>>` — The posterminals.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const terminals = await store.posterminal.list(42);
|
||||
// => [{ id, name, token, pairingpin, pairedAt, clientserial, maxknownobituaryid, rootCategoryId, syncRootArticles, lastseenAt, updatedAt }]
|
||||
// => [{ id, name, token, pairingpin, pairedAt, clientserial, maxknownobituaryid, rootCategoryId, syncRootArticles, lastseenAt, updatedAt, sort, enabled, clientversion }]
|
||||
```
|
||||
|
||||
## `posterminal.pair`
|
||||
@@ -335,11 +335,12 @@ Completes POS device pairing for a terminal.
|
||||
| `input.token` | `string` | The auth token for the paired device. |
|
||||
| `[input.clientserial]` | `string` | The client name or serial identifier. |
|
||||
| `[input.pairedAt]` | `string` | Timestamp when paired. |
|
||||
| `[input.clientversion]` | `string|null` | Client software version reported by the POS device during pairing. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null}>`
|
||||
`Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null, clientversion: string|null}>`
|
||||
|
||||
## `posterminal.recordObituary`
|
||||
|
||||
@@ -381,6 +382,31 @@ Enqueues a sync notification for all POS terminals of a company.
|
||||
|
||||
`Promise<void>`
|
||||
|
||||
## `posterminal.setClientversion`
|
||||
|
||||
*Source: `src/store/posterminal/setClientversion.js`*
|
||||
|
||||
Stores the software version reported by a POS terminal.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | `number` | The posterminal id. |
|
||||
| `clientversion` | `string|null` | Version string reported by the client (trimmed; empty → null). |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, clientversion: string|null}>`
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
await store.posterminal.setClientversion(7, '1.10.12.0');
|
||||
// => { id: 7, clientversion: '1.10.12.0' }
|
||||
```
|
||||
|
||||
## `posterminal.setMaxKnownObituaryId`
|
||||
|
||||
*Source: `src/store/posterminal/setMaxKnownObituaryId.js`*
|
||||
@@ -550,11 +576,13 @@ Creates or updates a posterminal for the user's selected company.
|
||||
| `input.pairingpin` | `string` | The pairing pin. |
|
||||
| `[input.token]` | `string` | Optional token. |
|
||||
| `[input.id]` | `number` | Optional id to update an existing posterminal. |
|
||||
| `[input.sort]` | `number` | Display order (integer, ascending). Default 0 on create. |
|
||||
| `[input.enabled]` | `boolean` | Whether the terminal may sync. Default true on create. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, name: string, token: string, pairingpin: string}>` — The upserted posterminal.
|
||||
`Promise<{id: number, name: string, token: string, pairingpin: string, sort: number, enabled: number}>` — The upserted posterminal.
|
||||
|
||||
**Throws**
|
||||
|
||||
@@ -564,5 +592,5 @@ Creates or updates a posterminal for the user's selected company.
|
||||
|
||||
```js
|
||||
const terminal = await store.posterminal.upsert(42, { name: 'Kasse 1', pairingpin: '1234', token: 'abc' });
|
||||
// => { id, name, token, pairingpin }
|
||||
// => { id, name, token, pairingpin, sort, enabled }
|
||||
```
|
||||
|
||||
@@ -63,7 +63,7 @@ WHERE pairingpin = @pairingpin
|
||||
|
||||
export const PAIR = `
|
||||
UPDATE posterminal
|
||||
SET token = @token, paired_at = @pairedAt, clientserial = @clientserial, pairingpin = NULL, lastseen_at = @pairedAt, updated_at = SYSUTCDATETIME()
|
||||
SET token = @token, paired_at = @pairedAt, clientserial = @clientserial, clientversion = @clientversion, pairingpin = NULL, lastseen_at = @pairedAt, updated_at = SYSUTCDATETIME()
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
@@ -75,7 +75,7 @@ VALUES (@companyId, @name, @token, @pairingpin, @pairedAt, @clientserial, @maxkn
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE posterminal
|
||||
SET name = @name, token = @token, pairingpin = @pairingpin, paired_at = @pairedAt, clientserial = @clientserial, maxknownobituaryid = @maxknownobituaryid, root_category_id = @rootCategoryId, sync_root_articles = @syncRootArticles, lastseen_at = @lastseenAt, sort = @sort, enabled = @enabled, updated_at = SYSUTCDATETIME()
|
||||
SET name = @name, token = @token, pairingpin = @pairingpin, paired_at = @pairedAt, clientserial = @clientserial, maxknownobituaryid = @maxknownobituaryid, root_category_id = @rootCategoryId, sync_root_articles = @syncRootArticles, lastseen_at = @lastseenAt, sort = @sort, enabled = @enabled, clientversion = @clientversion, updated_at = SYSUTCDATETIME()
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
|
||||
@@ -13,15 +13,18 @@ export default function pair(provider) {
|
||||
* @param {string} input.token - The auth token for the paired device.
|
||||
* @param {string} [input.clientserial] - The client name or serial identifier.
|
||||
* @param {string} [input.pairedAt] - Timestamp when paired.
|
||||
* @param {string|null} [input.clientversion] - Client software version reported by the POS device during pairing.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null}>}
|
||||
* @returns {Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null, clientversion: string|null}>}
|
||||
*/
|
||||
return async (id, { token, clientserial, pairedAt }, tx) => {
|
||||
return async (id, { token, clientserial, pairedAt, clientversion }, tx) => {
|
||||
const versionVal = clientversion ? String(clientversion).trim() : null;
|
||||
await makeRunner(provider, tx).query(sql, {
|
||||
id,
|
||||
token,
|
||||
clientserial: clientserial ?? null,
|
||||
pairedAt: pairedAt ?? null,
|
||||
clientversion: versionVal,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -29,6 +32,7 @@ export default function pair(provider) {
|
||||
token,
|
||||
clientserial: clientserial ?? null,
|
||||
pairedAt: pairedAt ?? null,
|
||||
clientversion: versionVal,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ export default function upsert(provider) {
|
||||
|
||||
const sort = input.sort !== undefined ? input.sort : existing.rows[0].sort;
|
||||
const enabled = input.enabled !== undefined ? (input.enabled ? 1 : 0) : (existing.rows[0].enabled ? 1 : 0);
|
||||
const clientversion = input.clientversion !== undefined ? (input.clientversion ? String(input.clientversion).trim() : null) : (existing.rows[0].clientversion ?? null);
|
||||
|
||||
const pairingpin =
|
||||
input.pairingpin !== undefined
|
||||
@@ -116,6 +117,7 @@ export default function upsert(provider) {
|
||||
lastseenAt,
|
||||
sort,
|
||||
enabled,
|
||||
clientversion,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -131,7 +133,7 @@ export default function upsert(provider) {
|
||||
lastseenAt,
|
||||
sort,
|
||||
enabled,
|
||||
clientversion: existing.rows[0].clientversion ?? null,
|
||||
clientversion,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
113
test/migration.test.js
Normal file
113
test/migration.test.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import test from 'node:test';
|
||||
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';
|
||||
import { createTenant } from './helpers.js';
|
||||
|
||||
test('schema migrations & posterminal version 2', async (t) => {
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'dash-migration-test-'));
|
||||
const dbPath = join(tempDir, 'test.db');
|
||||
const provider = new BetterSqlite3Provider({
|
||||
filename: dbPath,
|
||||
storageDir: join(tempDir, 'storage'),
|
||||
});
|
||||
|
||||
t.after(() => {
|
||||
try {
|
||||
provider.db?.close?.();
|
||||
} catch {}
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
await t.test('fresh database is initialized at version 2', async () => {
|
||||
await provider.initialize();
|
||||
const version = await provider.getSchemaVersion();
|
||||
assert.equal(version, 2, 'Schema version must be 2');
|
||||
|
||||
const cols = provider.db.pragma('table_info("posterminal")').map((c) => c.name);
|
||||
assert.ok(cols.includes('sort'), 'posterminal must have sort column');
|
||||
assert.ok(cols.includes('enabled'), 'posterminal must have enabled column');
|
||||
assert.ok(cols.includes('clientversion'), 'posterminal must have clientversion column');
|
||||
});
|
||||
|
||||
let tenant;
|
||||
let store;
|
||||
let terminalId;
|
||||
|
||||
await t.test('posterminal store operations support sort, enabled, clientversion', async () => {
|
||||
store = await new LibDashStore(provider);
|
||||
tenant = await createTenant(store, { name: 'Migration Tenant' });
|
||||
|
||||
const created = await store.posterminal.upsert(tenant.googleAuthId, {
|
||||
name: 'Terminal 1',
|
||||
sort: 5,
|
||||
enabled: true,
|
||||
});
|
||||
assert.equal(created.name, 'Terminal 1');
|
||||
assert.equal(created.sort, 5);
|
||||
assert.equal(created.enabled, 1);
|
||||
terminalId = created.id;
|
||||
|
||||
// setClientversion
|
||||
const versionRes = await store.posterminal.setClientversion(terminalId, '2.1.0');
|
||||
assert.equal(versionRes.clientversion, '2.1.0');
|
||||
|
||||
// list
|
||||
const list = await store.posterminal.list(tenant.googleAuthId);
|
||||
assert.equal(list.length, 1);
|
||||
assert.equal(list[0].sort, 5);
|
||||
assert.equal(list[0].enabled, 1);
|
||||
assert.equal(list[0].clientversion, '2.1.0');
|
||||
|
||||
// patch update
|
||||
const updated = await store.posterminal.upsert(tenant.googleAuthId, {
|
||||
id: terminalId,
|
||||
sort: 10,
|
||||
enabled: false,
|
||||
clientversion: '2.2.0',
|
||||
});
|
||||
assert.equal(updated.sort, 10);
|
||||
assert.equal(updated.enabled, 0);
|
||||
assert.equal(updated.clientversion, '2.2.0');
|
||||
|
||||
// pair with clientversion
|
||||
const paired = await store.posterminal.pair(terminalId, {
|
||||
token: 'tok_abc123',
|
||||
clientserial: 'POS-Device-1',
|
||||
pairedAt: '2026-10-29T03:00:00.000Z',
|
||||
clientversion: '3.0.0-beta',
|
||||
});
|
||||
assert.equal(paired.token, 'tok_abc123');
|
||||
assert.equal(paired.clientversion, '3.0.0-beta');
|
||||
|
||||
const afterPair = await store.posterminal.findByToken('tok_abc123');
|
||||
assert.equal(afterPair.clientversion, '3.0.0-beta');
|
||||
});
|
||||
|
||||
await t.test('downgrade from version 2 to version 1', async () => {
|
||||
const steps = await provider.downgradeSchema(1);
|
||||
assert.equal(steps, 1);
|
||||
const version = await provider.getSchemaVersion();
|
||||
assert.equal(version, 1);
|
||||
|
||||
const cols = provider.db.pragma('table_info("posterminal")').map((c) => c.name);
|
||||
assert.ok(!cols.includes('sort'), 'sort column should be removed');
|
||||
assert.ok(!cols.includes('enabled'), 'enabled column should be removed');
|
||||
assert.ok(!cols.includes('clientversion'), 'clientversion column should be removed');
|
||||
});
|
||||
|
||||
await t.test('upgrade from version 1 back to version 2 on re-initialize', async () => {
|
||||
await provider.initialize();
|
||||
const version = await provider.getSchemaVersion();
|
||||
assert.equal(version, 2);
|
||||
|
||||
const cols = provider.db.pragma('table_info("posterminal")').map((c) => c.name);
|
||||
assert.ok(cols.includes('sort'), 'sort column should be restored');
|
||||
assert.ok(cols.includes('enabled'), 'enabled column should be restored');
|
||||
assert.ok(cols.includes('clientversion'), 'clientversion column should be restored');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user