Compare commits
32 Commits
c172f2f4b1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26f21cd7ac | ||
|
|
d0a33b1f82 | ||
|
|
55f04de8a8 | ||
|
|
76ea6642f4 | ||
|
|
f612c87b6e | ||
|
|
c7d1b1a5d2 | ||
|
|
745d0d0077 | ||
|
|
2e00f4adb3 | ||
|
|
60cf5873cd | ||
|
|
211acd651d | ||
|
|
a4718d81c0 | ||
|
|
0fdffb864f | ||
|
|
64050dffaf | ||
|
|
3cb5ec5a7a | ||
|
|
a5cef78d30 | ||
|
|
c7db2262b0 | ||
|
|
0fb5893a70 | ||
|
|
75a83bdc28 | ||
|
|
b0521a4ea6 | ||
|
|
2ded87abb7 | ||
|
|
2d4d894881 | ||
|
|
ccb81cdb26 | ||
|
|
bab5c151d0 | ||
|
|
563945394b | ||
|
|
358e3009b2 | ||
|
|
5fd7f3228c | ||
|
|
53f1b24ec0 | ||
|
|
6b6c473f9d | ||
|
|
c6171a1d90 | ||
|
|
62d0643b99 | ||
|
|
1da82e2eb9 | ||
|
|
feffe4e1ab |
119
API.agentprinter.md
Normal file
119
API.agentprinter.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# agentprinter
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `agentprinter.delete`
|
||||
|
||||
*Source: `src/store/agentprinter/delete.js`*
|
||||
|
||||
Deletes an agent printer by id for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The agent printer id. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number}>`
|
||||
|
||||
## `agentprinter.findOrCreate`
|
||||
|
||||
*Source: `src/store/agentprinter/findOrCreate.js`*
|
||||
|
||||
Finds or creates an agentprinter by (printeragentId, type, model, serial) combo.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `printeragentId` | `number` | The parent printer agent id. |
|
||||
| `input` | `object` | |
|
||||
| `input.type` | `string` | Printer type ("zpl", "esc/pos", etc.). |
|
||||
| `[input.model]` | `string|null` | Discovered model name. |
|
||||
| `[input.serial]` | `string|null` | Discovered serial number. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{created: boolean, agentprinter: {id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}}>`
|
||||
|
||||
## `agentprinter.getById`
|
||||
|
||||
*Source: `src/store/agentprinter/getById.js`*
|
||||
|
||||
Retrieves an agent printer by id, scoped to the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The agent printer id. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}|null>`
|
||||
|
||||
## `agentprinter.list`
|
||||
|
||||
*Source: `src/store/agentprinter/list.js`*
|
||||
|
||||
Lists agent printers of the user's selected company, optionally filtered by printer agent.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `[printeragentId]` | `number` | Optional printer agent id filter. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<Array<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}>>`
|
||||
|
||||
## `agentprinter.order`
|
||||
|
||||
*Source: `src/store/agentprinter/order.js`*
|
||||
|
||||
Reorders agent printers for a given printer agent or globally across the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `input` | `object` | |
|
||||
| `[input.printeragentId]` | `number` | Optional printer agent id. If omitted, reorders all printers in company. |
|
||||
| `input.agentprinterIds` | `number[]` | Complete ordered list of agent printer IDs. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{agentprinterIds: number[]}>`
|
||||
|
||||
## `agentprinter.update`
|
||||
|
||||
*Source: `src/store/agentprinter/update.js`*
|
||||
|
||||
Updates enabled and/or sort of an agent printer for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `input` | `object` | |
|
||||
| `input.id` | `number` | Agent printer id. |
|
||||
| `[input.enabled]` | `boolean` | Whether the printer is enabled. |
|
||||
| `[input.sort]` | `number` | Display order integer. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}>`
|
||||
86
API.autoinvite.md
Normal file
86
API.autoinvite.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# autoinvite
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `autoinvite.add`
|
||||
|
||||
*Source: `src/store/autoinvite/add.js`*
|
||||
|
||||
Registers an email for automatic company membership. When that email is registered (`google_auth.add`) the invitation is consumed: it is removed from the list and the new user is added to the company with the given role.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the acting Google auth user (must be admin of the selected company). |
|
||||
| `input` | `{email: string, role?: 'admin'|'user'}` | The email to invite and the role (default 'user'). |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, email: string, role: 'admin'|'user'}>` — The created invitation.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized', 'Invalid email', 'Invalid role', 'User already registered', 'Invitation already exists'
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
await store.autoinvite.add(42, { email: 'new@example.com', role: 'user' });
|
||||
```
|
||||
|
||||
## `autoinvite.list`
|
||||
|
||||
*Source: `src/store/autoinvite/list.js`*
|
||||
|
||||
Lists the pending auto-invitations of the admin's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user (must be admin of the selected company). |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<Array<{id: number, email: string, role: 'admin'|'user', createdAt: string}>>` — The invitations ordered by email.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized' / 'No company selected'
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const invites = await store.autoinvite.list(42);
|
||||
```
|
||||
|
||||
## `autoinvite.remove`
|
||||
|
||||
*Source: `src/store/autoinvite/remove.js`*
|
||||
|
||||
Deletes a pending auto-invitation of the admin's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the acting Google auth user (must be admin of the selected company). |
|
||||
| `id` | `number` | The invitation id. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<void>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized', 'Invitation not found'
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
await store.autoinvite.remove(42, 3);
|
||||
```
|
||||
@@ -142,6 +142,37 @@ const id = await store.category.ensureByPath({ googleAuthId: 42, parentId: 5, pa
|
||||
// => 12
|
||||
```
|
||||
|
||||
## `category.generateImage`
|
||||
|
||||
*Source: `src/store/category/generateImage.js`*
|
||||
|
||||
Generates a mosaic image directly from the category's and its subcategories' articles (grouped by subcategory) and sets it as the new main picture for the category.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `inputOrCategoryId` | `number|object` | The id of the category or an options object. |
|
||||
| `[maybeOptsOrSize={}]` | `object|number` | Options object or numeric size. |
|
||||
| `[maybeOptsOrSize.size]` | `number` | Grid size (2 for 2x2, 3 for 3x3, 4 for 4x4). Defaults to 3 (falls back to 2 if < 7 images). |
|
||||
| `[maybeTx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, smallThumbW: number, smallThumbH: number, size: number, grid: string, articleCount: number}>` — The new main image info.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — If the category is not found, not authorized, size is invalid, or no images are found.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const result = await store.category.generateImage(42, 5, { size: 3 });
|
||||
// => { id: 5, fullHash, thumbW, thumbH, thumbHash, smallThumbW, smallThumbH, size: 3, grid: '3x3', articleCount: 12 }
|
||||
```
|
||||
|
||||
## `category.getCategoryArticles`
|
||||
|
||||
*Source: `src/store/category/getCategoryArticles.js`*
|
||||
|
||||
@@ -6,7 +6,7 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
|
||||
|
||||
*Source: `src/store/companyuser/add.js`*
|
||||
|
||||
Adds a company user linking a company to a Google auth account.
|
||||
Adds a company user linking a company to a Google auth account. No admin check: this is the low-level link primitive (company creation, invitations). Use `companyuser.addByEmail` for the admin-guarded flow.
|
||||
|
||||
**Parameters**
|
||||
|
||||
@@ -14,19 +14,52 @@ Adds a company user linking a company to a Google auth account.
|
||||
| --- | --- | --- |
|
||||
| `companyId` | `number` | The id of the company. |
|
||||
| `googleAuthId` | `number` | The id of the Google auth account. |
|
||||
| `[role='user']` | `'admin'|'user'` | The role within the company. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<number>` — The id of the created company user link.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Invalid role' if the role is not 'admin' or 'user'.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const id = await store.companyuser.add(1, 42);
|
||||
const id = await store.companyuser.add(1, 42, 'admin');
|
||||
// => 7
|
||||
```
|
||||
|
||||
## `companyuser.addByEmail`
|
||||
|
||||
*Source: `src/store/companyuser/addByEmail.js`*
|
||||
|
||||
Adds an existing Google auth user to the admin's selected company. The added user can then select the company as their own selected company. For emails that are not registered yet use `autoinvite.add`.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the acting Google auth user (must be admin of the selected company). |
|
||||
| `input` | `{email: string, role?: 'admin'|'user'}` | Email of the existing user and role (default 'user'). |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, googleAuthId: number, email: string, picture: string|null, role: 'admin'|'user'}>` — The created company user.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized', 'Invalid email', 'Invalid role', 'User not found', 'User already in company'
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const member = await store.companyuser.addByEmail(42, { email: 'new@example.com', role: 'user' });
|
||||
```
|
||||
|
||||
## `companyuser.delete`
|
||||
|
||||
*Source: `src/store/companyuser/delete.js`*
|
||||
@@ -73,3 +106,134 @@ Returns the company user ids associated with the given email address.
|
||||
const ids = await store.companyuser.getByEmail('user@example.com');
|
||||
// => [1, 2, 3]
|
||||
```
|
||||
|
||||
## `companyuser.list`
|
||||
|
||||
*Source: `src/store/companyuser/list.js`*
|
||||
|
||||
Lists the members of the admin's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user (must be admin of the selected company). |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<Array<{id: number, googleAuthId: number, email: string, picture: string|null, role: 'admin'|'user'}>>` — The company users ordered by email.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized' / 'No company selected'
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const members = await store.companyuser.list(42);
|
||||
```
|
||||
|
||||
## `companyuser.remove`
|
||||
|
||||
*Source: `src/store/companyuser/remove.js`*
|
||||
|
||||
Removes a member from the admin's selected company. History entries keep existing but lose their author link; if the removed user had this company selected, their selection is cleared.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the acting Google auth user (must be admin of the selected company). |
|
||||
| `id` | `number` | The company user id to remove. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, googleAuthId: number}>` — The removed company user.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized', 'Company user not found', 'Cannot remove the last admin'
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
await store.companyuser.remove(42, 7);
|
||||
```
|
||||
|
||||
## `companyuser.requireAdmin`
|
||||
|
||||
*Source: `src/store/companyuser/requireAdmin.js`*
|
||||
|
||||
Resolves the selected company and requires the user to be its admin.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | |
|
||||
| `[tx]` | `object` | |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{companyId: number, companyuserId: number, role: string}>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized' if the user is not an admin of the selected company.
|
||||
|
||||
## `companyuser.roles`
|
||||
|
||||
*Source: `src/store/companyuser/roles.js`*
|
||||
|
||||
Validates a company user role.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `role` | `unknown` | |
|
||||
|
||||
**Returns**
|
||||
|
||||
`'admin'|'user'`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Invalid role'
|
||||
|
||||
## `companyuser.setRole`
|
||||
|
||||
*Source: `src/store/companyuser/setRole.js`*
|
||||
|
||||
Changes the role of a member of the admin's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the acting Google auth user (must be admin of the selected company). |
|
||||
| `id` | `number` | The company user id. |
|
||||
| `role` | `'admin'|'user'` | The new role. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, googleAuthId: number, email: string, picture: string|null, role: 'admin'|'user'}>` — The updated company user.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized', 'Invalid role', 'Company user not found', 'Cannot demote the last admin'
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
await store.companyuser.setRole(42, 7, 'admin');
|
||||
```
|
||||
|
||||
## `companyuser.withTransaction`
|
||||
|
||||
*Source: `src/store/companyuser/withTransaction.js`*
|
||||
|
||||
Runs `fn(runner)` inside the given transaction, a new one, or (providers without transaction support) directly.
|
||||
|
||||
@@ -6,7 +6,7 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
|
||||
|
||||
*Source: `src/store/google_auth/add.js`*
|
||||
|
||||
Adds a new allowed Google auth email.
|
||||
Adds a new allowed Google auth email (registration). Pending `autoinvite` rows for this email are consumed in the same transaction: each is deleted and turned into a `companyuser` with the invited role.
|
||||
|
||||
**Parameters**
|
||||
|
||||
@@ -144,7 +144,7 @@ await store.google_auth.setProfile(42, { picture: 'url', pictureBinary: buffer,
|
||||
|
||||
*Source: `src/store/google_auth/setSelectedCompany.js`*
|
||||
|
||||
Sets the currently selected company for a Google auth user.
|
||||
Sets the currently selected company for a Google auth user. The user must be a member (`companyuser`) of the company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
@@ -157,6 +157,10 @@ Sets the currently selected company for a Google auth user.
|
||||
|
||||
`Promise<void>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Not authorized' if the user is not a member of the company.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
|
||||
67
API.md
67
API.md
@@ -62,10 +62,11 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- **ordersummary**: `id PK, company_id->company, manifest_id->manifest UQ, order_number:s(100), order_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, paid_total:f=0, ordered_quantity:f=0, shipped_quantity:f=0, is_paid:b=0, is_shipped:b=0`
|
||||
- **package**: `id PK, shipping_id->shipping, entityaddress_id->entityaddress, number:s(100), type:s(100), state:s(100), details?:txt`
|
||||
- **payment**: `id PK, account_id->account, manifest_id->manifest, type:s(100), amount:f`
|
||||
- **pic**: `id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), source:txt, fullbinary:bin, thumbbinary:bin`
|
||||
- **pic**: `id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), smallthumbw:i, smallthumbh:i, smallthumbbinary:bin, source:txt`
|
||||
- **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)`
|
||||
- **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`
|
||||
- **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)`
|
||||
@@ -81,9 +82,16 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
## Table of Contents
|
||||
|
||||
- [companyuser](API.companyuser.md)
|
||||
- `add(companyId: number, googleAuthId: number, tx?: object): Promise<number>`
|
||||
- `add(companyId: number, googleAuthId: number, role?: 'admin'|'user', tx?: object): Promise<number>`
|
||||
- `addByEmail(googleAuthId: number, input: {email: string, role?: 'admin'|'user'}, tx?: object): Promise<{id: number, googleAuthId: number, email: string, picture: string|null, role: 'admin'|'user'}>`
|
||||
- `delete(id: number, tx?: object): Promise<void>`
|
||||
- `getByEmail(email: string, tx?: object): Promise<Array<number>>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, googleAuthId: number, email: string, picture: string|null, role: 'admin'|'user'}>>`
|
||||
- `remove(googleAuthId: number, id: number, tx?: object): Promise<{id: number, googleAuthId: number}>`
|
||||
- `requireAdmin(googleAuthId: number, tx?: object): Promise<{companyId: number, companyuserId: number, role: string}>`
|
||||
- `roles(role: unknown): 'admin'|'user'`
|
||||
- `setRole(googleAuthId: number, id: number, role: 'admin'|'user', tx?: object): Promise<{id: number, googleAuthId: number, email: string, picture: string|null, role: 'admin'|'user'}>`
|
||||
- `withTransaction(): void`
|
||||
- [company](API.company.md)
|
||||
- `addHistory(googleAuthId: number, input: {text?: string}, tx?: object): Promise<{id: number, companyuserId: number|null, date: string, text: string}>`
|
||||
- `create(input: {iso639: string, name: string, taxrate: number, reducedtaxrate: number, googleAuthId: number}, tx?: object): Promise<{id: number, rootCategoryId: number, languageId: number, taxrateId: number, reducedtaxrateId: number, taxfreerateId: number, posCustomerGroupId: number}>`
|
||||
@@ -99,6 +107,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `delete(input: {googleAuthId: number, id: number, recursive?: boolean}, tx?: object): Promise<void>`
|
||||
- `deleteImage(googleAuthId: number, categoryId: number, tx?: object): Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null}>`
|
||||
- `ensureByPath(input: {googleAuthId: number, parentId: number, path: string}, tx?: object): Promise<number>`
|
||||
- `generateImage(googleAuthId: number, inputOrCategoryId: number|object, maybeOptsOrSize?: {size?: number}, maybeTx?: object): Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string, smallThumbW: number, smallThumbH: number, size: number, grid: string, articleCount: number}>`
|
||||
- `getCategoryArticles(googleAuthId: number, categoryId: number, tx?: object): Promise<Array<{id: number, sku: string, name: string, slug: string, net: number|null, taxRate: number|null, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null, smallThumbW: number|null, smallThumbH: number|null, smallThumbBinary: string|null, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, articlewarehousesummary: Array<{id: number, articleId: number, warehouseId: number, warehouseName: string, stock: number}>, warehouses: Array<{id: number, name: string, stock: number}>}>>`
|
||||
- `getTreeForCompany(googleAuthId: number, tx?: object): Promise<{languageId: number, tree: Array<{id: number, parentId: number|null, sort: number, name: string|null, slug: string|null, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null, articleCount: number, posterminalIds: number[]}>, articleCount: number}>`
|
||||
- `move(input: {googleAuthId: number, id: number, parentId: number}, tx?: object): Promise<{id: number, parentId: number, sort: number}>`
|
||||
@@ -254,8 +263,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>`
|
||||
@@ -268,16 +277,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>`
|
||||
@@ -340,6 +351,13 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `create(googleAuthId: number, input: {type: string, name: string}, tx?: object): Promise<{id: number, companyId: number, type: string, name: string}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, companyId: number, type: string, name: string}>>`
|
||||
- [agentprinter](API.agentprinter.md)
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<{id: number}>`
|
||||
- `findOrCreate(printeragentId: number, input: {type: string, model?: string|null, serial?: string|null}, tx?: object): Promise<{created: boolean, agentprinter: {id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}}>`
|
||||
- `getById(googleAuthId: number, id: number, tx?: object): Promise<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}|null>`
|
||||
- `list(googleAuthId: number, printeragentId?: number, tx?: object): Promise<Array<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}>>`
|
||||
- `order(googleAuthId: number, input: {printeragentId?: number, agentprinterIds: number[]}, tx?: object): Promise<{agentprinterIds: number[]}>`
|
||||
- `update(googleAuthId: number, input: {id: number, enabled?: boolean, sort?: number}, tx?: object): Promise<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}>`
|
||||
- [articlehistory](API.articlehistory.md)
|
||||
- `record(): void`
|
||||
- [articlelist](API.articlelist.md)
|
||||
@@ -347,6 +365,10 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `setItemsByArticleSku(googleAuthId: number, sku: string, items?: Array<{articleId?: number, sku?: string, count?: number, sort?: number}>, tx?: object): Promise<Array<{id: number, articleId: number, sku: string, name: string|null, count: number, sort: number}>>`
|
||||
- [articlewarehousesummary](API.articlewarehousesummary.md)
|
||||
- `syncSummary(): void`
|
||||
- [autoinvite](API.autoinvite.md)
|
||||
- `add(googleAuthId: number, input: {email: string, role?: 'admin'|'user'}, tx?: object): Promise<{id: number, email: string, role: 'admin'|'user'}>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, email: string, role: 'admin'|'user', createdAt: string}>>`
|
||||
- `remove(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- [carrier](API.carrier.md)
|
||||
- `create(googleAuthId: number, input: {name: string, sort?: number, hasPackages?: boolean, isReverse?: boolean}, tx?: object): Promise<{id: number, name: string, sort: number, hasPackages: boolean, isReverse: boolean}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
@@ -400,6 +422,37 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `create(googleAuthId: number, input: {accountId: number, manifestId: number, type: string, amount: number}, tx?: object): Promise<{id: number, accountId: number, manifestId: number, type: string, amount: number}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `listByManifestId(googleAuthId: number, manifestId: number, tx?: object): Promise<Array<{id: number, accountId: number, manifestId: number, type: string, amount: number}>>`
|
||||
- [printeragent](API.printeragent.md)
|
||||
- `create(googleAuthId: number, input: {name: string, pairingpin: string, sort?: number}, tx?: object): Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>`
|
||||
- `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, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}|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, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}|null>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>>`
|
||||
- `pair(id: number, input: {token: string, clientserial?: string, clientmodel?: string, agentversion?: string, pairedAt?: string}, tx?: object): Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>`
|
||||
- `touchLastseen(id: number, agentversion?: string|null, tx?: object): Promise<{id: number}>`
|
||||
- `update(googleAuthId: number, input: {id: number, name?: string, sort?: number}, tx?: object): Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>`
|
||||
- `updateState(id: number, clientstate: string|null, tx?: object): Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>`
|
||||
- [printerjob](API.printerjob.md)
|
||||
- `create(googleAuthId: number, input: {agentprinterId: number, data: string, type?: string, state?: string}, tx?: object): Promise<object>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<object>`
|
||||
- `getById(auth: number|{agentId: number}, id: number, tx?: object): Promise<object|null>`
|
||||
- `list(auth: number|{agentId: number}, filters?: {agentprinterId?: number, state?: string}, tx?: object): Promise<Array<object>>`
|
||||
- `updateState(auth: number|{agentId: number}, id: number, state: string, tx?: object): Promise<object>`
|
||||
- [retoure](API.retoure.md)
|
||||
- `create(googleAuthId: number, body: {number?: string, date: string, entityId?: number, externalNumber?: string, lines?: Array<object>}, tx?: object): Promise<object>`
|
||||
- `createShipment(googleAuthId: number, retoureId: number, input: object, tx?: object): Promise<object>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `getById(googleAuthId: number, id: number, tx?: object): Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>`
|
||||
- `list(googleAuthId: number, opts?: {offset?: number, limit?: number, search?: string, onlyUnpaid?: boolean, onlyUndelivered?: boolean, seekMonths?: number}, tx?: object): Promise<{retoures: Array<object>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<object>>`
|
||||
- `rebuildSummaries(provider: import('../../providers/provider.js').DatabaseProvider): void`
|
||||
- `syncSummary(): void`
|
||||
- `update(googleAuthId: number, id: number, patch: {number?: string, date?: string, entityId?: number, externalNumber?: string, lines?: Array<object>}, tx?: object): Promise<object>`
|
||||
- [return](API.return.md)
|
||||
- `book(provider: object): Function`
|
||||
- `getById(googleAuthId: number, id: number, tx?: object): Promise<object>`
|
||||
- `list(googleAuthId: number, opts?: {offset?: number, limit?: number, search?: string, onlyUnbooked?: boolean, supplierId?: number, seekMonths?: number}, tx?: object): Promise<{returns: Array<object>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<object>>`
|
||||
- `rebuildSummaries(provider: import('../../providers/provider.js').DatabaseProvider): void`
|
||||
- `syncSummary(): void`
|
||||
- [subagentschedule](API.subagentschedule.md)
|
||||
- `create(googleAuthId: number, input: {prompt: string, cron: string, desc?: string, skills?: string|Array<string>, lastagentnote?: string, max_itr?: number}, tx?: object): Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
|
||||
@@ -24,7 +24,7 @@ Ensures a number sequence row exists for a company and type, inferring the compa
|
||||
|
||||
*Source: `src/store/numbers/list.js`*
|
||||
|
||||
Lists the number sequences of the user's selected company. Ensures that default sequences ('sku', 'entity', 'delivery', 'adjustment', 'shipment', 'quote', 'order') exist.
|
||||
Lists the number sequences of the user's selected company. Ensures that default sequences ('sku', 'entity', 'delivery', 'adjustment', 'shipment', 'quote', 'order', 'return', 'retoure') exist.
|
||||
|
||||
**Parameters**
|
||||
|
||||
|
||||
@@ -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,13 +62,13 @@ 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`
|
||||
|
||||
*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**
|
||||
|
||||
@@ -76,7 +76,7 @@ Counts categories assigned to the POS terminal updated after the given unix time
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
|
||||
**Returns**
|
||||
@@ -87,7 +87,7 @@ Counts categories assigned to the POS terminal updated after the given unix time
|
||||
|
||||
*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**
|
||||
|
||||
@@ -95,7 +95,7 @@ Lists categories for a POS terminal modified after the cursor.
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
@@ -107,7 +107,7 @@ Lists categories for a POS terminal modified after the cursor.
|
||||
|
||||
*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**
|
||||
|
||||
@@ -115,7 +115,7 @@ Counts composite products (Stücklisten) assigned to the POS terminal updated af
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
|
||||
**Returns**
|
||||
@@ -126,7 +126,7 @@ Counts composite products (Stücklisten) assigned to the POS terminal updated af
|
||||
|
||||
*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**
|
||||
|
||||
@@ -134,7 +134,7 @@ Counts customers of the POS terminal company updated after the given unix timest
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
|
||||
**Returns**
|
||||
@@ -145,7 +145,7 @@ Counts customers of the POS terminal company updated after the given unix timest
|
||||
|
||||
*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**
|
||||
|
||||
@@ -153,7 +153,7 @@ Counts customer groups of the POS terminal company updated after the given unix
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
|
||||
**Returns**
|
||||
@@ -164,7 +164,7 @@ Counts customer groups of the POS terminal company updated after the given unix
|
||||
|
||||
*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**
|
||||
|
||||
@@ -172,7 +172,7 @@ Lists customer groups for a POS terminal modified after the cursor, excluding 'L
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
| `[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`*
|
||||
|
||||
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**
|
||||
|
||||
@@ -192,7 +192,7 @@ Lists customers for a POS terminal modified after the cursor, excluding 'Liefera
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
| `[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`*
|
||||
|
||||
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**
|
||||
|
||||
@@ -269,7 +269,7 @@ Counts products assigned to the POS terminal updated after the given unix timest
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
|
||||
**Returns**
|
||||
@@ -280,7 +280,7 @@ Counts products assigned to the POS terminal updated after the given unix timest
|
||||
|
||||
*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**
|
||||
|
||||
@@ -288,7 +288,7 @@ Lists products for a POS terminal modified after the cursor.
|
||||
| --- | --- | --- |
|
||||
| `input` | `object` | |
|
||||
| `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. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -361,6 +362,51 @@ Records an obituary for all active paired POS terminals of a company.
|
||||
|
||||
`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.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`*
|
||||
@@ -530,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**
|
||||
|
||||
@@ -544,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 }
|
||||
```
|
||||
|
||||
222
API.printeragent.md
Normal file
222
API.printeragent.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# printeragent
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `printeragent.create`
|
||||
|
||||
*Source: `src/store/printeragent/create.js`*
|
||||
|
||||
Creates an unpaired printer agent for the user's selected company. The agent registers itself later using the pairing pin.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `input` | `object` | The printer agent input. |
|
||||
| `input.name` | `string` | The display name. |
|
||||
| `input.pairingpin` | `string` | The pairing pin the agent uses to register. |
|
||||
| `[input.sort]` | `number` | Display order (integer, ascending). Default 0. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>` — The created printer agent.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Invalid printeragent' if name or pairingpin is missing/blank or sort is not an integer.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const agent = await store.printeragent.create(42, { name: 'Bar printer', pairingpin: '123456' });
|
||||
```
|
||||
|
||||
## `printeragent.delete`
|
||||
|
||||
*Source: `src/store/printeragent/delete.js`*
|
||||
|
||||
Deletes a printer agent of the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The printer agent id. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<void>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Printeragent not found' if the id is unknown in the company.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
await store.printeragent.delete(42, 5);
|
||||
```
|
||||
|
||||
## `printeragent.findByPairingpin`
|
||||
|
||||
*Source: `src/store/printeragent/findByPairingpin.js`*
|
||||
|
||||
Finds a printer agent by its pairing pin.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `pairingpin` | `string` | The pairing pin to search for. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}|null>` — The printer agent, or null if not found.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const agent = await store.printeragent.findByPairingpin('123456');
|
||||
```
|
||||
|
||||
## `printeragent.findByToken`
|
||||
|
||||
*Source: `src/store/printeragent/findByToken.js`*
|
||||
|
||||
Finds a printer agent by its authentication token.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `token` | `string` | The auth token to search for. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}|null>` — The printer agent, or null if not found.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const agent = await store.printeragent.findByToken('abc...');
|
||||
```
|
||||
|
||||
## `printeragent.list`
|
||||
|
||||
*Source: `src/store/printeragent/list.js`*
|
||||
|
||||
Lists the printer agents of the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<Array<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>>` — The printer agents.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const agents = await store.printeragent.list(42);
|
||||
// => [{ id, companyId, name, token, pairingpin, pairedAt, clientserial, clientmodel, clientstate, agentversion, lastseenAt, updatedAt, sort }]
|
||||
```
|
||||
|
||||
## `printeragent.pair`
|
||||
|
||||
*Source: `src/store/printeragent/pair.js`*
|
||||
|
||||
Completes device pairing for a printer agent.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | `number` | The printer agent id. |
|
||||
| `input` | `object` | |
|
||||
| `input.token` | `string` | Auth token for the paired device. |
|
||||
| `[input.clientserial]` | `string` | Device serial identifier. |
|
||||
| `[input.clientmodel]` | `string` | Device model name. |
|
||||
| `[input.agentversion]` | `string` | Device agent software version. |
|
||||
| `[input.pairedAt]` | `string` | Timestamp when paired. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>`
|
||||
|
||||
## `printeragent.touchLastseen`
|
||||
|
||||
*Source: `src/store/printeragent/touchLastseen.js`*
|
||||
|
||||
Updates lastseen_at (and optionally agentversion) for a printer agent.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | `number` | The printer agent id. |
|
||||
| `[agentversion]` | `string|null` | Optional agent version to update. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number}>`
|
||||
|
||||
## `printeragent.update`
|
||||
|
||||
*Source: `src/store/printeragent/update.js`*
|
||||
|
||||
Updates name and/or sort of a printer agent of the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `input` | `object` | The update input. |
|
||||
| `input.id` | `number` | The printer agent id. |
|
||||
| `[input.name]` | `string` | The new display name. |
|
||||
| `[input.sort]` | `number` | The new display order (integer). |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>` — The updated printer agent.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — 'Invalid printeragent' if name is blank, sort is not an integer, or neither is given; 'Printeragent not found' if id is unknown in the company.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const agent = await store.printeragent.update(42, { id: 5, name: 'Kitchen printer', sort: 1 });
|
||||
```
|
||||
|
||||
## `printeragent.updateState`
|
||||
|
||||
*Source: `src/store/printeragent/updateState.js`*
|
||||
|
||||
Updates the client state of a printer agent.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `id` | `number` | The printer agent id. |
|
||||
| `clientstate` | `string|null` | The state payload/string. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, companyId: number, name: string, token: string|null, pairingpin: string|null, pairedAt: string|null, clientserial: string|null, clientmodel: string|null, clientstate: string|null, agentversion: string|null, lastseenAt: string|null, updatedAt: string, sort: number}>`
|
||||
100
API.printerjob.md
Normal file
100
API.printerjob.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# printerjob
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `printerjob.create`
|
||||
|
||||
*Source: `src/store/printerjob/create.js`*
|
||||
|
||||
Creates a new printer job for an agent printer belonging to the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `input` | `object` | |
|
||||
| `input.agentprinterId` | `number` | Target agent printer ID. |
|
||||
| `input.data` | `string` | Job payload (ZPL code, etc.). |
|
||||
| `[input.type]` | `string` | Job type ("zpl", "pdf", etc., defaults to agentprinter type or "zpl"). |
|
||||
| `[input.state]` | `string` | Initial state (defaults to "pending"). |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — Created printer job with printeragentId attached.
|
||||
|
||||
## `printerjob.delete`
|
||||
|
||||
*Source: `src/store/printerjob/delete.js`*
|
||||
|
||||
Deletes a printer job belonging to the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | Printer job ID. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — The deleted job's snapshot.
|
||||
|
||||
## `printerjob.getById`
|
||||
|
||||
*Source: `src/store/printerjob/getById.js`*
|
||||
|
||||
Retrieves a printer job by ID. If caller is an agent ({ agentId: number }), checks that the job belongs to that printer agent. If caller is a user (googleAuthId: number), checks that the job belongs to the selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `auth` | `number|{agentId: number}` | User's googleAuthId or { agentId }. |
|
||||
| `id` | `number` | Printer job ID. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object|null>`
|
||||
|
||||
## `printerjob.list`
|
||||
|
||||
*Source: `src/store/printerjob/list.js`*
|
||||
|
||||
Lists printer jobs. If caller is an agent ({ agentId: number }), lists jobs for that agent. If caller is a user (googleAuthId: number), lists jobs for the selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `auth` | `number|{agentId: number}` | User's googleAuthId or { agentId }. |
|
||||
| `[filters]` | `object` | |
|
||||
| `[filters.agentprinterId]` | `number` | Filter by agentprinterId (company callers only). |
|
||||
| `[filters.state]` | `string` | Filter by job state (e.g. "pending"). |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<Array<object>>`
|
||||
|
||||
## `printerjob.updateState`
|
||||
|
||||
*Source: `src/store/printerjob/updateState.js`*
|
||||
|
||||
Updates a printer job's state ("processing", "completed", "cancelled", etc.).
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `auth` | `number|{agentId: number}` | User's googleAuthId or { agentId }. |
|
||||
| `id` | `number` | Printer job ID. |
|
||||
| `state` | `string` | New state. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — Updated printer job.
|
||||
155
API.retoure.md
Normal file
155
API.retoure.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# retoure
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `retoure.create`
|
||||
|
||||
*Source: `src/store/retoure/create.js`*
|
||||
|
||||
Creates a new retoure for the user's selected company with its lines.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `body` | `object` | The retoure input. |
|
||||
| `[body.number]` | `string` | The retoure number. |
|
||||
| `body.date` | `string` | The retoure date. |
|
||||
| `[body.entityId]` | `number` | The id of the customer entity. |
|
||||
| `[body.externalNumber]` | `string` | An external reference number. |
|
||||
| `[body.lines]` | `Array<object>` | The retoure lines. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — The created retoure.
|
||||
|
||||
## `retoure.createShipment`
|
||||
|
||||
*Source: `src/store/retoure/createShipment.js`*
|
||||
|
||||
Creates a shipment manifest for a retoure.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `retoureId` | `number` | The parent retoure manifest id. |
|
||||
| `input` | `object` | Shipment input. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — The created shipment.
|
||||
|
||||
## `retoure.delete`
|
||||
|
||||
*Source: `src/store/retoure/delete.js`*
|
||||
|
||||
Deletes a retoure by its id within the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The id of the retoure to delete. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<void>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — If the retoure is not found.
|
||||
|
||||
## `retoure.getById`
|
||||
|
||||
*Source: `src/store/retoure/getById.js`*
|
||||
|
||||
Returns a single retoure by its id within the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The id of the retoure. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, number: string, type: string, entityId: number|null, externalNumber: string|null, date: string, lines: Array<{id: number, sku: string|null, name: string|null, pu: number, amount: number, net: number, rebate: number, taxrate: number, comment: string|null}>}>` — The retoure with its lines.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — If the retoure is not found.
|
||||
|
||||
## `retoure.list`
|
||||
|
||||
*Source: `src/store/retoure/list.js`*
|
||||
|
||||
Lists the retoures of the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `[opts]` | `object` | Optional pagination and filter options. |
|
||||
| `[opts.offset=0]` | `number` | Number of retoures to skip. |
|
||||
| `[opts.limit=50]` | `number` | Number of retoures to return. |
|
||||
| `[opts.search]` | `string` | Free-text search matching retoure number, external number, or customer name. |
|
||||
| `[opts.onlyUnpaid=false]` | `boolean` | When true, returns only unpaid retoures. |
|
||||
| `[opts.onlyUndelivered=false]` | `boolean` | When true, returns only undelivered retoures. |
|
||||
| `[opts.seekMonths]` | `number` | Negative integer for cutoff months ago (e.g. -1). |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{retoures: Array<object>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<object>>`
|
||||
|
||||
## `retoure.rebuildSummaries`
|
||||
|
||||
*Source: `src/store/retoure/rebuildSummaries.js`*
|
||||
|
||||
Rebuilds the `retouresummary` table for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `provider` | `import('../../providers/provider.js').DatabaseProvider` | |
|
||||
|
||||
## `retoure.syncSummary`
|
||||
|
||||
*Source: `src/store/retoure/syncSummary.js`*
|
||||
|
||||
Centralized sync and rebuild logic for the materialized `retouresummary` table.
|
||||
|
||||
## `retoure.update`
|
||||
|
||||
*Source: `src/store/retoure/update.js`*
|
||||
|
||||
Updates a retoure's header and/or lines.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The id of the retoure to update. |
|
||||
| `patch` | `object` | The update patch. |
|
||||
| `[patch.number]` | `string` | The new retoure number. |
|
||||
| `[patch.date]` | `string` | The new retoure date. |
|
||||
| `[patch.entityId]` | `number` | The new customer entity id. |
|
||||
| `[patch.externalNumber]` | `string` | The new external reference number. |
|
||||
| `[patch.lines]` | `Array<object>` | The new retoure lines. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — The updated retoure.
|
||||
83
API.return.md
Normal file
83
API.return.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# return
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `return.book`
|
||||
|
||||
*Source: `src/store/return/book.js`*
|
||||
|
||||
Books all positions of a return manifest to the ledger.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `provider` | `object` | Storage provider instance. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Function` — `(googleAuthId, input, tx) => Promise<object>`
|
||||
|
||||
## `return.getById`
|
||||
|
||||
*Source: `src/store/return/getById.js`*
|
||||
|
||||
Returns a return manifest by its id within the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The id of the return manifest. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<object>` — The return manifest with lines and summary.
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — If the return is not found.
|
||||
|
||||
## `return.list`
|
||||
|
||||
*Source: `src/store/return/list.js`*
|
||||
|
||||
Lists the returns of the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `[opts]` | `object` | Optional pagination and filter options. |
|
||||
| `[opts.offset=0]` | `number` | Number of returns to skip. |
|
||||
| `[opts.limit=50]` | `number` | Number of returns to return. |
|
||||
| `[opts.search]` | `string` | Free-text search matching return number, external number, or supplier name. |
|
||||
| `[opts.onlyUnbooked=false]` | `boolean` | When true, returns only returns with unbooked stock items. |
|
||||
| `[opts.supplierId]` | `number` | Filter by supplier entity id. |
|
||||
| `[opts.seekMonths]` | `number` | Negative integer for cutoff months ago. |
|
||||
| `[tx]` | `object` | Optional transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{returns: Array<object>, total: number, unfilteredTotal: number, offset: number, limit: number}|Array<object>>`
|
||||
|
||||
## `return.rebuildSummaries`
|
||||
|
||||
*Source: `src/store/return/rebuildSummaries.js`*
|
||||
|
||||
Rebuilds the `returnsummary` table for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `provider` | `import('../../providers/provider.js').DatabaseProvider` | |
|
||||
|
||||
## `return.syncSummary`
|
||||
|
||||
*Source: `src/store/return/syncSummary.js`*
|
||||
|
||||
Centralized sync and rebuild logic for the materialized `returnsummary` table. Keeps `returnsummary` in sync with manifest (return), manifestarticle, article (track_stock), and ledger (RETURN transactions).
|
||||
@@ -563,7 +563,7 @@ Company-scoped POS terminals. Auth via selected company (`google_auth.selected_c
|
||||
|
||||
`move` moves a category to a new parent. Both `id` (source) and `parentId` (target) are validated via `resolveCategoryAccess` — the target must be a real category of the same authorized company, else `Parent not found`. Moving into itself or into a descendant (cycle) → `Invalid parent`. The moved category gets the next sibling `sort` under the new parent. Returns `{ id, parentId, sort }`. For `POST /api/category/:id/move`.
|
||||
|
||||
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`pic.fullbinary`). Thumb always scaled to max 200×200 (`pic.thumbbinary`) via `sharp`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `categorypic`. Auth via `resolveCategoryAccess`. Errors: `Category not found`, `Not authorized`.
|
||||
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`storageDir/fullbinary`). Thumb always scaled to max 200×200 (`storageDir/thumbbinary`) via `wasm-image-optimization`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `categorypic`. Auth via `resolveCategoryAccess`. Errors: `Category not found`, `Not authorized`.
|
||||
|
||||
`addImage` returns:
|
||||
|
||||
@@ -601,7 +601,7 @@ Company-scoped POS terminals. Auth via selected company (`google_auth.selected_c
|
||||
| `orderImages` | `(googleAuthId: string, sku: string, { fullHashes: string[] })` | `{ fullHashes: string[] }` |
|
||||
| `delete` | `(googleAuthId: string, sku: string)` | `void` |
|
||||
|
||||
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`pic.fullbinary`). Thumb always scaled to max 200×200 (`pic.thumbbinary`) via `sharp`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `articlepic`. Auth like `getBySku`. Errors: `Article not found`, `Not authorized`.
|
||||
`addImage` accepts a full-size AVIF `Buffer`. Full image: if already AVIF and ≤1000×1000, stored as-is; otherwise scaled to max 1000×1000 (`storageDir/fullbinary`). Thumb always scaled to max 200×200 (`storageDir/thumbbinary`) via `wasm-image-optimization`. Assigns random `fullHash` (`pic.hash`, unique), computes `thumbhash`, links `articlepic`. Auth like `getBySku`. Errors: `Article not found`, `Not authorized`.
|
||||
|
||||
`addImage` returns `ImageResult`:
|
||||
|
||||
|
||||
@@ -378,10 +378,11 @@ const SCHEMA_TABLES = [
|
||||
['ordersummary', 'id PK, company_id->company, manifest_id->manifest UQ, order_number:s(100), order_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, paid_total:f=0, ordered_quantity:f=0, shipped_quantity:f=0, is_paid:b=0, is_shipped:b=0'],
|
||||
['package', 'id PK, shipping_id->shipping, entityaddress_id->entityaddress, number:s(100), type:s(100), state:s(100), details?:txt'],
|
||||
['payment', 'id PK, account_id->account, manifest_id->manifest, type:s(100), amount:f'],
|
||||
['pic', 'id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), source:txt, fullbinary:bin, thumbbinary:bin'],
|
||||
['pic', 'id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), smallthumbw:i, smallthumbh:i, smallthumbbinary:bin, source:txt'],
|
||||
['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)'],
|
||||
['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'],
|
||||
['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)'],
|
||||
|
||||
@@ -170,8 +170,8 @@ function addInferredEdges(tables, edges) {
|
||||
const match = column.name.match(/^(.+)_id$/i);
|
||||
if (!match) continue;
|
||||
|
||||
// Obituary tables store references to deleted entities, not active foreign keys.
|
||||
if (tableName.includes('obituary') || tableName.includes('tombstone')) continue;
|
||||
// Obituary and sync queue tables store polymorphic entity_id references, not active foreign keys to entity.
|
||||
if (tableName.includes('obituary') || tableName.includes('tombstone') || tableName.includes('syncqueue')) continue;
|
||||
|
||||
const candidate = match[1];
|
||||
if (!tables.has(candidate) || candidate === tableName) continue;
|
||||
@@ -390,7 +390,7 @@ const CLUSTERS = [
|
||||
tables: ['tax', 'price', 'account', 'payment'],
|
||||
},
|
||||
{ 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',
|
||||
label: 'AI & Models',
|
||||
|
||||
666
package-lock.json
generated
666
package-lock.json
generated
@@ -14,9 +14,9 @@
|
||||
"chalk": "^6.0.1",
|
||||
"dotenv": "^18.0.8",
|
||||
"pdfconvert-wasm": "^0.3.0",
|
||||
"sharp": "^0.35.5",
|
||||
"skelehash-wasm": "file:../skelehash-wasm",
|
||||
"thumbhash": "^0.1.1"
|
||||
"skelehash-wasm": "^0.1.1",
|
||||
"thumbhash": "^0.1.1",
|
||||
"wasm-image-optimization": "^2.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"better-sqlite3": "^13.0.3",
|
||||
@@ -32,13 +32,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"../skelehash-wasm": {
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"sharp": "^0.35.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure-rest/core-client": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.9.0.tgz",
|
||||
@@ -346,570 +339,12 @@
|
||||
"node": ">= 20.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hpcc-js/wasm-graphviz": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@hpcc-js/wasm-graphviz/-/wasm-graphviz-1.30.0.tgz",
|
||||
"integrity": "sha512-T20IKosOZuex2qV049fQokVJy2MWv8w9Md5V/4mRKCXDf49fBCR50VD1yBlPYHaQlHPsFGRdqXN//r59vZliWQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz",
|
||||
"integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz",
|
||||
"integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-freebsd-wasm32": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz",
|
||||
"integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz",
|
||||
"integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz",
|
||||
"integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz",
|
||||
"integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz",
|
||||
"integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz",
|
||||
"integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz",
|
||||
"integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz",
|
||||
"integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz",
|
||||
"integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz",
|
||||
"integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz",
|
||||
"integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz",
|
||||
"integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz",
|
||||
"integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz",
|
||||
"integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-riscv64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz",
|
||||
"integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz",
|
||||
"integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz",
|
||||
"integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz",
|
||||
"integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz",
|
||||
"integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz",
|
||||
"integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==",
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.11.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-webcontainers-wasm32": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz",
|
||||
"integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz",
|
||||
"integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz",
|
||||
"integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz",
|
||||
"integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@js-joda/core": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-6.3.0.tgz",
|
||||
@@ -1160,15 +595,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "18.0.9",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.9.tgz",
|
||||
@@ -1633,6 +1059,7 @@
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -1641,55 +1068,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.35.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz",
|
||||
"integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.1.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.35.5",
|
||||
"@img/sharp-darwin-x64": "0.35.5",
|
||||
"@img/sharp-freebsd-wasm32": "0.35.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.3.4",
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.4",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.4",
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.3.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.4",
|
||||
"@img/sharp-linux-arm": "0.35.5",
|
||||
"@img/sharp-linux-arm64": "0.35.5",
|
||||
"@img/sharp-linux-ppc64": "0.35.5",
|
||||
"@img/sharp-linux-riscv64": "0.35.5",
|
||||
"@img/sharp-linux-s390x": "0.35.5",
|
||||
"@img/sharp-linux-x64": "0.35.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.35.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.35.5",
|
||||
"@img/sharp-webcontainers-wasm32": "0.35.5",
|
||||
"@img/sharp-win32-arm64": "0.35.5",
|
||||
"@img/sharp-win32-ia32": "0.35.5",
|
||||
"@img/sharp-win32-x64": "0.35.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
@@ -1697,8 +1075,10 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/skelehash-wasm": {
|
||||
"resolved": "../skelehash-wasm",
|
||||
"link": true
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/skelehash-wasm/-/skelehash-wasm-0.1.1.tgz",
|
||||
"integrity": "sha512-5kC7dESMvQUdpLvRYfTc0oZ/Ff78bedUhqvYAGnVjs3agsS9PP8RJ/lGpol5sSKoYET/7KJbQ2UVSDTNpmDtTw==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
@@ -1751,7 +1131,7 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
@@ -1761,6 +1141,34 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wasm-image-optimization": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/wasm-image-optimization/-/wasm-image-optimization-2.0.13.tgz",
|
||||
"integrity": "sha512-MGaFZtdxMHZyaL3VUUcNkN8qccJcy33lcXoUIaegav/y1TiK6c2aALbKAjoe4bXHFI7nDl+5+TG0e9Y8j1/h9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "15.0.0",
|
||||
"worker-lib": "2.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"wasm-image-optimization": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/wasm-image-optimization/node_modules/commander": {
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
|
||||
"integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-lib": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/worker-lib/-/worker-lib-2.2.1.tgz",
|
||||
"integrity": "sha512-CauU4vz3vZUBG3A2CY93GY94KLhbH8JtPKtuvKu2w5G07cHkZ73QkTAJ/lEHDL6arT5WDu1qpU++T0+kGDgSCg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wsl-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.1.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"init-schema": "node scripts/init-schema.js",
|
||||
"purge-db": "node scripts/purge-db.js",
|
||||
"reset-db": "node scripts/reset-db.js",
|
||||
"downgrade": "node scripts/downgrade.js",
|
||||
"test": "node --test \"test/*.test.js\"",
|
||||
"test:api": "node scripts/test-api.js",
|
||||
"test:benchmark": "node scripts/benchmark.js"
|
||||
@@ -30,9 +31,9 @@
|
||||
"chalk": "^6.0.1",
|
||||
"dotenv": "^18.0.8",
|
||||
"pdfconvert-wasm": "^0.3.0",
|
||||
"sharp": "^0.35.5",
|
||||
"skelehash-wasm": "file:../skelehash-wasm",
|
||||
"thumbhash": "^0.1.1"
|
||||
"skelehash-wasm": "^0.1.1",
|
||||
"thumbhash": "^0.1.1",
|
||||
"wasm-image-optimization": "^2.0.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"better-sqlite3": "^12.0.0",
|
||||
|
||||
274
schema-graph.dot
274
schema-graph.dot
File diff suppressed because one or more lines are too long
6554
schema-graph.svg
6554
schema-graph.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 408 KiB After Width: | Height: | Size: 459 KiB |
@@ -1,35 +1,21 @@
|
||||
import sharp from 'sharp';
|
||||
import { optimizeImage } from 'wasm-image-optimization';
|
||||
|
||||
/**
|
||||
* Creates sample PNG image buffer.
|
||||
*/
|
||||
async function makeSampleImage(width = 200, height = 200, color = { r: 50, g: 120, b: 220 }) {
|
||||
return sharp({
|
||||
create: {
|
||||
width,
|
||||
height,
|
||||
channels: 4,
|
||||
background: { ...color, alpha: 1 },
|
||||
},
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect width="${width}" height="${height}" fill="rgb(${color.r},${color.g},${color.b})"/></svg>`;
|
||||
const res = await optimizeImage({ image: svg, format: 'png' });
|
||||
return Buffer.from(res.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates sample AVIF thumbnail buffer.
|
||||
*/
|
||||
async function makeSampleThumb(width = 64, height = 64) {
|
||||
return sharp({
|
||||
create: {
|
||||
width,
|
||||
height,
|
||||
channels: 4,
|
||||
background: { r: 200, g: 60, b: 60, alpha: 1 },
|
||||
},
|
||||
})
|
||||
.avif()
|
||||
.toBuffer();
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><rect width="${width}" height="${height}" fill="rgb(200,60,60)"/></svg>`;
|
||||
const res = await optimizeImage({ image: svg, format: 'avif' });
|
||||
return Buffer.from(res.data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
54
scripts/downgrade.js
Normal file
54
scripts/downgrade.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Downgrades the database schema to a target version.
|
||||
*
|
||||
* npm run downgrade -- <targetVersion>
|
||||
*
|
||||
* Provider from env: DB_PROVIDER=sqlite (+ SQLITE_PATH) or mssql (MSSQL_*, default).
|
||||
* Runs src/providers/<provider>/downgrade/<from>_to_<to>.js one step at a time,
|
||||
* each in its own transaction. Not part of the server — the server only upgrades.
|
||||
* Note: starting a server whose code knows a higher version upgrades again.
|
||||
*/
|
||||
import 'dotenv/config';
|
||||
|
||||
const [rawTarget] = process.argv.slice(2);
|
||||
const target = Number(rawTarget);
|
||||
if (!rawTarget || !Number.isInteger(target) || target < 1) {
|
||||
console.error('Usage: npm run downgrade -- <targetVersion>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const providerName = process.env.DB_PROVIDER || (process.env.SQLITE_PATH ? 'sqlite' : 'mssql');
|
||||
let provider;
|
||||
|
||||
if (providerName === 'sqlite' || providerName === 'better-sqlite3') {
|
||||
if (!process.env.SQLITE_PATH) {
|
||||
console.error('SQLITE_PATH is required for DB_PROVIDER=sqlite');
|
||||
process.exit(1);
|
||||
}
|
||||
const { BetterSqlite3Provider } = await import('../src/providers/sqlite3/index.js');
|
||||
provider = new BetterSqlite3Provider({ filename: process.env.SQLITE_PATH, fileMustExist: true });
|
||||
} else if (providerName === 'mssql') {
|
||||
const { MssqlProvider } = await import('../src/providers/mssql/index.js');
|
||||
provider = new MssqlProvider({
|
||||
server: process.env.MSSQL_SERVER,
|
||||
database: process.env.MSSQL_DATABASE,
|
||||
user: process.env.MSSQL_USER,
|
||||
password: process.env.MSSQL_PASSWORD,
|
||||
options: { encrypt: false, trustServerCertificate: true },
|
||||
});
|
||||
} else {
|
||||
console.error(`Unknown DB_PROVIDER: ${providerName}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const before = await provider.getSchemaVersion();
|
||||
const steps = await provider.downgradeSchema(target);
|
||||
console.log(`Schema ${providerName}: ${before} -> ${target} (${steps} step(s))`);
|
||||
} catch (err) {
|
||||
console.error(`Downgrade failed: ${err.message}`);
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
await provider.close();
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
import Database from 'better-sqlite3';
|
||||
import { MssqlProvider } from '../src/providers/mssql/index.js';
|
||||
|
||||
async function migrateSqlite(sqlitePath) {
|
||||
console.log(`Migrating SQLite database at: ${sqlitePath}`);
|
||||
const db = new Database(sqlitePath);
|
||||
|
||||
const columns = db.pragma('table_info("manifest")');
|
||||
const hasCreatedAt = columns.some((c) => c.name === 'created_at');
|
||||
if (hasCreatedAt) {
|
||||
console.log('SQLite manifest already has created_at column.');
|
||||
db.close();
|
||||
return;
|
||||
}
|
||||
|
||||
db.pragma('foreign_keys = OFF');
|
||||
db.exec(`
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE manifest_new (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
"number" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
entity_id INTEGER,
|
||||
external_number TEXT,
|
||||
"date" TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL
|
||||
);
|
||||
INSERT INTO manifest_new (id, company_id, "number", "type", entity_id, external_number, "date", created_at)
|
||||
SELECT id, company_id, "number", "type", entity_id, external_number, "date", "date"
|
||||
FROM manifest;
|
||||
DROP TABLE manifest;
|
||||
ALTER TABLE manifest_new RENAME TO manifest;
|
||||
COMMIT;
|
||||
`);
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
const fkErrors = db.pragma('foreign_key_check');
|
||||
if (fkErrors.length > 0) {
|
||||
console.error('Foreign key check failed after SQLite migration:', fkErrors);
|
||||
} else {
|
||||
console.log('SQLite migration completed successfully.');
|
||||
}
|
||||
|
||||
console.log('Updated SQLite manifest columns:', db.pragma('table_info("manifest")'));
|
||||
console.log('Updated SQLite manifest sample row:', db.prepare('SELECT * FROM manifest LIMIT 1').get());
|
||||
db.close();
|
||||
}
|
||||
|
||||
async function migrateMssql() {
|
||||
console.log('Migrating MSSQL database...');
|
||||
const config = {
|
||||
server: process.env.MSSQL_SERVER,
|
||||
database: process.env.MSSQL_DATABASE,
|
||||
user: process.env.MSSQL_USER,
|
||||
password: process.env.MSSQL_PASSWORD,
|
||||
options: {
|
||||
encrypt: false,
|
||||
trustServerCertificate: true,
|
||||
},
|
||||
};
|
||||
|
||||
const provider = new MssqlProvider(config);
|
||||
try {
|
||||
const pool = await provider.ensurePool();
|
||||
const checkRes = await pool.request().query(`
|
||||
SELECT * FROM sys.columns
|
||||
WHERE object_id = OBJECT_ID(N'manifest') AND name = 'created_at'
|
||||
`);
|
||||
|
||||
if (checkRes.recordset.length > 0) {
|
||||
console.log('MSSQL manifest already has created_at column.');
|
||||
} else {
|
||||
await pool.request().query(`
|
||||
ALTER TABLE manifest ADD created_at DATETIME2 NOT NULL CONSTRAINT DF_manifest_created_at DEFAULT SYSUTCDATETIME();
|
||||
`);
|
||||
await pool.request().query(`
|
||||
UPDATE manifest SET created_at = [date];
|
||||
`);
|
||||
console.log('MSSQL migration completed successfully.');
|
||||
}
|
||||
|
||||
const colsRes = await pool.request().query(`
|
||||
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = 'manifest'
|
||||
`);
|
||||
console.log('Updated MSSQL manifest columns:', colsRes.recordset);
|
||||
const sampleRes = await pool.request().query(`
|
||||
SELECT TOP 1 * FROM manifest
|
||||
`);
|
||||
console.log('Updated MSSQL manifest sample row:', sampleRes.recordset[0]);
|
||||
await provider.close();
|
||||
} catch (err) {
|
||||
console.error('MSSQL migration failed:', err);
|
||||
await provider.close();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
await migrateSqlite('/home/seb/src/dashback/data/dash.db');
|
||||
await migrateMssql();
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error('Migration failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,105 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
import Database from 'better-sqlite3';
|
||||
import { MssqlProvider } from '../src/providers/mssql/index.js';
|
||||
|
||||
async function migrateSqlite(sqlitePath) {
|
||||
console.log(`Migrating SQLite database at: ${sqlitePath}`);
|
||||
const db = new Database(sqlitePath);
|
||||
|
||||
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'printer'").get();
|
||||
if (!row) {
|
||||
console.log('Printer table does not exist in SQLite.');
|
||||
db.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (row.sql.includes("'ipp'")) {
|
||||
console.log("SQLite printer table already includes 'ipp' in CHECK constraint.");
|
||||
db.close();
|
||||
return;
|
||||
}
|
||||
|
||||
db.pragma('foreign_keys = OFF');
|
||||
db.exec(`
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE printer_new (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK (type IN ('esc/pos', 'zpl', 'ipp')),
|
||||
model TEXT,
|
||||
serial TEXT,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
"desc" TEXT,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
INSERT INTO printer_new (id, company_id, address, type, model, serial, sort, "desc")
|
||||
SELECT id, company_id, address, type, model, serial, sort, "desc"
|
||||
FROM printer;
|
||||
DROP TABLE printer;
|
||||
ALTER TABLE printer_new RENAME TO printer;
|
||||
COMMIT;
|
||||
`);
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
const fkErrors = db.pragma('foreign_key_check');
|
||||
if (fkErrors.length > 0) {
|
||||
console.error('Foreign key check failed after SQLite migration:', fkErrors);
|
||||
} else {
|
||||
console.log('SQLite migration completed successfully.');
|
||||
}
|
||||
|
||||
const updatedRow = db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'printer'").get();
|
||||
console.log('Updated SQLite printer table SQL:', updatedRow?.sql);
|
||||
db.close();
|
||||
}
|
||||
|
||||
async function migrateMssql() {
|
||||
console.log('Migrating MSSQL database...');
|
||||
const config = {
|
||||
server: process.env.MSSQL_SERVER,
|
||||
database: process.env.MSSQL_DATABASE,
|
||||
user: process.env.MSSQL_USER,
|
||||
password: process.env.MSSQL_PASSWORD,
|
||||
options: {
|
||||
encrypt: false,
|
||||
trustServerCertificate: true,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const provider = new MssqlProvider(config);
|
||||
const pool = await provider.ensurePool();
|
||||
const tableRes = await pool.request().query(`
|
||||
SELECT * FROM sys.tables WHERE name = 'printer'
|
||||
`);
|
||||
if (tableRes.recordset.length === 0) {
|
||||
console.log('Printer table does not exist in MSSQL.');
|
||||
await provider.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop old CK_printer_type constraint if exists and add new one
|
||||
await pool.request().query(`
|
||||
IF EXISTS (SELECT * FROM sys.check_constraints WHERE name = 'CK_printer_type')
|
||||
BEGIN
|
||||
ALTER TABLE printer DROP CONSTRAINT CK_printer_type;
|
||||
END
|
||||
ALTER TABLE printer ADD CONSTRAINT CK_printer_type CHECK (type IN ('esc/pos', 'zpl', 'ipp'));
|
||||
`);
|
||||
console.log('MSSQL migration completed successfully.');
|
||||
await provider.close();
|
||||
} catch (err) {
|
||||
console.warn('MSSQL migration skipped/failed (possibly server not running):', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
await migrateSqlite('/home/seb/src/dashback/data/dash.db');
|
||||
await migrateMssql();
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error('Migration failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import sharp from 'sharp';
|
||||
import { optimizeImage } from 'wasm-image-optimization';
|
||||
import LibDashStore from '../src/index.js';
|
||||
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
|
||||
|
||||
@@ -42,16 +42,9 @@ async function run() {
|
||||
assert.ok(val.id > 0);
|
||||
|
||||
// Create a 100x100 test image
|
||||
const imageBuffer = await sharp({
|
||||
create: {
|
||||
width: 100,
|
||||
height: 100,
|
||||
channels: 4,
|
||||
background: { r: 25, g: 25, b: 112, alpha: 1 },
|
||||
},
|
||||
})
|
||||
.avif()
|
||||
.toBuffer();
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="rgb(25,25,112)"/></svg>`;
|
||||
const encoded = await optimizeImage({ image: svg, format: 'avif' });
|
||||
const imageBuffer = Buffer.from(encoded.data);
|
||||
|
||||
console.log('1. Adding image to attribute value...');
|
||||
const addResult = await store.attributevalue.addImage(googleAuthId, val.id, imageBuffer);
|
||||
|
||||
92
scripts/test-companyuser-roles.js
Normal file
92
scripts/test-companyuser-roles.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
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 open(tempDir) {
|
||||
const provider = new BetterSqlite3Provider({ filename: join(tempDir, 'test.db'), storageDir: join(tempDir, 'storage') });
|
||||
await provider.initialize();
|
||||
return { provider, store: await new LibDashStore(provider) };
|
||||
}
|
||||
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'test-cu-roles-'));
|
||||
try {
|
||||
console.log('--- Testing companyuser roles + autoinvite ---');
|
||||
let { provider, store } = await open(tempDir);
|
||||
|
||||
const adminId = await store.google_auth.add('admin@example.com');
|
||||
const userId = await store.google_auth.add('user@example.com');
|
||||
const company = await store.company.create({ name: 'Roles Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId: adminId });
|
||||
await store.google_auth.setSelectedCompany(adminId, company.id);
|
||||
|
||||
// creator is admin
|
||||
let members = await store.companyuser.list(adminId);
|
||||
assert.deepEqual(members.map((m) => [m.email, m.role]), [['admin@example.com', 'admin']]);
|
||||
assert.equal((await store.company.list(adminId))[0].role, 'admin');
|
||||
|
||||
// non-member cannot select the company
|
||||
await assert.rejects(store.google_auth.setSelectedCompany(userId, company.id), /Not authorized/);
|
||||
|
||||
// admin adds existing user
|
||||
await assert.rejects(store.companyuser.addByEmail(adminId, { email: 'nobody@example.com' }), /User not found/);
|
||||
await assert.rejects(store.companyuser.addByEmail(adminId, { email: 'user@example.com', role: 'root' }), /Invalid role/);
|
||||
const added = await store.companyuser.addByEmail(adminId, { email: 'User@Example.com' });
|
||||
assert.equal(added.role, 'user');
|
||||
await assert.rejects(store.companyuser.addByEmail(adminId, { email: 'user@example.com' }), /already in company/);
|
||||
|
||||
// user can now select it, but is not admin
|
||||
await store.google_auth.setSelectedCompany(userId, company.id);
|
||||
await assert.rejects(store.companyuser.list(userId), /Not authorized/);
|
||||
await assert.rejects(store.companyuser.addByEmail(userId, { email: 'admin@example.com' }), /Not authorized/);
|
||||
await assert.rejects(store.companyuser.setRole(userId, added.id, 'admin'), /Not authorized/);
|
||||
await assert.rejects(store.companyuser.remove(userId, added.id), /Not authorized/);
|
||||
await assert.rejects(store.autoinvite.add(userId, { email: 'x@example.com' }), /Not authorized/);
|
||||
|
||||
// last admin guard
|
||||
const adminCu = members[0].id;
|
||||
await assert.rejects(store.companyuser.setRole(adminId, adminCu, 'user'), /last admin/);
|
||||
await assert.rejects(store.companyuser.remove(adminId, adminCu), /last admin/);
|
||||
await store.companyuser.setRole(adminId, added.id, 'admin');
|
||||
await store.companyuser.setRole(adminId, adminCu, 'user');
|
||||
await store.companyuser.setRole(userId, adminCu, 'admin');
|
||||
await store.companyuser.setRole(userId, added.id, 'user');
|
||||
await assert.rejects(store.companyuser.setRole(adminId, 99999, 'user'), /Company user not found/);
|
||||
|
||||
// autoinvite
|
||||
await assert.rejects(store.autoinvite.add(adminId, { email: 'user@example.com' }), /already registered/);
|
||||
await assert.rejects(store.autoinvite.add(adminId, { email: 'bad' }), /Invalid email/);
|
||||
await store.autoinvite.add(adminId, { email: 'New@Example.com', role: 'admin' });
|
||||
await assert.rejects(store.autoinvite.add(adminId, { email: 'new@example.com' }), /already exists/);
|
||||
await store.autoinvite.add(adminId, { email: 'gone@example.com' });
|
||||
assert.equal((await store.autoinvite.list(adminId)).length, 2);
|
||||
await store.autoinvite.remove(adminId, (await store.autoinvite.list(adminId)).find((i) => i.email === 'gone@example.com').id);
|
||||
await assert.rejects(store.autoinvite.remove(adminId, 99999), /Invitation not found/);
|
||||
|
||||
const newId = await store.google_auth.add('new@example.com');
|
||||
assert.deepEqual(await store.autoinvite.list(adminId), []);
|
||||
await store.google_auth.setSelectedCompany(newId, company.id);
|
||||
members = await store.companyuser.list(adminId);
|
||||
assert.equal(members.find((m) => m.email === 'new@example.com').role, 'admin');
|
||||
|
||||
// remove clears selection
|
||||
const userCu = members.find((m) => m.email === 'user@example.com');
|
||||
await store.companyuser.remove(adminId, userCu.id);
|
||||
assert.equal((await store.google_auth.getById(userId)).selectedCompanyId, null);
|
||||
|
||||
// downgrade → upgrade through the migration engine
|
||||
await provider.close?.();
|
||||
const env = { ...process.env, DB_PROVIDER: 'sqlite', SQLITE_PATH: join(tempDir, 'test.db') };
|
||||
execFileSync('node', ['scripts/downgrade.js', '5'], { env, stdio: 'inherit' });
|
||||
({ provider, store } = await open(tempDir).catch((e) => { throw e; }));
|
||||
// opening upgrades to latest again; roles reset to admin for all members
|
||||
const rows = (await provider.query('SELECT role FROM companyuser')).rows;
|
||||
assert.ok(rows.length > 0 && rows.every((r) => r.role === 'admin'));
|
||||
assert.deepEqual((await provider.query('SELECT * FROM autoinvite')).rows, []);
|
||||
await provider.close?.();
|
||||
console.log('OK');
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
145
scripts/test-delivery-link-check.js
Normal file
145
scripts/test-delivery-link-check.js
Normal file
@@ -0,0 +1,145 @@
|
||||
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 Delivery Link Check & Sync in dashdbschema ---');
|
||||
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'test-delivery-link-check-'));
|
||||
const dbFile = join(tempDir, 'test.db');
|
||||
const storageDir = join(tempDir, 'storage');
|
||||
|
||||
const provider = new BetterSqlite3Provider({
|
||||
filename: dbFile,
|
||||
storageDir,
|
||||
});
|
||||
|
||||
await provider.initialize();
|
||||
const store = await new LibDashStore(provider);
|
||||
|
||||
try {
|
||||
const googleAuthId = await store.google_auth.add('link-check-test@example.com');
|
||||
const company = await store.company.create({
|
||||
name: 'Link Check Corp',
|
||||
iso639: 'de',
|
||||
taxrate: 19,
|
||||
reducedtaxrate: 7,
|
||||
googleAuthId,
|
||||
});
|
||||
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||
|
||||
await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
|
||||
|
||||
const supplier = await store.supplier.create(googleAuthId, {
|
||||
name: 'eks',
|
||||
});
|
||||
|
||||
const art = await store.article.createUnderParent({
|
||||
googleAuthId,
|
||||
parentId: company.rootCategoryId,
|
||||
name: 'Club Mate 0,5l',
|
||||
track_stock: 1,
|
||||
});
|
||||
|
||||
// Assign supplier with default pu=1, without EK
|
||||
await store.articlesupplier.create(googleAuthId, art.sku, {
|
||||
entityId: supplier.id,
|
||||
sku: 'mate',
|
||||
pu: 1,
|
||||
});
|
||||
|
||||
let asup = await store.articlesupplier.listByArticleSku(googleAuthId, art.sku);
|
||||
assert.equal(asup.length, 1);
|
||||
assert.equal(asup[0].pu, 1);
|
||||
assert.equal(asup[0].price, null);
|
||||
|
||||
// Create delivery with manifest line: sku: 'mate', pu: 20, net: 12
|
||||
const delivery = await store.delivery.create(googleAuthId, {
|
||||
date: '2026-10-25T10:00:00Z',
|
||||
externalNumber: 'LS-000001',
|
||||
supplierId: supplier.id,
|
||||
lines: [
|
||||
{ sku: 'mate', name: 'Club Mate 0,5l', pu: 20, amount: 2, net: 12, taxrate: 19 },
|
||||
],
|
||||
});
|
||||
|
||||
assert(delivery.id > 0);
|
||||
assert.equal(delivery.lines[0].pu, 20);
|
||||
|
||||
// Verify articlesupplier link was updated!
|
||||
asup = await store.articlesupplier.listByArticleSku(googleAuthId, art.sku);
|
||||
assert.equal(asup.length, 1);
|
||||
assert.equal(asup[0].pu, 20, 'VPE in articlesupplier must be synced to 20');
|
||||
assert.equal(asup[0].price.net, 12, 'Net price in articlesupplier must be synced to 12');
|
||||
assert.equal(asup[0].price.taxRate, 19, 'Tax rate in articlesupplier must be synced to 19');
|
||||
|
||||
// Verify article history
|
||||
const history = await store.article.getHistoryBySku(googleAuthId, art.sku);
|
||||
const historyTexts = (Array.isArray(history) ? history : history.history).map((h) => h.text);
|
||||
console.log('Article history entries:', historyTexts);
|
||||
|
||||
assert(
|
||||
historyTexts.some((t) => t.includes('Lieferant "eks" aktualisiert (VPE: 20, EK: € 12,00, MwSt.: 19%)')),
|
||||
'Must record history entry for supplier update with VPE, EK, and MwSt'
|
||||
);
|
||||
|
||||
// Now update delivery with new line pu: 24, net: 11.50, and taxrate: 7
|
||||
await store.delivery.update(googleAuthId, delivery.id, {
|
||||
lines: [
|
||||
{ sku: 'mate', name: 'Club Mate 0,5l', pu: 24, amount: 3, net: 11.5, taxrate: 7 },
|
||||
],
|
||||
});
|
||||
|
||||
asup = await store.articlesupplier.listByArticleSku(googleAuthId, art.sku);
|
||||
assert.equal(asup[0].pu, 24, 'VPE in articlesupplier must be synced to 24 on update');
|
||||
assert.equal(asup[0].price.net, 11.5, 'Net price in articlesupplier must be synced to 11.5 on update');
|
||||
assert.equal(asup[0].price.taxRate, 7, 'Tax rate in articlesupplier must be synced to 7 on update');
|
||||
|
||||
const historyAfterUpdate = await store.article.getHistoryBySku(googleAuthId, art.sku);
|
||||
const historyAfterTexts = (Array.isArray(historyAfterUpdate) ? historyAfterUpdate : historyAfterUpdate.history).map((h) => h.text);
|
||||
console.log('Article history entries after delivery update:', historyAfterTexts);
|
||||
assert(
|
||||
historyAfterTexts.some((t) => t.includes('Lieferant "eks" aktualisiert (VPE: 24, EK: € 11,50, MwSt.: 7%)')),
|
||||
'Must record history entry for supplier update on delivery update'
|
||||
);
|
||||
|
||||
// Now test booking the delivery with amount: 3, pu: 24
|
||||
// Should add 3 * 24 = 72 to stock and record clear history!
|
||||
await store.delivery.book(googleAuthId, delivery.id);
|
||||
|
||||
const artDetail = await store.article.getBySku(googleAuthId, art.sku);
|
||||
const totalStock = (artDetail.article.warehouses || []).reduce((acc, w) => acc + (w.stock || 0), 0);
|
||||
console.log('Article stock after booking delivery with amount 3, pu 24:', totalStock);
|
||||
assert.equal(totalStock, 72, 'Physical warehouse stock must be amount * pu (3 * 24 = 72)');
|
||||
|
||||
const historyAfterBooking = await store.article.getHistoryBySku(googleAuthId, art.sku);
|
||||
const bookingTexts = (Array.isArray(historyAfterBooking) ? historyAfterBooking : historyAfterBooking.history).map((h) => h.text);
|
||||
console.log('Article history entries after delivery booking:', bookingTexts);
|
||||
|
||||
assert(
|
||||
bookingTexts.some((t) => t.includes('Lager: "Hauptlager"') && t.includes('Menge: 72 (3 x VPE 24)')),
|
||||
'Warehouse history must clearly show Menge: 72 (3 x VPE 24)'
|
||||
);
|
||||
assert(
|
||||
bookingTexts.some((t) => t.includes('Lieferant: "eks"') && t.includes('Menge: 3, VPE: 24')),
|
||||
'Acceptance history must clearly show Menge: 3, VPE: 24'
|
||||
);
|
||||
|
||||
console.log('✓ Delivery link check and sync passed successfully!');
|
||||
} finally {
|
||||
try {
|
||||
provider.close();
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
// ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
118
scripts/test-printeragent.js
Normal file
118
scripts/test-printeragent.js
Normal file
@@ -0,0 +1,118 @@
|
||||
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';
|
||||
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'dash-printeragent-test-'));
|
||||
const provider = new BetterSqlite3Provider({ filename: join(tempDir, 'test.db'), storageDir: join(tempDir, 'storage') });
|
||||
await provider.initialize();
|
||||
const store = await new LibDashStore(provider);
|
||||
|
||||
try {
|
||||
const googleAuthId = await store.google_auth.add('printeragent-tester@example.com');
|
||||
const company = await store.company.create({ name: 'PA GmbH', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId });
|
||||
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||
|
||||
assert.deepEqual(await store.printeragent.list(googleAuthId), []);
|
||||
await assert.rejects(store.printeragent.create(googleAuthId, { name: ' ', pairingpin: '1' }), /Invalid printeragent/);
|
||||
|
||||
const a = await store.printeragent.create(googleAuthId, { name: ' Bar ', pairingpin: '123456', sort: 5 });
|
||||
assert.equal(a.name, 'Bar');
|
||||
assert.equal(a.pairingpin, '123456');
|
||||
assert.equal(a.token, null);
|
||||
assert.equal(a.sort, 5);
|
||||
assert.equal(a.companyId, company.id);
|
||||
assert.ok(a.updatedAt);
|
||||
|
||||
// findByPairingpin
|
||||
const foundByPin = await store.printeragent.findByPairingpin('123456');
|
||||
assert.equal(foundByPin.id, a.id);
|
||||
assert.equal(await store.printeragent.findByPairingpin('999999'), null);
|
||||
|
||||
// pair
|
||||
const paired = await store.printeragent.pair(a.id, {
|
||||
token: 'test-token-123',
|
||||
agentversion: '1.0.0',
|
||||
});
|
||||
assert.equal(paired.token, 'test-token-123');
|
||||
assert.equal(paired.agentversion, '1.0.0');
|
||||
assert.equal(paired.pairingpin, null);
|
||||
assert.equal(paired.enabled, true);
|
||||
assert.ok(paired.pairedAt);
|
||||
|
||||
// findByToken
|
||||
const foundByToken = await store.printeragent.findByToken('test-token-123');
|
||||
assert.equal(foundByToken.id, a.id);
|
||||
assert.equal(foundByToken.enabled, true);
|
||||
assert.equal(await store.printeragent.findByToken('invalid-token'), null);
|
||||
|
||||
// touchLastseen
|
||||
await store.printeragent.touchLastseen(a.id, '1.0.1');
|
||||
const afterTouch = await store.printeragent.findByToken('test-token-123');
|
||||
assert.equal(afterTouch.agentversion, '1.0.1');
|
||||
|
||||
// update name, sort, and enabled
|
||||
const b = await store.printeragent.update(googleAuthId, { id: a.id, name: 'Kitchen', sort: 1, enabled: false });
|
||||
assert.equal(b.name, 'Kitchen');
|
||||
assert.equal(b.sort, 1);
|
||||
assert.equal(b.enabled, false);
|
||||
|
||||
// list
|
||||
const list = await store.printeragent.list(googleAuthId);
|
||||
assert.equal(list.length, 1);
|
||||
assert.equal(list[0].name, 'Kitchen');
|
||||
assert.equal(list[0].sort, 1);
|
||||
assert.equal(list[0].enabled, false);
|
||||
|
||||
// test agentprinter findOrCreate
|
||||
const ap1 = await store.agentprinter.findOrCreate(a.id, { type: 'zpl', model: 'ZD410-200dpi', serial: '50J203002218' });
|
||||
assert.equal(ap1.created, true);
|
||||
assert.equal(ap1.agentprinter.type, 'zpl');
|
||||
assert.equal(ap1.agentprinter.model, 'ZD410-200dpi');
|
||||
assert.equal(ap1.agentprinter.serial, '50J203002218');
|
||||
assert.equal(ap1.agentprinter.enabled, true);
|
||||
|
||||
// second call with same combo returns existing
|
||||
const ap1Again = await store.agentprinter.findOrCreate(a.id, { type: 'zpl', model: 'ZD410-200dpi', serial: '50J203002218' });
|
||||
assert.equal(ap1Again.created, false);
|
||||
assert.equal(ap1Again.agentprinter.id, ap1.agentprinter.id);
|
||||
|
||||
// create second printer
|
||||
const ap2 = await store.agentprinter.findOrCreate(a.id, { type: 'esc/pos', model: 'TM-T88VI', serial: 'SN999' });
|
||||
assert.equal(ap2.created, true);
|
||||
|
||||
// list agentprinters
|
||||
const apList = await store.agentprinter.list(googleAuthId, a.id);
|
||||
assert.equal(apList.length, 2);
|
||||
|
||||
// getById
|
||||
const apGet = await store.agentprinter.getById(googleAuthId, ap1.agentprinter.id);
|
||||
assert.equal(apGet.serial, '50J203002218');
|
||||
|
||||
// update agentprinter
|
||||
const apUpdated = await store.agentprinter.update(googleAuthId, { id: ap1.agentprinter.id, enabled: false, sort: 10 });
|
||||
assert.equal(apUpdated.enabled, false);
|
||||
assert.equal(apUpdated.sort, 10);
|
||||
|
||||
// order agentprinters
|
||||
const ordered = await store.agentprinter.order(googleAuthId, {
|
||||
printeragentId: a.id,
|
||||
agentprinterIds: [ap2.agentprinter.id, ap1.agentprinter.id],
|
||||
});
|
||||
assert.deepEqual(ordered.agentprinterIds, [ap2.agentprinter.id, ap1.agentprinter.id]);
|
||||
|
||||
// delete agentprinter
|
||||
await store.agentprinter.delete(googleAuthId, ap2.agentprinter.id);
|
||||
assert.equal((await store.agentprinter.list(googleAuthId, a.id)).length, 1);
|
||||
|
||||
// delete
|
||||
await store.printeragent.delete(googleAuthId, a.id);
|
||||
assert.equal(await store.printeragent.findByToken('test-token-123'), null);
|
||||
assert.deepEqual(await store.printeragent.list(googleAuthId), []);
|
||||
console.log('printeragent full test OK');
|
||||
} finally {
|
||||
await provider.close?.();
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, existsSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import sharp from 'sharp';
|
||||
import { optimizeImage } from 'wasm-image-optimization';
|
||||
import LibDashStore from '../src/index.js';
|
||||
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
|
||||
|
||||
@@ -72,16 +72,9 @@ async function runTests() {
|
||||
|
||||
const manifestId = delivery.id;
|
||||
const testContent = Buffer.from('Hello SQLite Storage Engine! PDF or text content test.');
|
||||
const testThumb = await sharp({
|
||||
create: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
channels: 4,
|
||||
background: { r: 255, g: 0, b: 0, alpha: 1 },
|
||||
},
|
||||
})
|
||||
.avif()
|
||||
.toBuffer();
|
||||
const thumbSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><rect width="64" height="64" fill="rgb(255,0,0)"/></svg>`;
|
||||
const testThumbRes = await optimizeImage({ image: thumbSvg, format: 'avif' });
|
||||
const testThumb = Buffer.from(testThumbRes.data);
|
||||
|
||||
const attachment = await store.manifestattachment.add(googleAuthId, manifestId, {
|
||||
name: 'test-doc.txt',
|
||||
@@ -202,16 +195,9 @@ async function runTests() {
|
||||
|
||||
console.log('Article track_stock tests passed!');
|
||||
|
||||
const sampleImage = await sharp({
|
||||
create: {
|
||||
width: 300,
|
||||
height: 200,
|
||||
channels: 4,
|
||||
background: { r: 0, g: 128, b: 255, alpha: 1 },
|
||||
},
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
const sampleSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200"><rect width="300" height="200" fill="rgb(0,128,255)"/></svg>`;
|
||||
const sampleImageRes = await optimizeImage({ image: sampleSvg, format: 'png' });
|
||||
const sampleImage = Buffer.from(sampleImageRes.data);
|
||||
|
||||
const picResult = await store.article.addImage(googleAuthId, article.sku, sampleImage);
|
||||
const fullHash = picResult.fullHash;
|
||||
@@ -223,10 +209,10 @@ async function runTests() {
|
||||
assert.equal(smallThumbRow.smallthumbw, 32);
|
||||
assert.equal(smallThumbRow.smallthumbh, 21);
|
||||
assert(Buffer.isBuffer(smallThumbRow.smallthumbbinary), 'Small thumb AVIF must be stored in the pic row');
|
||||
const smallThumbMetadata = await sharp(smallThumbRow.smallthumbbinary).metadata();
|
||||
assert.equal(smallThumbMetadata.format, 'heif');
|
||||
assert.equal(smallThumbMetadata.width, 32);
|
||||
assert.equal(smallThumbMetadata.height, 21);
|
||||
const smallThumbMetadata = await optimizeImage({ image: smallThumbRow.smallthumbbinary, format: 'none' });
|
||||
assert.equal(smallThumbMetadata.originalFormat, 'avif');
|
||||
assert.equal(smallThumbMetadata.originalWidth, 32);
|
||||
assert.equal(smallThumbMetadata.originalHeight, 21);
|
||||
|
||||
const brand = await store.brand.create(googleAuthId, { name: 'Small Thumb Test Brand' });
|
||||
await store.brand.addImage(googleAuthId, brand.id, sampleImage);
|
||||
|
||||
436
scripts/test-syncqueue.js
Normal file
436
scripts/test-syncqueue.js
Normal 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);
|
||||
});
|
||||
106
src/index.js
106
src/index.js
@@ -1,6 +1,13 @@
|
||||
import companyuserGetByEmail from './store/companyuser/getByEmail.js';
|
||||
import companyuserAdd from './store/companyuser/add.js';
|
||||
import companyuserDelete from './store/companyuser/delete.js';
|
||||
import companyuserList from './store/companyuser/list.js';
|
||||
import companyuserAddByEmail from './store/companyuser/addByEmail.js';
|
||||
import companyuserSetRole from './store/companyuser/setRole.js';
|
||||
import companyuserRemove from './store/companyuser/remove.js';
|
||||
import autoinviteAdd from './store/autoinvite/add.js';
|
||||
import autoinviteList from './store/autoinvite/list.js';
|
||||
import autoinviteRemove from './store/autoinvite/remove.js';
|
||||
import companyCreate from './store/company/create.js';
|
||||
import companyUpdate from './store/company/update.js';
|
||||
import companyDelete from './store/company/delete.js';
|
||||
@@ -18,6 +25,7 @@ import categoryOrder from './store/category/order.js';
|
||||
import categoryDelete from './store/category/delete.js';
|
||||
import categoryAddImage from './store/category/addImage.js';
|
||||
import categoryDeleteImage from './store/category/deleteImage.js';
|
||||
import categoryGenerateImage from './store/category/generateImage.js';
|
||||
import articleDelete from './store/article/delete.js';
|
||||
import articleDeleteImage from './store/article/deleteImage.js';
|
||||
import articleOrderImages from './store/article/orderImages.js';
|
||||
@@ -107,6 +115,20 @@ import orderCreateShipment from './store/order/createShipment.js';
|
||||
import orderUpdate from './store/order/update.js';
|
||||
import orderDelete from './store/order/delete.js';
|
||||
import orderRebuildSummaries from './store/order/rebuildSummaries.js';
|
||||
import returnList from './store/return/list.js';
|
||||
import returnGetById from './store/return/getById.js';
|
||||
import returnCreate from './store/return/create.js';
|
||||
import returnUpdate from './store/return/update.js';
|
||||
import returnDelete from './store/return/delete.js';
|
||||
import returnBook from './store/return/book.js';
|
||||
import returnRebuildSummaries from './store/return/rebuildSummaries.js';
|
||||
import retoureList from './store/retoure/list.js';
|
||||
import retoureGetById from './store/retoure/getById.js';
|
||||
import retoureCreate from './store/retoure/create.js';
|
||||
import retoureCreateShipment from './store/retoure/createShipment.js';
|
||||
import retoureUpdate from './store/retoure/update.js';
|
||||
import retoureDelete from './store/retoure/delete.js';
|
||||
import retoureRebuildSummaries from './store/retoure/rebuildSummaries.js';
|
||||
import posterminalList from './store/posterminal/list.js';
|
||||
import posterminalUpsert from './store/posterminal/upsert.js';
|
||||
import posterminalDelete from './store/posterminal/delete.js';
|
||||
@@ -128,11 +150,13 @@ import posterminalGetCompositeProductCount from './store/posterminal/getComposit
|
||||
import posterminalGetDeletedEntityCount from './store/posterminal/getDeletedEntityCount.js';
|
||||
import posterminalGetDeletedEntityList from './store/posterminal/getDeletedEntityList.js';
|
||||
import posterminalRecordObituary from './store/posterminal/recordObituary.js';
|
||||
import posterminalRecordSyncQueue from './store/posterminal/recordSyncQueue.js';
|
||||
import posterminalGetMaxOrderIdCount from './store/posterminal/getMaxOrderIdCount.js';
|
||||
import posterminalGetCategoryList from './store/posterminal/getCategoryList.js';
|
||||
import posterminalGetProductList from './store/posterminal/getProductList.js';
|
||||
import posterminalSetMaxKnownObituaryId from './store/posterminal/setMaxKnownObituaryId.js';
|
||||
import posterminalTouchLastseen from './store/posterminal/touchLastseen.js';
|
||||
import posterminalSetClientversion from './store/posterminal/setClientversion.js';
|
||||
import langCreate from './store/lang/create.js';
|
||||
import langDelete from './store/lang/delete.js';
|
||||
import langList from './store/lang/list.js';
|
||||
@@ -183,6 +207,26 @@ import printerList from './store/printer/list.js';
|
||||
import printerUpdate from './store/printer/update.js';
|
||||
import printerDelete from './store/printer/delete.js';
|
||||
import printerOrder from './store/printer/order.js';
|
||||
import printeragentList from './store/printeragent/list.js';
|
||||
import printeragentCreate from './store/printeragent/create.js';
|
||||
import printeragentUpdate from './store/printeragent/update.js';
|
||||
import printeragentDelete from './store/printeragent/delete.js';
|
||||
import printeragentFindByPairingpin from './store/printeragent/findByPairingpin.js';
|
||||
import printeragentFindByToken from './store/printeragent/findByToken.js';
|
||||
import printeragentPair from './store/printeragent/pair.js';
|
||||
import printeragentUpdateState from './store/printeragent/updateState.js';
|
||||
import printeragentTouchLastseen from './store/printeragent/touchLastseen.js';
|
||||
import agentprinterList from './store/agentprinter/list.js';
|
||||
import agentprinterGetById from './store/agentprinter/getById.js';
|
||||
import agentprinterFindOrCreate from './store/agentprinter/findOrCreate.js';
|
||||
import agentprinterUpdate from './store/agentprinter/update.js';
|
||||
import agentprinterOrder from './store/agentprinter/order.js';
|
||||
import agentprinterDelete from './store/agentprinter/delete.js';
|
||||
import printerjobCreate from './store/printerjob/create.js';
|
||||
import printerjobGetById from './store/printerjob/getById.js';
|
||||
import printerjobList from './store/printerjob/list.js';
|
||||
import printerjobUpdateState from './store/printerjob/updateState.js';
|
||||
import printerjobDelete from './store/printerjob/delete.js';
|
||||
import modelconversationCreate from './store/modelconversation/create.js';
|
||||
import modelconversationGet from './store/modelconversation/get.js';
|
||||
import modelconversationList from './store/modelconversation/list.js';
|
||||
@@ -297,6 +341,16 @@ export default class LibDashStore {
|
||||
getByEmail: companyuserGetByEmail(provider),
|
||||
add: companyuserAdd(provider),
|
||||
delete: companyuserDelete(provider),
|
||||
list: companyuserList(provider),
|
||||
addByEmail: companyuserAddByEmail(provider),
|
||||
setRole: companyuserSetRole(provider),
|
||||
remove: companyuserRemove(provider),
|
||||
};
|
||||
|
||||
this.autoinvite = {
|
||||
add: autoinviteAdd(provider),
|
||||
list: autoinviteList(provider),
|
||||
remove: autoinviteRemove(provider),
|
||||
};
|
||||
|
||||
this.company = {
|
||||
@@ -321,6 +375,7 @@ export default class LibDashStore {
|
||||
delete: categoryDelete(provider),
|
||||
addImage: categoryAddImage(provider),
|
||||
deleteImage: categoryDeleteImage(provider),
|
||||
generateImage: categoryGenerateImage(provider),
|
||||
};
|
||||
|
||||
this.article = {
|
||||
@@ -470,6 +525,26 @@ export default class LibDashStore {
|
||||
rebuildSummaries: orderRebuildSummaries(provider),
|
||||
};
|
||||
|
||||
this.return = {
|
||||
list: returnList(provider),
|
||||
getById: returnGetById(provider),
|
||||
create: returnCreate(provider),
|
||||
update: returnUpdate(provider),
|
||||
delete: returnDelete(provider),
|
||||
book: returnBook(provider),
|
||||
rebuildSummaries: returnRebuildSummaries(provider),
|
||||
};
|
||||
|
||||
this.retoure = {
|
||||
list: retoureList(provider),
|
||||
getById: retoureGetById(provider),
|
||||
create: retoureCreate(provider),
|
||||
createShipment: retoureCreateShipment(provider),
|
||||
update: retoureUpdate(provider),
|
||||
delete: retoureDelete(provider),
|
||||
rebuildSummaries: retoureRebuildSummaries(provider),
|
||||
};
|
||||
|
||||
this.posterminal = {
|
||||
list: posterminalList(provider),
|
||||
upsert: posterminalUpsert(provider),
|
||||
@@ -492,11 +567,13 @@ export default class LibDashStore {
|
||||
getDeletedEntityCount: posterminalGetDeletedEntityCount(provider),
|
||||
getDeletedEntityList: posterminalGetDeletedEntityList(provider),
|
||||
recordObituary: posterminalRecordObituary(provider),
|
||||
recordSyncQueue: posterminalRecordSyncQueue(provider),
|
||||
getMaxOrderIdCount: posterminalGetMaxOrderIdCount(provider),
|
||||
getCategoryList: posterminalGetCategoryList(provider),
|
||||
getProductList: posterminalGetProductList(provider),
|
||||
setMaxKnownObituaryId: posterminalSetMaxKnownObituaryId(provider),
|
||||
touchLastseen: posterminalTouchLastseen(provider),
|
||||
setClientversion: posterminalSetClientversion(provider),
|
||||
};
|
||||
|
||||
this.account = {
|
||||
@@ -597,6 +674,35 @@ export default class LibDashStore {
|
||||
order: printerOrder(provider),
|
||||
};
|
||||
|
||||
this.printeragent = {
|
||||
list: printeragentList(provider),
|
||||
create: printeragentCreate(provider),
|
||||
update: printeragentUpdate(provider),
|
||||
delete: printeragentDelete(provider),
|
||||
findByPairingpin: printeragentFindByPairingpin(provider),
|
||||
findByToken: printeragentFindByToken(provider),
|
||||
pair: printeragentPair(provider),
|
||||
updateState: printeragentUpdateState(provider),
|
||||
touchLastseen: printeragentTouchLastseen(provider),
|
||||
};
|
||||
|
||||
this.agentprinter = {
|
||||
list: agentprinterList(provider),
|
||||
getById: agentprinterGetById(provider),
|
||||
findOrCreate: agentprinterFindOrCreate(provider),
|
||||
update: agentprinterUpdate(provider),
|
||||
order: agentprinterOrder(provider),
|
||||
delete: agentprinterDelete(provider),
|
||||
};
|
||||
|
||||
this.printerjob = {
|
||||
create: printerjobCreate(provider),
|
||||
getById: printerjobGetById(provider),
|
||||
list: printerjobList(provider),
|
||||
updateState: printerjobUpdateState(provider),
|
||||
delete: printerjobDelete(provider),
|
||||
};
|
||||
|
||||
this.modelconversation = {
|
||||
create: modelconversationCreate(provider),
|
||||
get: modelconversationGet(provider),
|
||||
|
||||
15
src/providers/mssql/downgrade/2_to_1.js
Normal file
15
src/providers/mssql/downgrade/2_to_1.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Schema 2 → 1 (MSSQL): drops posterminal `sort`, `enabled`, `clientversion`.
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(2);
|
||||
|
||||
await query(`
|
||||
ALTER TABLE posterminal DROP CONSTRAINT DF_posterminal_sort, DF_posterminal_enabled;
|
||||
ALTER TABLE posterminal DROP COLUMN sort, enabled, clientversion;
|
||||
`);
|
||||
|
||||
await setVersion(1);
|
||||
}
|
||||
|
||||
11
src/providers/mssql/downgrade/3_to_2.js
Normal file
11
src/providers/mssql/downgrade/3_to_2.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Schema 3 → 2 (MSSQL): drops the `printeragent` table.
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(3);
|
||||
|
||||
await query(`DROP TABLE IF EXISTS printeragent;`);
|
||||
|
||||
await setVersion(2);
|
||||
}
|
||||
38
src/providers/mssql/downgrade/4_to_3.js
Normal file
38
src/providers/mssql/downgrade/4_to_3.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Schema 4 → 3 (MSSQL):
|
||||
* - Drops agentprinter table
|
||||
* - Removes enabled from printeragent
|
||||
* - Restores clientserial, clientmodel, clientstate in printeragent
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(4);
|
||||
|
||||
await query(`DROP TABLE IF EXISTS agentprinter;`);
|
||||
|
||||
await query(`
|
||||
IF OBJECT_ID(N'dbo.printeragent', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
IF COL_LENGTH('printeragent', 'enabled') IS NOT NULL
|
||||
BEGIN
|
||||
DECLARE @defName NVARCHAR(255);
|
||||
SELECT @defName = dc.name
|
||||
FROM sys.default_constraints dc
|
||||
JOIN sys.columns c ON c.default_object_id = dc.object_id
|
||||
WHERE dc.parent_object_id = OBJECT_ID(N'dbo.printeragent') AND c.name = 'enabled';
|
||||
IF @defName IS NOT NULL
|
||||
EXEC('ALTER TABLE printeragent DROP CONSTRAINT ' + @defName);
|
||||
ALTER TABLE printeragent DROP COLUMN enabled;
|
||||
END
|
||||
IF COL_LENGTH('printeragent', 'clientserial') IS NULL
|
||||
ALTER TABLE printeragent ADD clientserial NVARCHAR(255) NULL;
|
||||
IF COL_LENGTH('printeragent', 'clientmodel') IS NULL
|
||||
ALTER TABLE printeragent ADD clientmodel NVARCHAR(255) NULL;
|
||||
IF COL_LENGTH('printeragent', 'clientstate') IS NULL
|
||||
ALTER TABLE printeragent ADD clientstate NVARCHAR(255) NULL;
|
||||
END
|
||||
`);
|
||||
|
||||
await setVersion(3);
|
||||
}
|
||||
|
||||
13
src/providers/mssql/downgrade/5_to_4.js
Normal file
13
src/providers/mssql/downgrade/5_to_4.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Schema 5 → 4 (MSSQL):
|
||||
* - Drops printerjob table
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(5);
|
||||
|
||||
await query(`DROP TABLE IF EXISTS printerjob;`);
|
||||
|
||||
await setVersion(4);
|
||||
}
|
||||
|
||||
18
src/providers/mssql/downgrade/6_to_5.js
Normal file
18
src/providers/mssql/downgrade/6_to_5.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Schema 6 → 5 (MSSQL):
|
||||
* - Drops autoinvite table
|
||||
* - Drops companyuser.role
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(6);
|
||||
|
||||
await query(`DROP TABLE IF EXISTS autoinvite;`);
|
||||
|
||||
await query(`
|
||||
ALTER TABLE companyuser DROP CONSTRAINT CK_companyuser_role, DF_companyuser_role;
|
||||
`);
|
||||
await query(`ALTER TABLE companyuser DROP COLUMN role;`);
|
||||
|
||||
await setVersion(5);
|
||||
}
|
||||
@@ -4,8 +4,12 @@
|
||||
* { rows, rowsAffected, insertId }
|
||||
*/
|
||||
|
||||
import { join } from 'node:path';
|
||||
import { mkdirSync, promises as fsPromises } from 'node:fs';
|
||||
import sql from 'mssql';
|
||||
import { logSlowQuery } from '../provider.js';
|
||||
import { safeWrite, safeReadByHash, safeDeleteByHash } from '../storage.js';
|
||||
import { upgradeSchema, downgradeSchema, readSchemaVersion } from './migrate.js';
|
||||
|
||||
function normalizeRows(result) {
|
||||
const recordset = result?.recordset ?? [];
|
||||
@@ -16,13 +20,57 @@ function normalizeRows(result) {
|
||||
}
|
||||
|
||||
export class MssqlExecutor {
|
||||
/** @param {() => import('mssql').Request} requestFactory */
|
||||
constructor(requestFactory) {
|
||||
/**
|
||||
* @param {() => import('mssql').Request} requestFactory
|
||||
* @param {string|null} [storageDir=null]
|
||||
*/
|
||||
constructor(requestFactory, storageDir = null) {
|
||||
this.#requestFactory = requestFactory;
|
||||
this.storageDir = storageDir;
|
||||
this.dialect = 'mssql';
|
||||
}
|
||||
|
||||
#requestFactory;
|
||||
|
||||
async writePicFull(hash, buffer) {
|
||||
if (!this.storageDir || !hash) return;
|
||||
await safeWrite(join(this.storageDir, 'fullbinary', hash + '.avif'), buffer);
|
||||
}
|
||||
|
||||
async readPicFull(hash) {
|
||||
if (!this.storageDir || !hash) return null;
|
||||
return safeReadByHash(join(this.storageDir, 'fullbinary'), hash, '.avif');
|
||||
}
|
||||
|
||||
async writePicThumb(hash, buffer) {
|
||||
if (!this.storageDir || !hash) return;
|
||||
await safeWrite(join(this.storageDir, 'thumbbinary', hash + '.avif'), buffer);
|
||||
}
|
||||
|
||||
async readPicThumb(hash) {
|
||||
if (!this.storageDir || !hash) return null;
|
||||
return safeReadByHash(join(this.storageDir, 'thumbbinary'), hash, '.avif');
|
||||
}
|
||||
|
||||
async deletePic(hash) {
|
||||
if (!this.storageDir || !hash) return;
|
||||
await safeDeleteByHash(join(this.storageDir, 'fullbinary'), hash);
|
||||
await safeDeleteByHash(join(this.storageDir, 'thumbbinary'), hash);
|
||||
}
|
||||
|
||||
async purgeFiles() {
|
||||
if (!this.storageDir) return;
|
||||
for (const sub of ['fullbinary', 'thumbbinary']) {
|
||||
const dir = join(this.storageDir, sub);
|
||||
try {
|
||||
const files = await fsPromises.readdir(dir);
|
||||
await Promise.all(files.map((f) => fsPromises.unlink(join(dir, f))));
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('../provider.js').QueryResult>} */
|
||||
async query(sql, params = {}) {
|
||||
const start = performance.now();
|
||||
@@ -73,8 +121,9 @@ export class MssqlProvider {
|
||||
* Either an already-connected mssql ConnectionPool, or a plain mssql
|
||||
* config object — in which case the pool is created lazily on first use
|
||||
* (and closed by {@link MssqlProvider#close}).
|
||||
* @param {{ storageDir?: string|null }} [options]
|
||||
*/
|
||||
constructor(poolOrConfig) {
|
||||
constructor(poolOrConfig, { storageDir = null } = {}) {
|
||||
const isPool =
|
||||
poolOrConfig && typeof poolOrConfig.request === 'function';
|
||||
if (!poolOrConfig || (!isPool && !poolOrConfig.server)) {
|
||||
@@ -87,12 +136,50 @@ export class MssqlProvider {
|
||||
// a different object after a lazy connection would leave them with null.
|
||||
this.pool = isPool ? poolOrConfig : new sql.ConnectionPool(poolOrConfig);
|
||||
this.#config = isPool ? null : poolOrConfig;
|
||||
this.storageDir = storageDir || poolOrConfig?.storageDir || null;
|
||||
this.dialect = 'mssql';
|
||||
}
|
||||
|
||||
#config;
|
||||
#connecting;
|
||||
|
||||
async #tryCreateDatabaseIfMissing() {
|
||||
if (!this.#config?.database) return false;
|
||||
const targetDb = String(this.#config.database);
|
||||
if (targetDb.toLowerCase() === 'master') return false;
|
||||
|
||||
const masterConfig = {
|
||||
...this.#config,
|
||||
database: 'master',
|
||||
pool: { min: 0, max: 1, idleTimeoutMillis: 5000 },
|
||||
};
|
||||
|
||||
let masterPool;
|
||||
try {
|
||||
masterPool = await new sql.ConnectionPool(masterConfig).connect();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const checkReq = masterPool.request();
|
||||
checkReq.input('dbname', sql.NVarChar, targetDb);
|
||||
const res = await checkReq.query('SELECT DB_ID(@dbname) AS dbid');
|
||||
if (res.recordset[0]?.dbid != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const createReq = masterPool.request();
|
||||
createReq.input('dbname', sql.NVarChar, targetDb);
|
||||
await createReq.query(
|
||||
"DECLARE @sql NVARCHAR(MAX) = N'CREATE DATABASE ' + QUOTENAME(@dbname); EXEC sp_executesql @sql;"
|
||||
);
|
||||
return true;
|
||||
} finally {
|
||||
await masterPool.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying pool, creating and connecting it on first use
|
||||
* when the provider was constructed from a config object.
|
||||
@@ -101,26 +188,37 @@ export class MssqlProvider {
|
||||
ensurePool() {
|
||||
if (this.pool.connected) return Promise.resolve(this.pool);
|
||||
if (!this.#connecting) {
|
||||
this.#connecting = this.pool
|
||||
.connect()
|
||||
.then((pool) => {
|
||||
return pool;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.#connecting = undefined;
|
||||
this.#connecting = (async () => {
|
||||
try {
|
||||
return await this.pool.connect();
|
||||
} catch (err) {
|
||||
if (err.code === 'ELOGIN') {
|
||||
try {
|
||||
const created = await this.#tryCreateDatabaseIfMissing();
|
||||
if (created) {
|
||||
return await this.pool.connect();
|
||||
}
|
||||
} catch {
|
||||
// ignore creation error and fall through to rethrow original error
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
})().catch((err) => {
|
||||
this.#connecting = undefined;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return this.#connecting;
|
||||
}
|
||||
|
||||
makeExecutor(requestFactory = () => this.pool.request()) {
|
||||
return new MssqlExecutor(requestFactory);
|
||||
return new MssqlExecutor(requestFactory, this.storageDir);
|
||||
}
|
||||
|
||||
async #withPool(fn) {
|
||||
const pool = await this.ensurePool();
|
||||
return fn(new MssqlExecutor(() => pool.request()));
|
||||
return fn(new MssqlExecutor(() => pool.request(), this.storageDir));
|
||||
}
|
||||
|
||||
async query(sql, params = {}) {
|
||||
@@ -131,6 +229,45 @@ export class MssqlProvider {
|
||||
return this.#withPool((executor) => executor.exec(procName, options));
|
||||
}
|
||||
|
||||
async writePicFull(hash, buffer) {
|
||||
if (!this.storageDir || !hash) return;
|
||||
await safeWrite(join(this.storageDir, 'fullbinary', hash + '.avif'), buffer);
|
||||
}
|
||||
|
||||
async readPicFull(hash) {
|
||||
if (!this.storageDir || !hash) return null;
|
||||
return safeReadByHash(join(this.storageDir, 'fullbinary'), hash, '.avif');
|
||||
}
|
||||
|
||||
async writePicThumb(hash, buffer) {
|
||||
if (!this.storageDir || !hash) return;
|
||||
await safeWrite(join(this.storageDir, 'thumbbinary', hash + '.avif'), buffer);
|
||||
}
|
||||
|
||||
async readPicThumb(hash) {
|
||||
if (!this.storageDir || !hash) return null;
|
||||
return safeReadByHash(join(this.storageDir, 'thumbbinary'), hash, '.avif');
|
||||
}
|
||||
|
||||
async deletePic(hash) {
|
||||
if (!this.storageDir || !hash) return;
|
||||
await safeDeleteByHash(join(this.storageDir, 'fullbinary'), hash);
|
||||
await safeDeleteByHash(join(this.storageDir, 'thumbbinary'), hash);
|
||||
}
|
||||
|
||||
async purgeFiles() {
|
||||
if (!this.storageDir) return;
|
||||
for (const sub of ['fullbinary', 'thumbbinary']) {
|
||||
const dir = join(this.storageDir, sub);
|
||||
try {
|
||||
const files = await fsPromises.readdir(dir);
|
||||
await Promise.all(files.map((f) => fsPromises.unlink(join(dir, f))));
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `fn(runner)` inside a transaction. Commit on success,
|
||||
* rollback on error. `runner` has query/exec like the provider.
|
||||
@@ -149,6 +286,12 @@ export class MssqlProvider {
|
||||
runner.query = runner;
|
||||
runner.exec = (proc, opts) => executor.exec(proc, opts);
|
||||
runner.dialect = 'mssql';
|
||||
runner.writePicFull = executor.writePicFull.bind(executor);
|
||||
runner.readPicFull = executor.readPicFull.bind(executor);
|
||||
runner.writePicThumb = executor.writePicThumb.bind(executor);
|
||||
runner.readPicThumb = executor.readPicThumb.bind(executor);
|
||||
runner.deletePic = executor.deletePic.bind(executor);
|
||||
runner.purgeFiles = executor.purgeFiles.bind(executor);
|
||||
const result = await fn(runner, tx);
|
||||
await tx.commit();
|
||||
return result;
|
||||
@@ -162,8 +305,17 @@ export class MssqlProvider {
|
||||
}
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
/**
|
||||
* Initializes the MSSQL schema.
|
||||
* @param {((message: string) => void) | { log?: (message: string) => void }} [options]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize(options = {}) {
|
||||
const pool = await this.ensurePool();
|
||||
const log = typeof options === 'function' ? options : options?.log;
|
||||
// Versioned upgrades first, so schema.js never meets an outdated table shape.
|
||||
await upgradeSchema(this, log);
|
||||
|
||||
const { SCHEMA_SCRIPTS } = await import('./schema.js');
|
||||
for (const script of SCHEMA_SCRIPTS) {
|
||||
if (script && script.trim()) {
|
||||
@@ -199,20 +351,57 @@ export class MssqlProvider {
|
||||
ALTER TABLE category ADD updated_at DATETIME2 NOT NULL CONSTRAINT DF_category_updated_at DEFAULT SYSUTCDATETIME();
|
||||
END
|
||||
|
||||
IF OBJECT_ID(N'dbo.payment', N'U') IS NOT NULL AND COL_LENGTH('payment', 'created_at') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE payment ADD created_at DATETIME2 NOT NULL CONSTRAINT DF_payment_created_at DEFAULT SYSUTCDATETIME();
|
||||
END
|
||||
|
||||
IF OBJECT_ID(N'dbo.customergroup', N'U') IS NOT NULL AND COL_LENGTH('customergroup', 'updated_at') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE customergroup ADD updated_at DATETIME2 NOT NULL CONSTRAINT DF_customergroup_updated_at DEFAULT SYSUTCDATETIME();
|
||||
END
|
||||
|
||||
IF OBJECT_ID(N'dbo.printeragent', N'U') IS NOT NULL AND COL_LENGTH('printeragent', 'sort') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE printeragent ADD sort INT NOT NULL CONSTRAINT DF_printeragent_sort DEFAULT 0;
|
||||
END
|
||||
`);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (this.storageDir) {
|
||||
for (const sub of ['fullbinary', 'thumbbinary']) {
|
||||
mkdirSync(join(this.storageDir, sub), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async purge() {
|
||||
const pool = await this.ensurePool();
|
||||
const { buildPurgeSql } = await import('./purgeSql.js');
|
||||
await pool.request().query(buildPurgeSql());
|
||||
if (this.storageDir) {
|
||||
await this.purgeFiles();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current `meta.version`, or `null` if the database has none.
|
||||
* @returns {Promise<number|null>}
|
||||
*/
|
||||
async getSchemaVersion() {
|
||||
return readSchemaVersion((sql, params) => this.query(sql, params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs downgrade steps down to `target`. CLI only (`npm run downgrade`).
|
||||
* @param {number} target
|
||||
* @param {(message: string) => void} [log]
|
||||
* @returns {Promise<number>} Steps applied.
|
||||
*/
|
||||
async downgradeSchema(target, log) {
|
||||
return downgradeSchema(this, target, log);
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
126
src/providers/mssql/migrate.js
Normal file
126
src/providers/mssql/migrate.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* MSSQL schema versioning (see ../schemaMigrations.js).
|
||||
*
|
||||
* Step context: `{ query, getVersion, assertVersion, setVersion }` where
|
||||
* `query(sql, params)` runs inside the step's transaction (same contract as
|
||||
* `provider.query`). Note: a column added by `ALTER TABLE` is not visible to
|
||||
* later statements of the SAME batch — use separate `query()` calls.
|
||||
*/
|
||||
|
||||
import {
|
||||
BASE_SCHEMA_VERSION,
|
||||
assertStepResult,
|
||||
latestSchemaVersion,
|
||||
makeVersionContext,
|
||||
migrateSchema,
|
||||
parseSchemaVersion,
|
||||
} from '../schemaMigrations.js';
|
||||
|
||||
export const UPGRADE_DIR = new URL('./upgrade/', import.meta.url);
|
||||
export const DOWNGRADE_DIR = new URL('./downgrade/', import.meta.url);
|
||||
|
||||
/** Schema version a fresh database is created at. */
|
||||
export const SCHEMA_VERSION = latestSchemaVersion(UPGRADE_DIR);
|
||||
|
||||
/**
|
||||
* @typedef {(sql: string, params?: object) => Promise<{ rows: any[] }>} QueryFn
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reads `meta.version`, or `null` when the meta table/row is missing.
|
||||
* @param {QueryFn} query
|
||||
* @returns {Promise<number|null>}
|
||||
*/
|
||||
export async function readSchemaVersion(query) {
|
||||
const result = await query(`
|
||||
IF OBJECT_ID(N'dbo.meta', N'U') IS NOT NULL
|
||||
SELECT value FROM meta WHERE [key] = N'version'
|
||||
ELSE
|
||||
SELECT CAST(NULL AS NVARCHAR(MAX)) AS value WHERE 1 = 0
|
||||
`);
|
||||
const row = result.rows[0];
|
||||
return row ? parseSchemaVersion(row.value) : null;
|
||||
}
|
||||
|
||||
async function writeSchemaVersion(query, version) {
|
||||
await query(`
|
||||
UPDATE meta SET value = @version WHERE [key] = N'version';
|
||||
IF @@ROWCOUNT = 0 INSERT INTO meta ([key], value) VALUES (N'version', @version);
|
||||
`, { version: String(version) });
|
||||
}
|
||||
|
||||
async function hasAnyTable(query) {
|
||||
const result = await query(`SELECT TOP 1 1 AS found FROM sys.tables WHERE is_ms_shipped = 0`);
|
||||
return result.rows.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./index.js').MssqlProvider} provider
|
||||
*/
|
||||
function makeRunStep(provider) {
|
||||
return async (step, fn) => {
|
||||
await provider.transaction(async (runner) => {
|
||||
const query = (sql, params) => runner.query(sql, params);
|
||||
const ctx = {
|
||||
query,
|
||||
...makeVersionContext(step.file, () => readSchemaVersion(query), (v) => writeSchemaVersion(query, v)),
|
||||
};
|
||||
await fn(ctx);
|
||||
assertStepResult(step, await readSchemaVersion(query));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades an existing database to `SCHEMA_VERSION`. Must run BEFORE the
|
||||
* `schema.js` scripts so they never meet an outdated table shape.
|
||||
* Fresh (empty) databases are skipped — `INIT_META` stamps them.
|
||||
* A non-empty database without a version row is treated as version 1.
|
||||
* @param {import('./index.js').MssqlProvider} provider
|
||||
* @param {(message: string) => void} [log]
|
||||
*/
|
||||
export async function upgradeSchema(provider, log) {
|
||||
const query = (sql, params) => provider.query(sql, params);
|
||||
if (!(await hasAnyTable(query))) {
|
||||
log?.(`Fresh database detected; schema will initialize directly at version ${SCHEMA_VERSION}`);
|
||||
return 0;
|
||||
}
|
||||
let current = await readSchemaVersion(query);
|
||||
if (current === null) {
|
||||
log?.(`Existing database has no version row; setting baseline version ${BASE_SCHEMA_VERSION}`);
|
||||
await query(`
|
||||
IF OBJECT_ID(N'dbo.meta', N'U') IS NULL
|
||||
CREATE TABLE meta ([key] NVARCHAR(255) NOT NULL, value NVARCHAR(MAX) NOT NULL, CONSTRAINT PK_meta PRIMARY KEY ([key]));
|
||||
`);
|
||||
await writeSchemaVersion(query, BASE_SCHEMA_VERSION);
|
||||
current = BASE_SCHEMA_VERSION;
|
||||
}
|
||||
return migrateSchema({
|
||||
direction: 'upgrade',
|
||||
dirUrl: UPGRADE_DIR,
|
||||
current,
|
||||
target: SCHEMA_VERSION,
|
||||
runStep: makeRunStep(provider),
|
||||
log,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Downgrades the database to `target` (CLI only, never at server start).
|
||||
* @param {import('./index.js').MssqlProvider} provider
|
||||
* @param {number} target
|
||||
* @param {(message: string) => void} [log]
|
||||
*/
|
||||
export async function downgradeSchema(provider, target, log) {
|
||||
const current = await readSchemaVersion((sql, params) => provider.query(sql, params));
|
||||
if (current === null) throw new Error('Database has no schema version (meta table missing)');
|
||||
return migrateSchema({
|
||||
direction: 'downgrade',
|
||||
dirUrl: DOWNGRADE_DIR,
|
||||
current,
|
||||
target,
|
||||
runStep: makeRunStep(provider),
|
||||
log,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,8 +24,13 @@ export const TABLES = [
|
||||
'modelconversation',
|
||||
'companyuser',
|
||||
'companysetting',
|
||||
'posterminalsyncqueue',
|
||||
'posterminalobituary',
|
||||
'posterminalcategory',
|
||||
'posterminal',
|
||||
'printerjob',
|
||||
'agentprinter',
|
||||
'printeragent',
|
||||
'printer',
|
||||
'numbers',
|
||||
'ledger',
|
||||
|
||||
69
src/providers/mssql/queries/agentprinter.js
Normal file
69
src/providers/mssql/queries/agentprinter.js
Normal file
@@ -0,0 +1,69 @@
|
||||
export const AGENTPRINTER_COLUMNS = `
|
||||
ap.id,
|
||||
ap.printeragent_id AS printeragentId,
|
||||
ap.type,
|
||||
ap.model,
|
||||
ap.serial,
|
||||
ap.enabled,
|
||||
ap.sort
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
${AGENTPRINTER_COLUMNS}
|
||||
FROM agentprinter ap
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE pa.company_id = @companyId
|
||||
ORDER BY ap.sort, ap.id
|
||||
`;
|
||||
|
||||
export const LIST_BY_PRINTERAGENT = `
|
||||
SELECT
|
||||
${AGENTPRINTER_COLUMNS}
|
||||
FROM agentprinter ap
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE ap.printeragent_id = @printeragentId AND pa.company_id = @companyId
|
||||
ORDER BY ap.sort, ap.id
|
||||
`;
|
||||
|
||||
export const GET_BY_ID = `
|
||||
SELECT
|
||||
${AGENTPRINTER_COLUMNS}
|
||||
FROM agentprinter ap
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE ap.id = @id AND pa.company_id = @companyId
|
||||
`;
|
||||
|
||||
export const FIND_BY_COMBO = `
|
||||
SELECT
|
||||
${AGENTPRINTER_COLUMNS}
|
||||
FROM agentprinter ap
|
||||
WHERE ap.printeragent_id = @printeragentId
|
||||
AND ap.type = @type
|
||||
AND (ap.serial = @serial OR (ap.serial IS NULL AND @serial IS NULL))
|
||||
AND (ap.model = @model OR (ap.model IS NULL AND @model IS NULL))
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO agentprinter (printeragent_id, type, model, serial, enabled, sort)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.printeragent_id AS printeragentId,
|
||||
INSERTED.type,
|
||||
INSERTED.model,
|
||||
INSERTED.serial,
|
||||
INSERTED.enabled,
|
||||
INSERTED.sort
|
||||
VALUES (@printeragentId, @type, @model, @serial, @enabled, @sort)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE agentprinter
|
||||
SET enabled = @enabled, sort = @sort
|
||||
WHERE id = @id AND printeragent_id IN (SELECT id FROM printeragent WHERE company_id = @companyId)
|
||||
`;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE FROM agentprinter
|
||||
WHERE id = @id AND printeragent_id IN (SELECT id FROM printeragent WHERE company_id = @companyId)
|
||||
`;
|
||||
@@ -29,6 +29,8 @@ export const ARTICLE_LIST_COLUMNS = `
|
||||
a.sku,
|
||||
at.name,
|
||||
at.slug,
|
||||
ISNULL(LEN(at.[desc]), 0) AS descLength,
|
||||
at.seo AS seo,
|
||||
pr.net,
|
||||
t.rate AS taxRate,
|
||||
a.track_stock,
|
||||
@@ -249,6 +251,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 = `
|
||||
INSERT INTO article (company_id, sku, unit_id, unitamount, weight, track_stock)
|
||||
@@ -462,12 +496,12 @@ export const GET_ARTICLE_BY_SKU_FULL = `
|
||||
co.default_language_id AS languageId,
|
||||
co.poscustomergroup_id AS posCustomerGroupId,
|
||||
cu.id AS companyuserId
|
||||
FROM article a
|
||||
INNER JOIN company co ON co.id = a.company_id
|
||||
INNER JOIN google_auth ga ON ga.id = @googleAuthId
|
||||
INNER JOIN companyuser cu
|
||||
FROM article a WITH (NOLOCK)
|
||||
INNER JOIN company co WITH (NOLOCK) ON co.id = a.company_id
|
||||
INNER JOIN google_auth ga WITH (NOLOCK) ON ga.id = @googleAuthId
|
||||
INNER JOIN companyuser cu WITH (NOLOCK)
|
||||
ON cu.company_id = a.company_id AND cu.google_auth_id = @googleAuthId
|
||||
LEFT JOIN categoryarticle ca ON ca.article_id = a.id
|
||||
LEFT JOIN categoryarticle ca WITH (NOLOCK) ON ca.article_id = a.id
|
||||
WHERE a.sku = @sku
|
||||
AND (ga.selected_company IS NULL OR a.company_id = ga.selected_company)
|
||||
)
|
||||
@@ -503,7 +537,7 @@ SELECT
|
||||
bp.thumbhash AS brandThumbHash,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM optionslistarticle ola
|
||||
FROM optionslistarticle ola WITH (NOLOCK)
|
||||
WHERE ola.optionslist_id = a.optionslist_id
|
||||
) AS optionsCount,
|
||||
a.optionslist_id AS optionslistId,
|
||||
@@ -516,16 +550,16 @@ SELECT
|
||||
ola.count,
|
||||
ola.sort,
|
||||
ola.mandated
|
||||
FROM optionslistarticle ola
|
||||
INNER JOIN article a_opt ON a_opt.id = ola.article_id
|
||||
LEFT JOIN articletext at_opt ON at_opt.article_id = a_opt.id AND at_opt.language_id = auth.languageId
|
||||
FROM optionslistarticle ola WITH (NOLOCK)
|
||||
INNER JOIN article a_opt WITH (NOLOCK) ON a_opt.id = ola.article_id
|
||||
LEFT JOIN articletext at_opt WITH (NOLOCK) ON at_opt.article_id = a_opt.id AND at_opt.language_id = auth.languageId
|
||||
WHERE ola.optionslist_id = a.optionslist_id
|
||||
ORDER BY ola.sort ASC, ola.id ASC
|
||||
FOR JSON PATH
|
||||
) AS optionslistItemsJson,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM articlelistarticle ala
|
||||
FROM articlelistarticle ala WITH (NOLOCK)
|
||||
WHERE ala.articlelist_id = a.articlelist_id
|
||||
) AS articleListCount,
|
||||
a.articlelist_id AS articlelistId,
|
||||
@@ -537,16 +571,16 @@ SELECT
|
||||
at_item.name,
|
||||
ala.count,
|
||||
ala.sort
|
||||
FROM articlelistarticle ala
|
||||
INNER JOIN article a_item ON a_item.id = ala.article_id
|
||||
LEFT JOIN articletext at_item ON at_item.article_id = a_item.id AND at_item.language_id = auth.languageId
|
||||
FROM articlelistarticle ala WITH (NOLOCK)
|
||||
INNER JOIN article a_item WITH (NOLOCK) ON a_item.id = ala.article_id
|
||||
LEFT JOIN articletext at_item WITH (NOLOCK) ON at_item.article_id = a_item.id AND at_item.language_id = auth.languageId
|
||||
WHERE ala.articlelist_id = a.articlelist_id
|
||||
ORDER BY ala.sort ASC, ala.id ASC
|
||||
FOR JSON PATH
|
||||
) AS articlelistItemsJson,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM article child
|
||||
FROM article child WITH (NOLOCK)
|
||||
WHERE child.parentarticle_id = a.id
|
||||
) AS childCount,
|
||||
a.is_parent AS isParent,
|
||||
@@ -563,11 +597,11 @@ SELECT
|
||||
at_child.name,
|
||||
av.id AS attributeValueId,
|
||||
av.value AS attributeValue
|
||||
FROM article child
|
||||
LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = auth.languageId
|
||||
FROM article child WITH (NOLOCK)
|
||||
LEFT JOIN articletext at_child WITH (NOLOCK) ON at_child.article_id = child.id AND at_child.language_id = auth.languageId
|
||||
LEFT JOIN (
|
||||
articleattributevalue aav
|
||||
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.parentattributename_id
|
||||
articleattributevalue aav WITH (NOLOCK)
|
||||
INNER JOIN attributevalue av WITH (NOLOCK) ON av.id = aav.attributevalue_id AND av.attribute_id = a.parentattributename_id
|
||||
) ON aav.article_id = child.id
|
||||
WHERE child.parentarticle_id = a.id
|
||||
ORDER BY child.parentsort ASC, child.id ASC
|
||||
@@ -575,7 +609,7 @@ SELECT
|
||||
) AS childrenJson,
|
||||
(
|
||||
SELECT ca2.category_id
|
||||
FROM categoryarticle ca2
|
||||
FROM categoryarticle ca2 WITH (NOLOCK)
|
||||
WHERE ca2.article_id = a.id
|
||||
ORDER BY ca2.category_id
|
||||
FOR JSON PATH
|
||||
@@ -590,15 +624,15 @@ SELECT
|
||||
p.thumbw AS thumbW,
|
||||
p.thumbh AS thumbH,
|
||||
p.thumbhash AS thumbHash
|
||||
FROM articlepic ap
|
||||
INNER JOIN pic p ON p.id = ap.pic_id
|
||||
FROM articlepic ap WITH (NOLOCK)
|
||||
INNER JOIN pic p WITH (NOLOCK) ON p.id = ap.pic_id
|
||||
WHERE ap.article_id = a.id
|
||||
ORDER BY ap.sort ASC, ap.id ASC
|
||||
FOR JSON PATH
|
||||
) AS photosJson,
|
||||
(
|
||||
SELECT ae.ean
|
||||
FROM articleean ae
|
||||
FROM articleean ae WITH (NOLOCK)
|
||||
WHERE ae.article_id = a.id
|
||||
ORDER BY ae.ean
|
||||
FOR JSON PATH
|
||||
@@ -609,8 +643,8 @@ SELECT
|
||||
pr.net,
|
||||
t.rate AS taxRate,
|
||||
pr.customergroup_id AS customergroupId
|
||||
FROM price pr
|
||||
INNER JOIN tax t ON t.id = pr.tax_id
|
||||
FROM price pr WITH (NOLOCK)
|
||||
INNER JOIN tax t WITH (NOLOCK) ON t.id = pr.tax_id
|
||||
WHERE pr.article_id = a.id
|
||||
ORDER BY pr.customergroup_id, pr.minmultiplier
|
||||
FOR JSON PATH
|
||||
@@ -632,11 +666,11 @@ SELECT
|
||||
SELECT
|
||||
m.[date] AS [date],
|
||||
SUM(ma.amount - COALESCE(ma_ledger.booked_amount, 0)) AS amount
|
||||
FROM manifest m
|
||||
INNER JOIN manifestarticle ma ON ma.manifest_id = m.id
|
||||
FROM manifest m WITH (NOLOCK)
|
||||
INNER JOIN manifestarticle ma WITH (NOLOCK) ON ma.manifest_id = m.id
|
||||
LEFT JOIN (
|
||||
SELECT manifestarticle_id, SUM(amount) AS booked_amount
|
||||
FROM ledger
|
||||
FROM ledger WITH (NOLOCK)
|
||||
WHERE warehouse_id IS NULL
|
||||
GROUP BY manifestarticle_id
|
||||
) ma_ledger ON ma_ledger.manifestarticle_id = ma.id
|
||||
@@ -651,9 +685,9 @@ SELECT
|
||||
ORDER BY m.[date] ASC, m.id ASC
|
||||
FOR JSON PATH
|
||||
) AS incomingJson
|
||||
FROM articlesupplier asup
|
||||
INNER JOIN entity e ON e.id = asup.entity_id
|
||||
LEFT JOIN tax t ON t.id = asup.tax_id
|
||||
FROM articlesupplier asup WITH (NOLOCK)
|
||||
INNER JOIN entity e WITH (NOLOCK) ON e.id = asup.entity_id
|
||||
LEFT JOIN tax t WITH (NOLOCK) ON t.id = asup.tax_id
|
||||
WHERE asup.article_id = a.id
|
||||
ORDER BY asup.sort ASC, asup.id ASC
|
||||
FOR JSON PATH
|
||||
@@ -665,44 +699,23 @@ SELECT
|
||||
w.sort,
|
||||
CASE
|
||||
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||
SELECT 1 FROM articlelistarticle ala WITH (NOLOCK) WHERE ala.articlelist_id = a.articlelist_id
|
||||
) THEN COALESCE((
|
||||
SELECT CASE
|
||||
WHEN MIN(COALESCE(comp_stock.stock, 0) / ala.count) < 0 THEN 0
|
||||
ELSE FLOOR(MIN(COALESCE(comp_stock.stock, 0) / ala.count))
|
||||
WHEN MIN(COALESCE(aws_comp.stock, 0) / ala.count) < 0 THEN 0
|
||||
ELSE FLOOR(MIN(COALESCE(aws_comp.stock, 0) / ala.count))
|
||||
END
|
||||
FROM articlelistarticle ala
|
||||
LEFT JOIN (
|
||||
SELECT ca.id AS comp_id, SUM(l.amount) AS stock
|
||||
FROM ledger l
|
||||
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||
INNER JOIN article ca ON (
|
||||
ma.sku = ca.sku
|
||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||
)
|
||||
WHERE m.company_id = a.company_id
|
||||
AND l.warehouse_id = w.id
|
||||
GROUP BY ca.id
|
||||
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||
FROM articlelistarticle ala WITH (NOLOCK)
|
||||
LEFT JOIN articlewarehousesummary aws_comp WITH (NOLOCK)
|
||||
ON aws_comp.article_id = ala.article_id AND aws_comp.warehouse_id = w.id
|
||||
WHERE ala.articlelist_id = a.articlelist_id
|
||||
), 0)
|
||||
WHEN a.articlelist_id IS NOT NULL THEN 0
|
||||
ELSE COALESCE(stock_calc.stock, 0)
|
||||
ELSE COALESCE(aws.stock, 0)
|
||||
END AS stock
|
||||
FROM warehouse w
|
||||
LEFT JOIN (
|
||||
SELECT l.warehouse_id, SUM(l.amount) AS stock
|
||||
FROM ledger l
|
||||
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||
WHERE m.company_id = a.company_id
|
||||
AND (
|
||||
ma.sku = a.sku
|
||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
||||
)
|
||||
GROUP BY l.warehouse_id
|
||||
) stock_calc ON stock_calc.warehouse_id = w.id
|
||||
FROM warehouse w WITH (NOLOCK)
|
||||
LEFT JOIN articlewarehousesummary aws WITH (NOLOCK)
|
||||
ON aws.article_id = a.id AND aws.warehouse_id = w.id
|
||||
WHERE w.company_id = a.company_id
|
||||
ORDER BY w.sort ASC, w.name ASC
|
||||
FOR JSON PATH
|
||||
@@ -715,56 +728,50 @@ SELECT
|
||||
COALESCE(os.customer_name, LTRIM(RTRIM(ISNULL(e.first_name + ' ', '') + ISNULL(e.name, '')))) AS customerName,
|
||||
ROUND(SUM(ma.amount) - COALESCE((
|
||||
SELECT SUM(ma_s.amount)
|
||||
FROM manifest m_s
|
||||
INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = m_s.id
|
||||
LEFT JOIN shipmentsummary ss ON ss.manifest_id = m_s.id
|
||||
WHERE m_s.company_id = m.company_id
|
||||
AND m_s.[type] = 'shipment'
|
||||
FROM shipmentsummary ss WITH (NOLOCK)
|
||||
INNER JOIN manifestarticle ma_s WITH (NOLOCK) ON ma_s.manifest_id = ss.manifest_id
|
||||
WHERE ss.company_id = m.company_id
|
||||
AND ss.order_id = m.id
|
||||
AND ISNULL(ss.state, 'new') != 'cancelled'
|
||||
AND m_s.[number] NOT LIKE '%#cancelled#%'
|
||||
AND ma_s.sku = a.sku
|
||||
AND m_s.[number] LIKE m.[number] + COALESCE((SELECT prefix FROM numbers WHERE company_id = m.company_id AND [type] = 'shipment'), '-L-') + '%'
|
||||
), 0), 3) AS amount
|
||||
FROM manifest m
|
||||
INNER JOIN manifestarticle ma ON ma.manifest_id = m.id
|
||||
LEFT JOIN ordersummary os ON os.manifest_id = m.id
|
||||
LEFT JOIN entity e ON e.id = m.entity_id
|
||||
FROM manifest m WITH (NOLOCK)
|
||||
INNER JOIN manifestarticle ma WITH (NOLOCK) ON ma.manifest_id = m.id
|
||||
LEFT JOIN ordersummary os WITH (NOLOCK) ON os.manifest_id = m.id
|
||||
LEFT JOIN entity e WITH (NOLOCK) ON e.id = m.entity_id
|
||||
WHERE m.company_id = a.company_id
|
||||
AND m.[type] = 'order'
|
||||
AND ma.sku = a.sku
|
||||
AND COALESCE(os.is_shipped, 0) = 0
|
||||
GROUP BY m.id, m.[number], m.[date], os.customer_name, e.first_name, e.name
|
||||
GROUP BY m.id, m.[number], m.[date], m.company_id, os.customer_name, e.first_name, e.name
|
||||
HAVING (SUM(ma.amount) - COALESCE((
|
||||
SELECT SUM(ma_s.amount)
|
||||
FROM manifest m_s
|
||||
INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = m_s.id
|
||||
LEFT JOIN shipmentsummary ss ON ss.manifest_id = m_s.id
|
||||
WHERE m_s.company_id = m.company_id
|
||||
AND m_s.[type] = 'shipment'
|
||||
FROM shipmentsummary ss WITH (NOLOCK)
|
||||
INNER JOIN manifestarticle ma_s WITH (NOLOCK) ON ma_s.manifest_id = ss.manifest_id
|
||||
WHERE ss.company_id = m.company_id
|
||||
AND ss.order_id = m.id
|
||||
AND ISNULL(ss.state, 'new') != 'cancelled'
|
||||
AND m_s.[number] NOT LIKE '%#cancelled#%'
|
||||
AND ma_s.sku = a.sku
|
||||
AND m_s.[number] LIKE m.[number] + COALESCE((SELECT prefix FROM numbers WHERE company_id = m.company_id AND [type] = 'shipment'), '-L-') + '%'
|
||||
), 0)) > 0
|
||||
ORDER BY m.[date] ASC, m.id ASC
|
||||
FOR JSON PATH
|
||||
) AS undeliveredOrdersJson
|
||||
FROM article a
|
||||
LEFT JOIN articletext at
|
||||
FROM article a WITH (NOLOCK)
|
||||
LEFT JOIN articletext at WITH (NOLOCK)
|
||||
ON at.article_id = a.id AND at.language_id = auth.languageId
|
||||
LEFT JOIN unit u ON u.id = a.unit_id
|
||||
LEFT JOIN unittext ut
|
||||
LEFT JOIN unit u WITH (NOLOCK) ON u.id = a.unit_id
|
||||
LEFT JOIN unittext ut WITH (NOLOCK)
|
||||
ON ut.unit_id = u.id AND ut.language_id = auth.languageId
|
||||
LEFT JOIN brand b ON b.id = a.brand_id
|
||||
LEFT JOIN brand b WITH (NOLOCK) ON b.id = a.brand_id
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1 bp2.pic_id
|
||||
FROM brandpic bp2
|
||||
FROM brandpic bp2 WITH (NOLOCK)
|
||||
WHERE bp2.brand_id = b.id
|
||||
ORDER BY bp2.sort ASC, bp2.id ASC
|
||||
) first_brand_pic
|
||||
LEFT JOIN pic bp ON bp.id = first_brand_pic.pic_id
|
||||
LEFT JOIN article fa ON fa.id = a.parentarticle_id
|
||||
LEFT JOIN attributename fan ON fan.id = a.parentattributename_id
|
||||
LEFT JOIN pic bp WITH (NOLOCK) ON bp.id = first_brand_pic.pic_id
|
||||
LEFT JOIN article fa WITH (NOLOCK) ON fa.id = a.parentarticle_id
|
||||
LEFT JOIN attributename fan WITH (NOLOCK) ON fan.id = a.parentattributename_id
|
||||
WHERE a.id = auth.articleId
|
||||
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
|
||||
) AS articleJson
|
||||
|
||||
43
src/providers/mssql/queries/autoinvite.js
Normal file
43
src/providers/mssql/queries/autoinvite.js
Normal file
@@ -0,0 +1,43 @@
|
||||
export const ADD = `
|
||||
INSERT INTO autoinvite (company_id, email, role)
|
||||
OUTPUT INSERTED.id
|
||||
VALUES (@companyId, @email, @role)
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
role,
|
||||
created_at AS createdAt
|
||||
FROM autoinvite
|
||||
WHERE company_id = @companyId
|
||||
ORDER BY email
|
||||
`;
|
||||
|
||||
export const LIST_BY_EMAIL = `
|
||||
SELECT
|
||||
id,
|
||||
company_id AS companyId,
|
||||
role
|
||||
FROM autoinvite
|
||||
WHERE email = @email
|
||||
ORDER BY id
|
||||
`;
|
||||
|
||||
export const GET_IN_COMPANY = `
|
||||
SELECT id
|
||||
FROM autoinvite
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const GET_BY_COMPANY_EMAIL = `
|
||||
SELECT id
|
||||
FROM autoinvite
|
||||
WHERE company_id = @companyId AND email = @email
|
||||
`;
|
||||
|
||||
export const DELETE = `
|
||||
DELETE FROM autoinvite
|
||||
WHERE id = @id
|
||||
`;
|
||||
@@ -64,7 +64,6 @@ FROM categoryarticle ca
|
||||
INNER JOIN article a ON a.id = ca.article_id
|
||||
${ARTICLE_LIST_JOINS}
|
||||
WHERE ca.category_id = @categoryId
|
||||
AND a.parentarticle_id IS NULL
|
||||
ORDER BY a.id
|
||||
`;
|
||||
|
||||
@@ -80,10 +79,19 @@ INNER JOIN warehouse w ON w.id = aws.warehouse_id
|
||||
INNER JOIN categoryarticle ca ON ca.article_id = aws.article_id
|
||||
INNER JOIN article a ON a.id = ca.article_id
|
||||
WHERE ca.category_id = @categoryId
|
||||
AND a.parentarticle_id IS NULL
|
||||
ORDER BY w.sort ASC, w.name ASC, aws.id ASC
|
||||
`;
|
||||
|
||||
export const GET_CATEGORY_ARTICLE_EANS = `
|
||||
SELECT
|
||||
ae.article_id AS articleId,
|
||||
ae.ean
|
||||
FROM articleean ae
|
||||
INNER JOIN categoryarticle ca ON ca.article_id = ae.article_id
|
||||
WHERE ca.category_id = @categoryId
|
||||
ORDER BY ae.ean ASC
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO category (parent_id, sort)
|
||||
OUTPUT INSERTED.id, INSERTED.parent_id AS parentId, INSERTED.sort
|
||||
@@ -178,3 +186,102 @@ export const DELETE = `
|
||||
DELETE FROM category
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const LIST_CHILD_CATEGORIES = `
|
||||
SELECT id
|
||||
FROM category
|
||||
WHERE parent_id = @parentId
|
||||
ORDER BY sort ASC, id ASC
|
||||
`;
|
||||
|
||||
export const GET_CATEGORY_ARTICLE_PICS = `
|
||||
SELECT
|
||||
a.id AS articleId,
|
||||
p.id AS picId,
|
||||
p.hash,
|
||||
NULL AS buffer
|
||||
FROM categoryarticle ca
|
||||
INNER JOIN article a ON a.id = ca.article_id
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1 ap.pic_id
|
||||
FROM articlepic ap
|
||||
WHERE ap.article_id = a.id
|
||||
ORDER BY ap.sort ASC, ap.id ASC
|
||||
) first_pic
|
||||
INNER JOIN pic p ON p.id = first_pic.pic_id
|
||||
WHERE ca.category_id = @categoryId
|
||||
AND a.parentarticle_id IS NULL
|
||||
ORDER BY a.id ASC
|
||||
`;
|
||||
|
||||
export const TOUCH_SUBTREE_CATEGORIES = `
|
||||
;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
|
||||
)
|
||||
UPDATE category
|
||||
SET updated_at = SYSUTCDATETIME()
|
||||
WHERE id IN (SELECT id FROM cats)
|
||||
`;
|
||||
|
||||
export const TOUCH_SUBTREE_ARTICLES = `
|
||||
;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
|
||||
)
|
||||
UPDATE article
|
||||
SET updated_at = SYSUTCDATETIME()
|
||||
WHERE id IN (
|
||||
SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats)
|
||||
UNION
|
||||
SELECT child.id FROM article child
|
||||
WHERE child.parentarticle_id IN (
|
||||
SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats)
|
||||
)
|
||||
UNION
|
||||
SELECT parent.parentarticle_id FROM article parent
|
||||
WHERE parent.id IN (
|
||||
SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats)
|
||||
) AND parent.parentarticle_id IS NOT NULL
|
||||
)
|
||||
`;
|
||||
|
||||
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
|
||||
`;
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ SELECT
|
||||
l.iso639,
|
||||
tr.rate AS taxrate,
|
||||
rtr.rate AS reducedtaxrate,
|
||||
tfr.rate AS taxfreerate
|
||||
tfr.rate AS taxfreerate,
|
||||
cu.role AS role
|
||||
FROM company c
|
||||
INNER JOIN companyuser cu ON cu.company_id = c.id
|
||||
LEFT JOIN language l ON l.id = c.default_language_id
|
||||
|
||||
@@ -10,7 +10,8 @@ SELECT
|
||||
c.root_category_id AS rootCategoryId,
|
||||
c.default_language_id AS languageId,
|
||||
c.poscustomergroup_id AS posCustomerGroupId,
|
||||
cu.id AS companyuserId
|
||||
cu.id AS companyuserId,
|
||||
cu.role AS role
|
||||
FROM company c
|
||||
LEFT JOIN companyuser cu
|
||||
ON cu.company_id = c.id AND cu.google_auth_id = @googleAuthId
|
||||
@@ -18,12 +19,75 @@ WHERE c.id = @companyId
|
||||
`;
|
||||
|
||||
export const ADD = `
|
||||
INSERT INTO companyuser (google_auth_id, company_id)
|
||||
INSERT INTO companyuser (google_auth_id, company_id, role)
|
||||
OUTPUT INSERTED.id
|
||||
VALUES (@googleAuthId, @companyId)
|
||||
VALUES (@googleAuthId, @companyId, @role)
|
||||
`;
|
||||
|
||||
export const DELETE = `
|
||||
DELETE FROM companyuser
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
cu.id,
|
||||
cu.google_auth_id AS googleAuthId,
|
||||
ga.email,
|
||||
ga.picture,
|
||||
cu.role
|
||||
FROM companyuser cu
|
||||
INNER JOIN google_auth ga ON ga.id = cu.google_auth_id
|
||||
WHERE cu.company_id = @companyId
|
||||
ORDER BY ga.email
|
||||
`;
|
||||
|
||||
export const GET_IN_COMPANY = `
|
||||
SELECT
|
||||
cu.id,
|
||||
cu.google_auth_id AS googleAuthId,
|
||||
ga.email,
|
||||
ga.picture,
|
||||
cu.role
|
||||
FROM companyuser cu
|
||||
INNER JOIN google_auth ga ON ga.id = cu.google_auth_id
|
||||
WHERE cu.id = @id AND cu.company_id = @companyId
|
||||
`;
|
||||
|
||||
export const GET_MEMBERSHIP = `
|
||||
SELECT id, role
|
||||
FROM companyuser
|
||||
WHERE google_auth_id = @googleAuthId AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const COUNT_ADMINS = `
|
||||
SELECT COUNT(*) AS count
|
||||
FROM companyuser
|
||||
WHERE company_id = @companyId AND role = 'admin'
|
||||
`;
|
||||
|
||||
export const SET_ROLE = `
|
||||
UPDATE companyuser
|
||||
SET role = @role
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const CLEAR_ENTITYHISTORY_USER = `
|
||||
UPDATE entityhistory SET companyuser_id = NULL WHERE companyuser_id = @id
|
||||
`;
|
||||
|
||||
export const CLEAR_COMPANYHISTORY_USER = `
|
||||
UPDATE companyhistory SET companyuser_id = NULL WHERE companyuser_id = @id
|
||||
`;
|
||||
|
||||
export const IS_MEMBER = `
|
||||
SELECT id
|
||||
FROM companyuser
|
||||
WHERE google_auth_id = @googleAuthId AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const CLEAR_SELECTED_COMPANY = `
|
||||
UPDATE google_auth
|
||||
SET selected_company = NULL
|
||||
WHERE id = @googleAuthId AND selected_company = @companyId
|
||||
`;
|
||||
|
||||
@@ -136,6 +136,12 @@ WHERE id = @id
|
||||
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 = `
|
||||
DELETE e
|
||||
FROM entity e
|
||||
|
||||
@@ -3,6 +3,7 @@ SELECT
|
||||
ma.id,
|
||||
ma.sku,
|
||||
ma.amount AS lineAmount,
|
||||
ma.pu AS linePu,
|
||||
ma.net,
|
||||
ma.rebate,
|
||||
ma.taxrate,
|
||||
@@ -67,7 +68,7 @@ SELECT TOP 1
|
||||
hash_version,
|
||||
prev_hash,
|
||||
created_at
|
||||
FROM ledger WITH (UPDLOCK, HOLDLOCK)
|
||||
FROM ledger WITH (UPDLOCK, ROWLOCK)
|
||||
ORDER BY id DESC;
|
||||
`;
|
||||
|
||||
|
||||
@@ -39,12 +39,12 @@ WHERE id = @id AND company_id = @companyId
|
||||
|
||||
export const PAYMENT_CREATE = `
|
||||
INSERT INTO payment (account_id, manifest_id, [type], amount)
|
||||
OUTPUT INSERTED.id, INSERTED.account_id, INSERTED.manifest_id, INSERTED.[type], INSERTED.amount
|
||||
OUTPUT INSERTED.id, INSERTED.account_id, INSERTED.manifest_id, INSERTED.[type], INSERTED.amount, INSERTED.created_at AS createdAt
|
||||
VALUES (@accountId, @manifestId, @type, @amount)
|
||||
`;
|
||||
|
||||
export const PAYMENT_LIST_BY_MANIFEST = `
|
||||
SELECT id, account_id, manifest_id, [type], amount
|
||||
SELECT id, account_id, manifest_id, [type], amount, created_at AS createdAt
|
||||
FROM payment
|
||||
WHERE manifest_id = @manifestId
|
||||
ORDER BY id ASC
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
export const QUOTE_TYPE = 'quote';
|
||||
export const ORDER_TYPE = 'order';
|
||||
export const RETOURE_TYPE = 'retoure';
|
||||
export const RETURN_TYPE = 'return';
|
||||
|
||||
export const LIST_MANIFESTS = `
|
||||
SELECT
|
||||
@@ -158,22 +160,61 @@ SELECT
|
||||
a.id,
|
||||
a.is_parent AS isParent,
|
||||
COALESCE(
|
||||
(SELECT at.name FROM articletext at WHERE at.article_id = a.id AND at.language_id = co.default_language_id),
|
||||
(SELECT MIN(at2.name) FROM articletext at2 WHERE at2.article_id = a.id),
|
||||
(SELECT at.name FROM articletext at WITH (NOLOCK) WHERE at.article_id = a.id AND at.language_id = co.default_language_id),
|
||||
(SELECT MIN(at2.name) FROM articletext at2 WITH (NOLOCK) WHERE at2.article_id = a.id),
|
||||
a.sku
|
||||
) AS articleName,
|
||||
COALESCE(
|
||||
(SELECT MIN(asup.pu) FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = @sku),
|
||||
(SELECT MIN(asup2.pu) FROM articlesupplier asup2 WHERE asup2.article_id = a.id),
|
||||
(SELECT MIN(asup.pu) FROM articlesupplier asup WITH (NOLOCK) WHERE asup.article_id = a.id AND asup.sku = @sku),
|
||||
(SELECT MIN(asup2.pu) FROM articlesupplier asup2 WITH (NOLOCK) WHERE asup2.article_id = a.id),
|
||||
a.unitamount,
|
||||
1
|
||||
) AS pu
|
||||
FROM article a
|
||||
INNER JOIN manifest m ON m.company_id = a.company_id
|
||||
INNER JOIN company co ON co.id = m.company_id
|
||||
FROM article a WITH (NOLOCK)
|
||||
INNER JOIN manifest m WITH (NOLOCK) ON m.company_id = a.company_id
|
||||
INNER JOIN company co WITH (NOLOCK) ON co.id = m.company_id
|
||||
WHERE m.id = @manifestId
|
||||
AND (a.sku = @sku OR EXISTS (
|
||||
SELECT 1 FROM articlesupplier asup WHERE asup.article_id = a.id AND asup.sku = @sku
|
||||
SELECT 1 FROM articlesupplier asup WITH (NOLOCK) WHERE asup.article_id = a.id AND asup.sku = @sku
|
||||
))
|
||||
`;
|
||||
|
||||
export const GET_MANIFEST_CONTEXT = `
|
||||
SELECT
|
||||
m.id,
|
||||
m.company_id AS companyId,
|
||||
m.[type] AS [type],
|
||||
m.entity_id AS entityId,
|
||||
e.name AS entityName
|
||||
FROM manifest m
|
||||
LEFT JOIN entity e ON e.id = m.entity_id
|
||||
WHERE m.id = @manifestId
|
||||
`;
|
||||
|
||||
export const GET_ARTICLE_SUPPLIER_LINK = `
|
||||
SELECT
|
||||
asup.id,
|
||||
asup.article_id AS articleId,
|
||||
asup.entity_id AS entityId,
|
||||
asup.sku,
|
||||
asup.pu,
|
||||
asup.net,
|
||||
asup.tax_id AS taxId,
|
||||
t.rate AS taxRate,
|
||||
e.name AS supplierName
|
||||
FROM articlesupplier asup
|
||||
LEFT JOIN entity e ON e.id = asup.entity_id
|
||||
LEFT JOIN tax t ON t.id = asup.tax_id
|
||||
WHERE asup.article_id = @articleId
|
||||
AND asup.entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const UPDATE_ARTICLE_SUPPLIER_LINK = `
|
||||
UPDATE articlesupplier
|
||||
SET
|
||||
pu = @pu,
|
||||
net = @net,
|
||||
tax_id = @taxId
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
export const CREATE = `
|
||||
INSERT INTO pic (
|
||||
w, h, size, hash, thumbw, thumbh, thumbhash,
|
||||
smallthumbw, smallthumbh, smallthumbbinary,
|
||||
source, fullbinary, thumbbinary
|
||||
smallthumbw, smallthumbh, smallthumbbinary, source
|
||||
)
|
||||
OUTPUT INSERTED.id
|
||||
VALUES (
|
||||
@w, @h, @size, @hash, @thumbw, @thumbh, @thumbhash,
|
||||
@smallthumbw, @smallthumbh, @smallthumbbinary,
|
||||
@source, @fullbinary, @thumbbinary
|
||||
@smallthumbw, @smallthumbh, @smallthumbbinary, @source
|
||||
)
|
||||
`;
|
||||
|
||||
@@ -60,13 +58,13 @@ WHERE categoryId IS NOT NULL
|
||||
`;
|
||||
|
||||
export const GET_FULL_BY_HASH = `
|
||||
SELECT w, h, size, fullbinary AS buffer
|
||||
SELECT w, h, size, hash
|
||||
FROM pic
|
||||
WHERE hash = @hash
|
||||
`;
|
||||
|
||||
export const GET_THUMB_BY_HASH = `
|
||||
SELECT thumbw AS w, thumbh AS h, thumbbinary AS buffer
|
||||
SELECT thumbw AS w, thumbh AS h, hash
|
||||
FROM pic
|
||||
WHERE hash = @hash
|
||||
`;
|
||||
|
||||
@@ -10,10 +10,13 @@ SELECT
|
||||
root_category_id AS rootCategoryId,
|
||||
sync_root_articles AS syncRootArticles,
|
||||
lastseen_at AS lastseenAt,
|
||||
updated_at AS updatedAt
|
||||
updated_at AS updatedAt,
|
||||
sort,
|
||||
enabled,
|
||||
clientversion
|
||||
FROM posterminal
|
||||
WHERE company_id = @companyId
|
||||
ORDER BY id
|
||||
ORDER BY sort, id
|
||||
`;
|
||||
|
||||
export const GET_BY_ID = `
|
||||
@@ -29,7 +32,10 @@ SELECT
|
||||
root_category_id AS rootCategoryId,
|
||||
sync_root_articles AS syncRootArticles,
|
||||
lastseen_at AS lastseenAt,
|
||||
updated_at AS updatedAt
|
||||
updated_at AS updatedAt,
|
||||
sort,
|
||||
enabled,
|
||||
clientversion
|
||||
FROM posterminal
|
||||
WHERE id = @id
|
||||
`;
|
||||
@@ -47,29 +53,38 @@ SELECT
|
||||
root_category_id AS rootCategoryId,
|
||||
sync_root_articles AS syncRootArticles,
|
||||
lastseen_at AS lastseenAt,
|
||||
updated_at AS updatedAt
|
||||
updated_at AS updatedAt,
|
||||
sort,
|
||||
enabled,
|
||||
clientversion
|
||||
FROM posterminal
|
||||
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
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO posterminal (company_id, name, token, pairingpin, paired_at, clientserial, maxknownobituaryid, root_category_id, sync_root_articles, lastseen_at, updated_at)
|
||||
OUTPUT INSERTED.id, INSERTED.name, INSERTED.token, INSERTED.pairingpin, INSERTED.paired_at AS pairedAt, INSERTED.clientserial AS clientserial, INSERTED.maxknownobituaryid AS maxknownobituaryid, INSERTED.root_category_id AS rootCategoryId, INSERTED.sync_root_articles AS syncRootArticles, INSERTED.lastseen_at AS lastseenAt, INSERTED.updated_at AS updatedAt
|
||||
VALUES (@companyId, @name, @token, @pairingpin, @pairedAt, @clientserial, @maxknownobituaryid, @rootCategoryId, @syncRootArticles, @lastseenAt, SYSUTCDATETIME())
|
||||
INSERT INTO posterminal (company_id, name, token, pairingpin, paired_at, clientserial, maxknownobituaryid, root_category_id, sync_root_articles, lastseen_at, sort, enabled, updated_at)
|
||||
OUTPUT INSERTED.id, INSERTED.name, INSERTED.token, INSERTED.pairingpin, INSERTED.paired_at AS pairedAt, INSERTED.clientserial AS clientserial, INSERTED.maxknownobituaryid AS maxknownobituaryid, INSERTED.root_category_id AS rootCategoryId, INSERTED.sync_root_articles AS syncRootArticles, INSERTED.lastseen_at AS lastseenAt, INSERTED.updated_at AS updatedAt, INSERTED.sort, INSERTED.enabled, INSERTED.clientversion
|
||||
VALUES (@companyId, @name, @token, @pairingpin, @pairedAt, @clientserial, @maxknownobituaryid, @rootCategoryId, @syncRootArticles, @lastseenAt, @sort, @enabled, SYSUTCDATETIME())
|
||||
`;
|
||||
|
||||
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, 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
|
||||
`;
|
||||
|
||||
export const SET_CLIENTVERSION = `
|
||||
UPDATE posterminal
|
||||
SET clientversion = @clientversion, updated_at = SYSUTCDATETIME()
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const TOUCH_LASTSEEN = `
|
||||
UPDATE posterminal
|
||||
SET lastseen_at = SYSUTCDATETIME()
|
||||
@@ -166,72 +181,109 @@ SELECT
|
||||
root_category_id AS rootCategoryId,
|
||||
sync_root_articles AS syncRootArticles,
|
||||
lastseen_at AS lastseenAt,
|
||||
updated_at AS updatedAt
|
||||
updated_at AS updatedAt,
|
||||
sort,
|
||||
enabled,
|
||||
clientversion
|
||||
FROM posterminal
|
||||
WHERE token = @token
|
||||
`;
|
||||
|
||||
export const COUNT_PRODUCTS = `
|
||||
SELECT COUNT(DISTINCT a.id) AS count
|
||||
FROM article a
|
||||
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM categoryarticle ca
|
||||
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
|
||||
WHERE ca.article_id = a.id
|
||||
AND pc.posterminal_id = @posterminalId
|
||||
)
|
||||
AND (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) > @cursor
|
||||
SELECT COUNT(*) AS count
|
||||
FROM (
|
||||
SELECT MAX(q.id) AS syncId, a.id
|
||||
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
|
||||
FROM categoryarticle ca
|
||||
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
|
||||
WHERE ca.article_id = a.id
|
||||
AND pc.posterminal_id = @posterminalId
|
||||
)
|
||||
GROUP BY a.id
|
||||
HAVING MAX(q.id) > @cursor
|
||||
) t
|
||||
`;
|
||||
|
||||
export const COUNT_CATEGORIES = `
|
||||
SELECT COUNT(*) AS count
|
||||
FROM category c
|
||||
INNER JOIN posterminalcategory pc ON pc.category_id = c.id
|
||||
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
|
||||
INNER JOIN company co ON co.id = pt.company_id
|
||||
WHERE pc.posterminal_id = @posterminalId
|
||||
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
|
||||
FROM (
|
||||
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 company co ON co.id = pt.company_id
|
||||
WHERE q.posterminal_id = @posterminalId
|
||||
AND q.entity_type = 'category'
|
||||
AND (co.root_category_id IS NULL OR c.id <> co.root_category_id)
|
||||
GROUP BY c.id
|
||||
HAVING MAX(q.id) > @cursor
|
||||
) t
|
||||
`;
|
||||
|
||||
export const COUNT_CUSTOMERS = `
|
||||
SELECT COUNT(*) AS count
|
||||
FROM entity e
|
||||
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
|
||||
WHERE cg.name <> 'Lieferanten'
|
||||
AND DATEDIFF_BIG(second, '1970-01-01', e.updated_at) > @cursor
|
||||
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 posterminal pt ON pt.id = q.posterminal_id AND pt.company_id = cg.company_id
|
||||
WHERE q.posterminal_id = @posterminalId
|
||||
AND q.entity_type = 'customer'
|
||||
AND cg.name <> 'Lieferanten'
|
||||
GROUP BY e.id
|
||||
HAVING MAX(q.id) > @cursor
|
||||
) t
|
||||
`;
|
||||
|
||||
|
||||
export const COUNT_CUSTOMER_GROUPS = `
|
||||
SELECT COUNT(*) AS count
|
||||
FROM customergroup cg
|
||||
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
|
||||
WHERE cg.name <> 'Lieferanten'
|
||||
AND DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) > @cursor
|
||||
FROM (
|
||||
SELECT MAX(q.id) AS syncId, cg.id
|
||||
FROM posterminalsyncqueue q
|
||||
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 = `
|
||||
SELECT COUNT(DISTINCT a.id) AS count
|
||||
FROM article a
|
||||
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
|
||||
WHERE a.articlelist_id IS NOT NULL
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM articlelistarticle ala
|
||||
WHERE ala.articlelist_id = a.articlelist_id
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM categoryarticle ca
|
||||
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
|
||||
WHERE ca.article_id = a.id
|
||||
AND pc.posterminal_id = @posterminalId
|
||||
)
|
||||
AND (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) > @cursor
|
||||
SELECT COUNT(*) AS count
|
||||
FROM (
|
||||
SELECT MAX(q.id) AS syncId, a.id
|
||||
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 (
|
||||
SELECT 1
|
||||
FROM articlelistarticle ala
|
||||
WHERE ala.articlelist_id = a.articlelist_id
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM categoryarticle ca
|
||||
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
|
||||
WHERE ca.article_id = a.id
|
||||
AND pc.posterminal_id = @posterminalId
|
||||
)
|
||||
GROUP BY a.id
|
||||
HAVING MAX(q.id) > @cursor
|
||||
) t
|
||||
`;
|
||||
|
||||
export const MAX_ORDER_ID = `
|
||||
@@ -255,16 +307,21 @@ SELECT TOP (@limit)
|
||||
WHERE cp.category_id = c.id
|
||||
ORDER BY cp.sort ASC, cp.id ASC
|
||||
) AS imgHash,
|
||||
DATEDIFF_BIG(second, '1970-01-01', c.updated_at) AS lastChanged
|
||||
FROM category c
|
||||
INNER JOIN posterminalcategory pc ON pc.category_id = c.id
|
||||
sq.syncId AS lastChanged
|
||||
FROM (
|
||||
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 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
|
||||
WHERE pc.posterminal_id = @posterminalId
|
||||
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
|
||||
ORDER BY DATEDIFF_BIG(second, '1970-01-01', c.updated_at) ASC, c.id ASC
|
||||
WHERE (co.root_category_id IS NULL OR c.id <> co.root_category_id)
|
||||
ORDER BY sq.syncId ASC
|
||||
`;
|
||||
|
||||
export const LIST_POS_PRODUCTS = `
|
||||
@@ -275,7 +332,7 @@ SELECT TOP (@limit)
|
||||
COALESCE(pr.net, 0) AS netPrice,
|
||||
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
||||
a.created_at AS createdAt,
|
||||
(DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) AS lastChanged,
|
||||
sq.syncId AS lastChanged,
|
||||
COALESCE(
|
||||
(
|
||||
SELECT TOP 1 p.hash
|
||||
@@ -374,7 +431,14 @@ SELECT TOP (@limit)
|
||||
co.id AS companyId,
|
||||
co.root_category_id AS rootCategoryId,
|
||||
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 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
|
||||
@@ -388,8 +452,7 @@ WHERE EXISTS (
|
||||
WHERE ca.article_id = a.id
|
||||
AND pc.posterminal_id = @posterminalId
|
||||
)
|
||||
AND (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) > @cursor
|
||||
ORDER BY (DATEDIFF_BIG(second, '1970-01-01', a.updated_at) * 1000000 + (a.id % 1000000)) ASC
|
||||
ORDER BY sq.syncId ASC
|
||||
`;
|
||||
|
||||
export const LIST_POS_PRODUCT_CATEGORIES = `
|
||||
@@ -411,13 +474,19 @@ SELECT TOP (@limit)
|
||||
cg.name,
|
||||
CASE WHEN cg.id = co.poscustomergroup_id THEN '1' ELSE '0' END AS standard,
|
||||
'0.000000' AS discountPercent,
|
||||
DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) AS lastChanged
|
||||
FROM customergroup cg
|
||||
sq.syncId AS lastChanged
|
||||
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 company co ON co.id = pt.company_id
|
||||
WHERE cg.name <> 'Lieferanten'
|
||||
AND DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) > @cursor
|
||||
ORDER BY DATEDIFF_BIG(second, '1970-01-01', cg.updated_at) ASC, cg.id ASC
|
||||
ORDER BY sq.syncId ASC
|
||||
`;
|
||||
|
||||
export const LIST_POS_CUSTOMERS = `
|
||||
@@ -469,13 +538,19 @@ SELECT TOP (@limit)
|
||||
WHERE ec.entity_id = e.id AND ec.contacttype IN ('email', 'mail')
|
||||
ORDER BY ec.sort ASC, ec.id ASC
|
||||
) AS email,
|
||||
DATEDIFF_BIG(second, '1970-01-01', e.updated_at) AS lastChanged
|
||||
FROM entity e
|
||||
sq.syncId AS lastChanged
|
||||
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 posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
|
||||
WHERE cg.name <> 'Lieferanten'
|
||||
AND DATEDIFF_BIG(second, '1970-01-01', e.updated_at) > @cursor
|
||||
ORDER BY DATEDIFF_BIG(second, '1970-01-01', e.updated_at) ASC, e.id ASC
|
||||
ORDER BY sq.syncId ASC
|
||||
`;
|
||||
|
||||
|
||||
@@ -541,6 +616,34 @@ FROM posterminal pt
|
||||
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 = `
|
||||
SELECT COUNT(*) AS count
|
||||
FROM posterminalobituary
|
||||
|
||||
109
src/providers/mssql/queries/printeragent.js
Normal file
109
src/providers/mssql/queries/printeragent.js
Normal file
@@ -0,0 +1,109 @@
|
||||
export const PRINTERAGENT_COLUMNS = `
|
||||
id,
|
||||
company_id AS companyId,
|
||||
name,
|
||||
token,
|
||||
pairingpin,
|
||||
paired_at AS pairedAt,
|
||||
agentversion,
|
||||
lastseen_at AS lastseenAt,
|
||||
updated_at AS updatedAt,
|
||||
sort,
|
||||
enabled
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
${PRINTERAGENT_COLUMNS}
|
||||
FROM printeragent
|
||||
WHERE company_id = @companyId
|
||||
ORDER BY sort, id
|
||||
`;
|
||||
|
||||
export const GET_BY_COMPANY_AND_ID = `
|
||||
SELECT
|
||||
${PRINTERAGENT_COLUMNS}
|
||||
FROM printeragent
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const GET_BY_ID = `
|
||||
SELECT
|
||||
${PRINTERAGENT_COLUMNS}
|
||||
FROM printeragent
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const FIND_BY_PAIRINGPIN = `
|
||||
SELECT
|
||||
${PRINTERAGENT_COLUMNS}
|
||||
FROM printeragent
|
||||
WHERE pairingpin = @pairingpin
|
||||
`;
|
||||
|
||||
export const FIND_BY_TOKEN = `
|
||||
SELECT
|
||||
${PRINTERAGENT_COLUMNS}
|
||||
FROM printeragent
|
||||
WHERE token = @token
|
||||
`;
|
||||
|
||||
export const PAIR = `
|
||||
UPDATE printeragent
|
||||
SET token = @token,
|
||||
pairingpin = NULL,
|
||||
paired_at = @pairedAt,
|
||||
agentversion = @agentversion,
|
||||
lastseen_at = SYSUTCDATETIME(),
|
||||
updated_at = SYSUTCDATETIME()
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const UPDATE_STATE = `
|
||||
UPDATE printeragent
|
||||
SET clientstate = @clientstate,
|
||||
lastseen_at = SYSUTCDATETIME(),
|
||||
updated_at = SYSUTCDATETIME()
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const TOUCH_LASTSEEN = `
|
||||
UPDATE printeragent
|
||||
SET lastseen_at = SYSUTCDATETIME()
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const TOUCH_LASTSEEN_AND_VERSION = `
|
||||
UPDATE printeragent
|
||||
SET lastseen_at = SYSUTCDATETIME(),
|
||||
agentversion = @agentversion
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO printeragent (company_id, name, pairingpin, sort, enabled)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.company_id AS companyId,
|
||||
INSERTED.name,
|
||||
INSERTED.token,
|
||||
INSERTED.pairingpin,
|
||||
INSERTED.paired_at AS pairedAt,
|
||||
INSERTED.agentversion,
|
||||
INSERTED.lastseen_at AS lastseenAt,
|
||||
INSERTED.updated_at AS updatedAt,
|
||||
INSERTED.sort,
|
||||
INSERTED.enabled
|
||||
VALUES (@companyId, @name, @pairingpin, @sort, @enabled)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE printeragent
|
||||
SET name = @name, sort = @sort, enabled = @enabled, updated_at = SYSUTCDATETIME()
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE FROM printeragent
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
115
src/providers/mssql/queries/printerjob.js
Normal file
115
src/providers/mssql/queries/printerjob.js
Normal file
@@ -0,0 +1,115 @@
|
||||
export const PRINTERJOB_COLUMNS = `
|
||||
pj.id,
|
||||
pj.agentprinter_id AS agentprinterId,
|
||||
pj.type,
|
||||
pj.state,
|
||||
pj.data,
|
||||
pj.created_at AS createdAt,
|
||||
pj.completed_at AS completedAt
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO printerjob (agentprinter_id, type, state, data, created_at)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.agentprinter_id AS agentprinterId,
|
||||
INSERTED.type,
|
||||
INSERTED.state,
|
||||
INSERTED.data,
|
||||
INSERTED.created_at AS createdAt,
|
||||
INSERTED.completed_at AS completedAt
|
||||
VALUES (@agentprinterId, @type, @state, @data, SYSUTCDATETIME())
|
||||
`;
|
||||
|
||||
export const GET_BY_ID_FOR_COMPANY = `
|
||||
SELECT
|
||||
${PRINTERJOB_COLUMNS},
|
||||
ap.printeragent_id AS printeragentId
|
||||
FROM printerjob pj
|
||||
JOIN agentprinter ap ON ap.id = pj.agentprinter_id
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE pj.id = @id AND pa.company_id = @companyId
|
||||
`;
|
||||
|
||||
export const GET_BY_ID_FOR_AGENT = `
|
||||
SELECT
|
||||
${PRINTERJOB_COLUMNS},
|
||||
ap.printeragent_id AS printeragentId
|
||||
FROM printerjob pj
|
||||
JOIN agentprinter ap ON ap.id = pj.agentprinter_id
|
||||
WHERE pj.id = @id AND ap.printeragent_id = @agentId
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
${PRINTERJOB_COLUMNS},
|
||||
ap.printeragent_id AS printeragentId
|
||||
FROM printerjob pj
|
||||
JOIN agentprinter ap ON ap.id = pj.agentprinter_id
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE pa.company_id = @companyId
|
||||
ORDER BY pj.id DESC
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY_AND_AGENTPRINTER = `
|
||||
SELECT
|
||||
${PRINTERJOB_COLUMNS},
|
||||
ap.printeragent_id AS printeragentId
|
||||
FROM printerjob pj
|
||||
JOIN agentprinter ap ON ap.id = pj.agentprinter_id
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE pa.company_id = @companyId AND pj.agentprinter_id = @agentprinterId
|
||||
ORDER BY pj.id DESC
|
||||
`;
|
||||
|
||||
export const LIST_BY_AGENT = `
|
||||
SELECT
|
||||
${PRINTERJOB_COLUMNS},
|
||||
ap.printeragent_id AS printeragentId
|
||||
FROM printerjob pj
|
||||
JOIN agentprinter ap ON ap.id = pj.agentprinter_id
|
||||
WHERE ap.printeragent_id = @agentId
|
||||
ORDER BY pj.id ASC
|
||||
`;
|
||||
|
||||
export const LIST_BY_AGENT_AND_STATE = `
|
||||
SELECT
|
||||
${PRINTERJOB_COLUMNS},
|
||||
ap.printeragent_id AS printeragentId
|
||||
FROM printerjob pj
|
||||
JOIN agentprinter ap ON ap.id = pj.agentprinter_id
|
||||
WHERE ap.printeragent_id = @agentId AND pj.state = @state
|
||||
ORDER BY pj.id ASC
|
||||
`;
|
||||
|
||||
export const UPDATE_STATE_FOR_AGENT = `
|
||||
UPDATE printerjob
|
||||
SET
|
||||
state = @state,
|
||||
completed_at = CASE WHEN @state IN ('completed', 'cancelled') THEN SYSUTCDATETIME() ELSE completed_at END
|
||||
WHERE id = @id AND agentprinter_id IN (
|
||||
SELECT ap.id FROM agentprinter ap WHERE ap.printeragent_id = @agentId
|
||||
)
|
||||
`;
|
||||
|
||||
export const UPDATE_STATE_FOR_COMPANY = `
|
||||
UPDATE printerjob
|
||||
SET
|
||||
state = @state,
|
||||
completed_at = CASE WHEN @state IN ('completed', 'cancelled') THEN SYSUTCDATETIME() ELSE completed_at END
|
||||
WHERE id = @id AND agentprinter_id IN (
|
||||
SELECT ap.id FROM agentprinter ap
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE pa.company_id = @companyId
|
||||
)
|
||||
`;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE FROM printerjob
|
||||
WHERE id = @id AND agentprinter_id IN (
|
||||
SELECT ap.id FROM agentprinter ap
|
||||
JOIN printeragent pa ON pa.id = ap.printeragent_id
|
||||
WHERE pa.company_id = @companyId
|
||||
)
|
||||
`;
|
||||
|
||||
25
src/providers/mssql/queries/retoure.js
Normal file
25
src/providers/mssql/queries/retoure.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export const RETOURE_TYPE = 'retoure';
|
||||
|
||||
export const GET_RETOURE_HEADER = `
|
||||
SELECT
|
||||
m.id,
|
||||
m.[number],
|
||||
m.[type],
|
||||
m.entity_id AS entityId,
|
||||
m.external_number AS externalNumber,
|
||||
m.[date],
|
||||
m.created_at AS createdAt,
|
||||
rs.positions,
|
||||
rs.gross_total AS grossTotal,
|
||||
rs.paid_total AS paidTotal,
|
||||
rs.ordered_quantity AS orderedQuantity,
|
||||
rs.shipped_quantity AS shippedQuantity,
|
||||
rs.is_paid AS isPaid,
|
||||
rs.is_shipped AS isShipped,
|
||||
rs.customer_name AS customerName
|
||||
FROM manifest m
|
||||
LEFT JOIN retouresummary rs ON rs.manifest_id = m.id
|
||||
WHERE m.id = @id
|
||||
AND m.company_id = @companyId
|
||||
AND m.[type] = @type
|
||||
`;
|
||||
79
src/providers/mssql/queries/return.js
Normal file
79
src/providers/mssql/queries/return.js
Normal file
@@ -0,0 +1,79 @@
|
||||
export const RETURN_TYPE = 'return';
|
||||
|
||||
export const GET_RETURN_HEADER = `
|
||||
SELECT
|
||||
m.id,
|
||||
m.[number],
|
||||
m.[type],
|
||||
m.entity_id AS supplierId,
|
||||
m.external_number AS externalNumber,
|
||||
m.[date],
|
||||
m.created_at AS createdAt,
|
||||
rs.positions,
|
||||
rs.gross_total AS grossTotal,
|
||||
rs.net_total AS netTotal,
|
||||
rs.total_quantity AS totalQuantity,
|
||||
rs.stock_positions AS stockPositions,
|
||||
rs.booked_positions AS bookedPositions,
|
||||
rs.unbooked_positions AS unbookedPositions,
|
||||
rs.stock_quantity AS stockQuantity,
|
||||
rs.booked_quantity AS bookedQuantity,
|
||||
rs.unbooked_quantity AS unbookedQuantity,
|
||||
rs.is_booked AS isBooked,
|
||||
rs.supplier_name AS supplierName
|
||||
FROM manifest m
|
||||
LEFT JOIN returnsummary rs ON rs.manifest_id = m.id
|
||||
WHERE m.id = @id
|
||||
AND m.company_id = @companyId
|
||||
AND m.[type] = @type
|
||||
`;
|
||||
|
||||
export const GET_RETURN_LINES = `
|
||||
SELECT
|
||||
ma.id,
|
||||
ma.sku,
|
||||
ma.name,
|
||||
ma.pu,
|
||||
ma.amount,
|
||||
ma.net,
|
||||
ma.rebate,
|
||||
ma.taxrate,
|
||||
ma.comment
|
||||
FROM manifestarticle ma
|
||||
WHERE ma.manifest_id = @manifestId
|
||||
ORDER BY ma.id
|
||||
`;
|
||||
|
||||
export const LIST_RETURNS = `
|
||||
SELECT
|
||||
m.id,
|
||||
m.[number],
|
||||
m.[type],
|
||||
m.entity_id AS supplierId,
|
||||
m.external_number AS externalNumber,
|
||||
m.[date],
|
||||
m.created_at AS createdAt,
|
||||
COUNT(ma.id) AS totalPositions,
|
||||
COUNT(CASE WHEN ma.id IS NOT NULL AND (
|
||||
(ma.amount > 0 AND COALESCE(ma_ledger.booked_amount, 0) >= ma.amount) OR
|
||||
(ma.amount <= 0 AND COALESCE(ma_ledger.booked_amount, 0) > 0)
|
||||
) THEN 1 END) AS completelyOnLedgerPositions
|
||||
FROM manifest m
|
||||
LEFT JOIN manifestarticle ma ON ma.manifest_id = m.id
|
||||
LEFT JOIN (
|
||||
SELECT manifestarticle_id, SUM(amount) AS booked_amount
|
||||
FROM ledger
|
||||
GROUP BY manifestarticle_id
|
||||
) ma_ledger ON ma_ledger.manifestarticle_id = ma.id
|
||||
WHERE m.company_id = @companyId
|
||||
AND m.[type] = @type
|
||||
GROUP BY
|
||||
m.id,
|
||||
m.[number],
|
||||
m.[type],
|
||||
m.entity_id,
|
||||
m.external_number,
|
||||
m.[date],
|
||||
m.created_at
|
||||
ORDER BY m.[date] DESC, m.id DESC
|
||||
`;
|
||||
@@ -3,7 +3,7 @@ export const SHIPMENT_TYPE = 'shipment';
|
||||
export const GET_ORDER_HEADER = `
|
||||
SELECT id, company_id AS companyId, [number], [type], entity_id AS entityId, external_number AS externalNumber, [date]
|
||||
FROM manifest
|
||||
WHERE id = @orderId AND company_id = @companyId AND [type] = 'order';
|
||||
WHERE id = @orderId AND company_id = @companyId AND [type] IN ('order', 'retoure');
|
||||
`;
|
||||
|
||||
export const GET_ORDER_LINES = `
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { SCHEMA_VERSION } from './migrate.js';
|
||||
|
||||
export const INIT_META = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'meta')
|
||||
BEGIN
|
||||
@@ -11,7 +13,7 @@ END
|
||||
IF NOT EXISTS (SELECT 1 FROM meta WHERE [key] = N'version')
|
||||
BEGIN
|
||||
INSERT INTO meta ([key], value)
|
||||
VALUES (N'version', N'1');
|
||||
VALUES (N'version', N'${SCHEMA_VERSION}');
|
||||
END
|
||||
`;
|
||||
|
||||
@@ -97,10 +99,8 @@ BEGIN
|
||||
thumbhash NVARCHAR(64) NOT NULL,
|
||||
smallthumbw INT NOT NULL,
|
||||
smallthumbh INT NOT NULL,
|
||||
source NVARCHAR(MAX) NOT NULL,
|
||||
fullbinary VARBINARY(MAX) NOT NULL,
|
||||
thumbbinary VARBINARY(MAX) NOT NULL,
|
||||
smallthumbbinary VARBINARY(MAX) NOT NULL,
|
||||
source NVARCHAR(MAX) NOT NULL,
|
||||
CONSTRAINT UQ_pic_hash UNIQUE (hash)
|
||||
);
|
||||
END
|
||||
@@ -360,6 +360,16 @@ BEGIN
|
||||
ALTER TABLE article ADD track_stock BIT NOT NULL CONSTRAINT DF_article_track_stock DEFAULT 0;
|
||||
END
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_article_sku' AND object_id = OBJECT_ID('article'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_article_sku ON article (sku);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_article_parentarticle_id' AND object_id = OBJECT_ID('article'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_article_parentarticle_id ON article (parentarticle_id);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLEHISTORY = `
|
||||
@@ -387,7 +397,7 @@ BEGIN
|
||||
[date] DATETIME2 NOT NULL CONSTRAINT DF_companyhistory_date DEFAULT SYSUTCDATETIME(),
|
||||
[text] NVARCHAR(MAX) NOT NULL,
|
||||
CONSTRAINT FK_companyhistory_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_companyhistory_companyuser FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE SET NULL
|
||||
CONSTRAINT FK_companyhistory_companyuser FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE NO ACTION
|
||||
);
|
||||
END
|
||||
`;
|
||||
@@ -409,6 +419,23 @@ BEGIN
|
||||
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 = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articleean')
|
||||
@@ -624,6 +651,18 @@ BEGIN
|
||||
CREATE INDEX IX_articlesupplier_article_entity
|
||||
ON articlesupplier (article_id, entity_id);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_articlesupplier_sku' AND object_id = OBJECT_ID('articlesupplier'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_articlesupplier_sku
|
||||
ON articlesupplier (sku, article_id);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_articlesupplier_entity' AND object_id = OBJECT_ID('articlesupplier'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_articlesupplier_entity
|
||||
ON articlesupplier (entity_id);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_MANIFEST = `
|
||||
@@ -648,6 +687,12 @@ BEGIN
|
||||
CREATE UNIQUE NONCLUSTERED INDEX UQ_manifest_company_type_number
|
||||
ON manifest (company_id, [type], [number]);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_manifest_entity_id' AND object_id = OBJECT_ID('manifest'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_manifest_entity_id
|
||||
ON manifest (entity_id);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_MANIFESTARTICLE = `
|
||||
@@ -668,6 +713,18 @@ BEGIN
|
||||
CONSTRAINT FK_manifestarticle_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_manifestarticle_manifest_id' AND object_id = OBJECT_ID('manifestarticle'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_manifestarticle_manifest_id
|
||||
ON manifestarticle (manifest_id);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_manifestarticle_sku' AND object_id = OBJECT_ID('manifestarticle'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_manifestarticle_sku
|
||||
ON manifestarticle (sku);
|
||||
END
|
||||
`;
|
||||
|
||||
|
||||
@@ -706,6 +763,18 @@ BEGIN
|
||||
CONSTRAINT FK_ledger_warehouse FOREIGN KEY (warehouse_id) REFERENCES warehouse(id) ON DELETE NO ACTION
|
||||
) WITH (LEDGER = ON (APPEND_ONLY = ON));
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_ledger_manifestarticle_warehouse' AND object_id = OBJECT_ID('ledger'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_ledger_manifestarticle_warehouse
|
||||
ON ledger (manifestarticle_id, warehouse_id);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_ledger_warehouse_id' AND object_id = OBJECT_ID('ledger'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_ledger_warehouse_id
|
||||
ON ledger (warehouse_id);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_NUMBERS = `
|
||||
@@ -797,6 +866,22 @@ END
|
||||
');
|
||||
`;
|
||||
|
||||
export const INIT_AUTOINVITE = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'autoinvite')
|
||||
BEGIN
|
||||
CREATE TABLE autoinvite (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
email NVARCHAR(255) NOT NULL,
|
||||
role NVARCHAR(10) NOT NULL CONSTRAINT DF_autoinvite_role DEFAULT 'user',
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_autoinvite_created_at DEFAULT SYSUTCDATETIME(),
|
||||
CONSTRAINT CK_autoinvite_role CHECK (role IN ('admin', 'user')),
|
||||
CONSTRAINT UQ_autoinvite_company_email UNIQUE (company_id, email),
|
||||
CONSTRAINT FK_autoinvite_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_COMPANYUSER = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'companyuser')
|
||||
BEGIN
|
||||
@@ -804,6 +889,8 @@ BEGIN
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
google_auth_id INT NOT NULL,
|
||||
company_id INT NOT NULL,
|
||||
role NVARCHAR(10) NOT NULL CONSTRAINT DF_companyuser_role DEFAULT 'user',
|
||||
CONSTRAINT CK_companyuser_role CHECK (role IN ('admin', 'user')),
|
||||
CONSTRAINT UQ_companyuser_google_auth_company UNIQUE (google_auth_id, company_id),
|
||||
CONSTRAINT FK_companyuser_google_auth FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_companyuser_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
@@ -899,6 +986,9 @@ BEGIN
|
||||
sync_root_articles BIT NOT NULL CONSTRAINT DF_posterminal_sync_root_articles DEFAULT 1,
|
||||
lastseen_at DATETIME2 NULL,
|
||||
updated_at DATETIME2 NOT NULL CONSTRAINT DF_posterminal_updated_at DEFAULT SYSUTCDATETIME(),
|
||||
sort INT NOT NULL CONSTRAINT DF_posterminal_sort DEFAULT 0,
|
||||
enabled BIT NOT NULL CONSTRAINT DF_posterminal_enabled DEFAULT 1,
|
||||
clientversion NVARCHAR(255) NULL,
|
||||
CONSTRAINT FK_posterminal_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_posterminal_root_category FOREIGN KEY (root_category_id) REFERENCES category(id) ON DELETE SET NULL
|
||||
);
|
||||
@@ -939,6 +1029,63 @@ END
|
||||
`;
|
||||
|
||||
|
||||
export const INIT_PRINTERAGENT = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'printeragent')
|
||||
BEGIN
|
||||
CREATE TABLE printeragent (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
name NVARCHAR(255) NOT NULL,
|
||||
token NVARCHAR(255) NULL,
|
||||
pairingpin NVARCHAR(50) NULL,
|
||||
paired_at DATETIME2 NULL,
|
||||
agentversion NVARCHAR(255) NULL,
|
||||
lastseen_at DATETIME2 NULL,
|
||||
updated_at DATETIME2 NOT NULL CONSTRAINT DF_printeragent_updated_at DEFAULT SYSUTCDATETIME(),
|
||||
sort INT NOT NULL CONSTRAINT DF_printeragent_sort DEFAULT 0,
|
||||
enabled BIT NOT NULL CONSTRAINT DF_printeragent_enabled DEFAULT 1,
|
||||
CONSTRAINT FK_printeragent_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX UQ_printeragent_token ON printeragent(token) WHERE token IS NOT NULL;
|
||||
CREATE UNIQUE INDEX UQ_printeragent_pairingpin ON printeragent(pairingpin) WHERE pairingpin IS NOT NULL;
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_AGENTPRINTER = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'agentprinter')
|
||||
BEGIN
|
||||
CREATE TABLE agentprinter (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
printeragent_id INT NOT NULL,
|
||||
type NVARCHAR(50) NOT NULL,
|
||||
model NVARCHAR(255) NULL,
|
||||
serial NVARCHAR(255) NULL,
|
||||
enabled BIT NOT NULL CONSTRAINT DF_agentprinter_enabled DEFAULT 1,
|
||||
sort INT NOT NULL CONSTRAINT DF_agentprinter_sort DEFAULT 0,
|
||||
CONSTRAINT FK_agentprinter_printeragent FOREIGN KEY (printeragent_id) REFERENCES printeragent(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IX_agentprinter_printeragent_id ON agentprinter(printeragent_id);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_PRINTERJOB = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'printerjob')
|
||||
BEGIN
|
||||
CREATE TABLE printerjob (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
agentprinter_id INT NOT NULL,
|
||||
type NVARCHAR(50) NOT NULL,
|
||||
state NVARCHAR(50) NOT NULL CONSTRAINT DF_printerjob_state DEFAULT 'pending',
|
||||
data NVARCHAR(MAX) NOT NULL,
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_printerjob_created_at DEFAULT SYSUTCDATETIME(),
|
||||
completed_at DATETIME2 NULL,
|
||||
CONSTRAINT FK_printerjob_agentprinter FOREIGN KEY (agentprinter_id) REFERENCES agentprinter(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IX_printerjob_agentprinter_id ON printerjob(agentprinter_id);
|
||||
CREATE INDEX IX_printerjob_state ON printerjob(state);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_LABELTEMPLATE = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'labeltemplate')
|
||||
BEGIN
|
||||
@@ -1141,10 +1288,17 @@ BEGIN
|
||||
manifest_id INT NOT NULL,
|
||||
[type] NVARCHAR(100) NOT NULL,
|
||||
amount FLOAT NOT NULL,
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_payment_created_at DEFAULT SYSUTCDATETIME(),
|
||||
CONSTRAINT FK_payment_account FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_payment_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_payment_manifest_id' AND object_id = OBJECT_ID('payment'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX IX_payment_manifest_id
|
||||
ON payment (manifest_id);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_CARRIER = `
|
||||
@@ -1341,6 +1495,87 @@ BEGIN
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_RETURNSUMMARY = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'returnsummary')
|
||||
BEGIN
|
||||
CREATE TABLE returnsummary (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
manifest_id INT NOT NULL CONSTRAINT UQ_returnsummary_manifest UNIQUE,
|
||||
return_number NVARCHAR(100) NOT NULL,
|
||||
external_number NVARCHAR(100) NULL,
|
||||
return_date DATETIME2 NOT NULL,
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_returnsummary_created_at DEFAULT SYSUTCDATETIME(),
|
||||
supplier_id INT NULL,
|
||||
supplier_name NVARCHAR(255) NULL,
|
||||
positions INT NOT NULL CONSTRAINT DF_returnsummary_positions DEFAULT 0,
|
||||
gross_total FLOAT NOT NULL CONSTRAINT DF_returnsummary_gross_total DEFAULT 0,
|
||||
net_total FLOAT NOT NULL CONSTRAINT DF_returnsummary_net_total DEFAULT 0,
|
||||
total_quantity FLOAT NOT NULL CONSTRAINT DF_returnsummary_total_quantity DEFAULT 0,
|
||||
stock_positions INT NOT NULL CONSTRAINT DF_returnsummary_stock_positions DEFAULT 0,
|
||||
booked_positions INT NOT NULL CONSTRAINT DF_returnsummary_booked_positions DEFAULT 0,
|
||||
unbooked_positions INT NOT NULL CONSTRAINT DF_returnsummary_unbooked_positions DEFAULT 0,
|
||||
stock_quantity FLOAT NOT NULL CONSTRAINT DF_returnsummary_stock_quantity DEFAULT 0,
|
||||
booked_quantity FLOAT NOT NULL CONSTRAINT DF_returnsummary_booked_quantity DEFAULT 0,
|
||||
unbooked_quantity FLOAT NOT NULL CONSTRAINT DF_returnsummary_unbooked_quantity DEFAULT 0,
|
||||
is_booked TINYINT NOT NULL CONSTRAINT DF_returnsummary_is_booked DEFAULT 0,
|
||||
CONSTRAINT FK_returnsummary_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||
CONSTRAINT FK_returnsummary_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_returnsummary_supplier FOREIGN KEY (supplier_id) REFERENCES entity(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IX_returnsummary_company_date
|
||||
ON returnsummary (company_id, return_date DESC, manifest_id DESC);
|
||||
|
||||
CREATE INDEX IX_returnsummary_company_number
|
||||
ON returnsummary (company_id, return_number);
|
||||
|
||||
CREATE INDEX IX_returnsummary_company_booked
|
||||
ON returnsummary (company_id, is_booked);
|
||||
|
||||
CREATE INDEX IX_returnsummary_company_supplier
|
||||
ON returnsummary (company_id, supplier_id);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_RETOURESUMMARY = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'retouresummary')
|
||||
BEGIN
|
||||
CREATE TABLE retouresummary (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
manifest_id INT NOT NULL CONSTRAINT UQ_retouresummary_manifest UNIQUE,
|
||||
retoure_number NVARCHAR(100) NOT NULL,
|
||||
retoure_date DATETIME2 NOT NULL,
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_retouresummary_created_at DEFAULT SYSUTCDATETIME(),
|
||||
entity_id INT NULL,
|
||||
customer_name NVARCHAR(255) NULL,
|
||||
positions INT NOT NULL CONSTRAINT DF_retouresummary_positions DEFAULT 0,
|
||||
gross_total FLOAT NOT NULL CONSTRAINT DF_retouresummary_gross_total DEFAULT 0,
|
||||
paid_total FLOAT NOT NULL CONSTRAINT DF_retouresummary_paid_total DEFAULT 0,
|
||||
ordered_quantity FLOAT NOT NULL CONSTRAINT DF_retouresummary_ordered_quantity DEFAULT 0,
|
||||
shipped_quantity FLOAT NOT NULL CONSTRAINT DF_retouresummary_shipped_quantity DEFAULT 0,
|
||||
is_paid TINYINT NOT NULL CONSTRAINT DF_retouresummary_is_paid DEFAULT 0,
|
||||
is_shipped TINYINT NOT NULL CONSTRAINT DF_retouresummary_is_shipped DEFAULT 0,
|
||||
CONSTRAINT FK_retouresummary_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||
CONSTRAINT FK_retouresummary_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_retouresummary_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IX_retouresummary_company_date
|
||||
ON retouresummary (company_id, retoure_date DESC, manifest_id DESC);
|
||||
|
||||
CREATE INDEX IX_retouresummary_company_shipped
|
||||
ON retouresummary (company_id, is_shipped);
|
||||
|
||||
CREATE INDEX IX_retouresummary_company_paid
|
||||
ON retouresummary (company_id, is_paid);
|
||||
|
||||
CREATE INDEX IX_retouresummary_company_number
|
||||
ON retouresummary (company_id, retoure_number);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLEWAREHOUSESUMMARY = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlewarehousesummary')
|
||||
BEGIN
|
||||
@@ -1494,6 +1729,7 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_GET_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
|
||||
INIT_PEEK_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
|
||||
INIT_COMPANYUSER,
|
||||
INIT_AUTOINVITE,
|
||||
INIT_MODELCONVERSATION,
|
||||
INIT_MODELMESSAGE,
|
||||
INIT_MODELVFS,
|
||||
@@ -1504,7 +1740,11 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_POSTERMINAL,
|
||||
INIT_POSTERMINALCATEGORY,
|
||||
INIT_POSTERMINALOBITUARY,
|
||||
INIT_POSTERMINALSYNCQUEUE,
|
||||
INIT_PRINTER,
|
||||
INIT_PRINTERAGENT,
|
||||
INIT_AGENTPRINTER,
|
||||
INIT_PRINTERJOB,
|
||||
INIT_LABELTEMPLATE,
|
||||
INIT_FILE,
|
||||
INIT_MANIFESTATTACHMENT,
|
||||
@@ -1518,6 +1758,8 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_QUOTESUMMARY,
|
||||
INIT_SHIPMENTSUMMARY,
|
||||
INIT_DELIVERYSUMMARY,
|
||||
INIT_RETURNSUMMARY,
|
||||
INIT_RETOURESUMMARY,
|
||||
INIT_ARTICLEWAREHOUSESUMMARY,
|
||||
INIT_MCP_CLIENT,
|
||||
INIT_MCP_AUTH_CODE,
|
||||
|
||||
17
src/providers/mssql/upgrade/1_to_2.js
Normal file
17
src/providers/mssql/upgrade/1_to_2.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Schema 1 → 2 (MSSQL): posterminal gets `sort`, `enabled`, `clientversion`.
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(1);
|
||||
|
||||
await query(`
|
||||
ALTER TABLE posterminal ADD
|
||||
sort INT NOT NULL CONSTRAINT DF_posterminal_sort DEFAULT 0,
|
||||
enabled BIT NOT NULL CONSTRAINT DF_posterminal_enabled DEFAULT 1,
|
||||
clientversion NVARCHAR(255) NULL;
|
||||
`);
|
||||
|
||||
await setVersion(2);
|
||||
}
|
||||
|
||||
20
src/providers/mssql/upgrade/2_to_3.js
Normal file
20
src/providers/mssql/upgrade/2_to_3.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { INIT_PRINTERAGENT } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 2 → 3 (MSSQL): adds the `printeragent` table.
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(2);
|
||||
|
||||
await query(INIT_PRINTERAGENT);
|
||||
|
||||
await query(`
|
||||
IF OBJECT_ID(N'dbo.printeragent', N'U') IS NOT NULL AND COL_LENGTH('printeragent', 'sort') IS NULL
|
||||
BEGIN
|
||||
ALTER TABLE printeragent ADD sort INT NOT NULL CONSTRAINT DF_printeragent_sort DEFAULT 0;
|
||||
END
|
||||
`);
|
||||
|
||||
await setVersion(3);
|
||||
}
|
||||
31
src/providers/mssql/upgrade/3_to_4.js
Normal file
31
src/providers/mssql/upgrade/3_to_4.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { INIT_AGENTPRINTER } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 3 → 4 (MSSQL):
|
||||
* - Drops clientstate, clientmodel, clientserial from printeragent
|
||||
* - Adds enabled to printeragent
|
||||
* - Creates agentprinter table
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(3);
|
||||
|
||||
await query(`
|
||||
IF OBJECT_ID(N'dbo.printeragent', N'U') IS NOT NULL
|
||||
BEGIN
|
||||
IF COL_LENGTH('printeragent', 'clientstate') IS NOT NULL
|
||||
ALTER TABLE printeragent DROP COLUMN clientstate;
|
||||
IF COL_LENGTH('printeragent', 'clientmodel') IS NOT NULL
|
||||
ALTER TABLE printeragent DROP COLUMN clientmodel;
|
||||
IF COL_LENGTH('printeragent', 'clientserial') IS NOT NULL
|
||||
ALTER TABLE printeragent DROP COLUMN clientserial;
|
||||
IF COL_LENGTH('printeragent', 'enabled') IS NULL
|
||||
ALTER TABLE printeragent ADD enabled BIT NOT NULL CONSTRAINT DF_printeragent_enabled DEFAULT 1;
|
||||
END
|
||||
`);
|
||||
|
||||
await query(INIT_AGENTPRINTER);
|
||||
|
||||
await setVersion(4);
|
||||
}
|
||||
|
||||
15
src/providers/mssql/upgrade/4_to_5.js
Normal file
15
src/providers/mssql/upgrade/4_to_5.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { INIT_PRINTERJOB } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 4 → 5 (MSSQL):
|
||||
* - Creates printerjob table
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(4);
|
||||
|
||||
await query(INIT_PRINTERJOB);
|
||||
|
||||
await setVersion(5);
|
||||
}
|
||||
|
||||
25
src/providers/mssql/upgrade/5_to_6.js
Normal file
25
src/providers/mssql/upgrade/5_to_6.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { INIT_AUTOINVITE } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 5 → 6 (MSSQL):
|
||||
* - Adds companyuser.role ('admin' | 'user'); existing members become 'admin'
|
||||
* (they had full access before roles existed)
|
||||
* - Creates autoinvite table
|
||||
* @param {{ query: (sql: string, params?: object) => Promise<unknown>, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ query, assertVersion, setVersion }) {
|
||||
await assertVersion(5);
|
||||
|
||||
await query(`
|
||||
ALTER TABLE companyuser ADD
|
||||
role NVARCHAR(10) NOT NULL CONSTRAINT DF_companyuser_role DEFAULT 'user';
|
||||
`);
|
||||
await query(`
|
||||
ALTER TABLE companyuser ADD CONSTRAINT CK_companyuser_role CHECK (role IN ('admin', 'user'));
|
||||
`);
|
||||
await query(`UPDATE companyuser SET role = 'admin';`);
|
||||
|
||||
await query(INIT_AUTOINVITE);
|
||||
|
||||
await setVersion(6);
|
||||
}
|
||||
198
src/providers/schemaMigrations.js
Normal file
198
src/providers/schemaMigrations.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* Versioned schema migrations shared by the MSSQL and SQLite providers.
|
||||
*
|
||||
* Layout (per provider):
|
||||
* src/providers/<provider>/upgrade/<from>_to_<to>.js (to = from + 1)
|
||||
* src/providers/<provider>/downgrade/<from>_to_<to>.js (to = from - 1)
|
||||
*
|
||||
* Each step file default-exports `async function (ctx)`. `ctx` always has:
|
||||
* - `getVersion()` → current `meta.version` (number)
|
||||
* - `assertVersion(expected)` → throws unless `meta.version === expected`
|
||||
* - `setVersion(version)` → writes `meta.version`
|
||||
* plus provider-specific handles (`db`/`exec` for SQLite, `query` for MSSQL).
|
||||
*
|
||||
* A step must assert its start version and finish by setting its end version;
|
||||
* the runner verifies the end version and rolls back on mismatch. Each step
|
||||
* runs in its own transaction.
|
||||
*
|
||||
* Fresh databases are created from `schema.js` (always the latest shape) and
|
||||
* stamped with `SCHEMA_VERSION` (= highest upgrade target) by `INIT_META`.
|
||||
* Existing databases are upgraded step by step at provider `initialize()`.
|
||||
*
|
||||
* Adding a version N → N+1:
|
||||
* 1. Add `upgrade/N_to_N+1.js` and `downgrade/N+1_to_N.js` for BOTH providers.
|
||||
* 2. Update both `schema.js` files so a fresh DB has the new shape.
|
||||
* `SCHEMA_VERSION` is derived from the upgrade folder — nothing else to bump.
|
||||
*/
|
||||
|
||||
import { readdirSync } from 'node:fs';
|
||||
|
||||
/** Version of a database that has never run a versioned upgrade. */
|
||||
export const BASE_SCHEMA_VERSION = 1;
|
||||
|
||||
const STEP_FILE = /^(\d+)_to_(\d+)\.js$/;
|
||||
|
||||
/**
|
||||
* Lists and validates the step files of one direction.
|
||||
* @param {URL} dirUrl - Directory URL (trailing slash).
|
||||
* @param {'upgrade'|'downgrade'} direction
|
||||
* @returns {Array<{ from: number, to: number, file: string, url: URL }>} Sorted by `from` (asc for upgrade, desc for downgrade).
|
||||
*/
|
||||
export function listSteps(dirUrl, direction) {
|
||||
let names;
|
||||
try {
|
||||
names = readdirSync(dirUrl);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') return [];
|
||||
throw err;
|
||||
}
|
||||
|
||||
const delta = direction === 'upgrade' ? 1 : -1;
|
||||
const steps = [];
|
||||
const seen = new Set();
|
||||
for (const file of names) {
|
||||
if (!file.endsWith('.js')) continue;
|
||||
const match = STEP_FILE.exec(file);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid schema ${direction} file name: ${file} (expected <from>_to_<to>.js)`);
|
||||
}
|
||||
const from = Number(match[1]);
|
||||
const to = Number(match[2]);
|
||||
if (to !== from + delta) {
|
||||
throw new Error(`Invalid schema ${direction} step ${file}: must go from N to N${delta > 0 ? '+' : '-'}1`);
|
||||
}
|
||||
if (seen.has(from)) {
|
||||
throw new Error(`Duplicate schema ${direction} step starting at version ${from}`);
|
||||
}
|
||||
seen.add(from);
|
||||
steps.push({ from, to, file, url: new URL(file, dirUrl) });
|
||||
}
|
||||
return steps.sort((a, b) => (a.from - b.from) * delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highest schema version reachable through a gap-free upgrade chain starting
|
||||
* at `BASE_SCHEMA_VERSION`.
|
||||
* @param {URL} upgradeDirUrl
|
||||
* @returns {number}
|
||||
*/
|
||||
export function latestSchemaVersion(upgradeDirUrl) {
|
||||
let version = BASE_SCHEMA_VERSION;
|
||||
for (const step of listSteps(upgradeDirUrl, 'upgrade')) {
|
||||
if (step.from !== version) {
|
||||
throw new Error(`Schema upgrade chain broken: expected ${version}_to_${version + 1}.js, found ${step.file}`);
|
||||
}
|
||||
version = step.to;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a `meta.version` value.
|
||||
* @param {unknown} value
|
||||
* @returns {number}
|
||||
*/
|
||||
export function parseSchemaVersion(value) {
|
||||
const version = Number(value);
|
||||
if (!Number.isInteger(version) || version < BASE_SCHEMA_VERSION) {
|
||||
throw new Error(`Invalid schema version in meta table: ${value}`);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the version helpers every step receives.
|
||||
* @param {string} file - Step file name (for error messages).
|
||||
* @param {() => Promise<number>|number} getVersion
|
||||
* @param {(version: number) => Promise<void>|void} setVersion
|
||||
*/
|
||||
export function makeVersionContext(file, getVersion, setVersion) {
|
||||
return {
|
||||
getVersion: async () => getVersion(),
|
||||
assertVersion: async (expected) => {
|
||||
const actual = await getVersion();
|
||||
if (actual !== expected) {
|
||||
throw new Error(`Schema step ${file}: expected database version ${expected}, found ${actual}`);
|
||||
}
|
||||
},
|
||||
setVersion: async (version) => {
|
||||
if (!Number.isInteger(version) || version < BASE_SCHEMA_VERSION) {
|
||||
throw new Error(`Schema step ${file}: invalid version ${version}`);
|
||||
}
|
||||
await setVersion(version);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the steps that move the database from `current` to `target`.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {'upgrade'|'downgrade'} options.direction
|
||||
* @param {URL} options.dirUrl - Step directory for this direction.
|
||||
* @param {number} options.current - Current database version.
|
||||
* @param {number} options.target - Target version.
|
||||
* @param {(step: { from: number, to: number, file: string }, fn: (ctx: object) => Promise<void>) => Promise<void>} options.runStep
|
||||
* Provider hook: runs `fn(ctx)` in a transaction and verifies the end version.
|
||||
* @param {(message: string) => void} [options.log]
|
||||
* @returns {Promise<number>} Number of steps applied.
|
||||
*/
|
||||
export async function migrateSchema({ direction, dirUrl, current, target, runStep, log = console.log }) {
|
||||
if (direction === 'upgrade' && current > target) {
|
||||
throw new Error(
|
||||
`Refusing to start: database schema version (${current}) is higher than the highest version in the upgrade folder (${target}). ` +
|
||||
`The database was updated by a newer version of the application. Please update the application code or downgrade the database schema.`
|
||||
);
|
||||
}
|
||||
if (direction === 'downgrade' && target > current) {
|
||||
throw new Error(`Cannot downgrade schema from version ${current} to higher version ${target}`);
|
||||
}
|
||||
if (target < BASE_SCHEMA_VERSION) {
|
||||
throw new Error(`Cannot ${direction} schema below version ${BASE_SCHEMA_VERSION}`);
|
||||
}
|
||||
|
||||
if (current === target) {
|
||||
log?.(`Schema version ${current} is up to date (no migrations needed)`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const byFrom = new Map(listSteps(dirUrl, direction).map((step) => [step.from, step]));
|
||||
const plan = [];
|
||||
for (let version = current; version !== target; ) {
|
||||
const step = byFrom.get(version);
|
||||
if (!step) {
|
||||
const next = direction === 'upgrade' ? version + 1 : version - 1;
|
||||
throw new Error(`Missing schema ${direction} step ${version}_to_${next}.js`);
|
||||
}
|
||||
plan.push(step);
|
||||
version = step.to;
|
||||
}
|
||||
|
||||
log?.(`Starting schema ${direction}: version ${current} -> ${target} (${plan.length} step(s) pending)`);
|
||||
|
||||
for (const step of plan) {
|
||||
const start = performance.now();
|
||||
const mod = await import(step.url.href);
|
||||
if (typeof mod.default !== 'function') {
|
||||
throw new Error(`Schema ${direction} step ${step.file} must default-export a function`);
|
||||
}
|
||||
await runStep(step, mod.default);
|
||||
const duration = Math.round(performance.now() - start);
|
||||
log?.(`Applied schema ${direction} ${step.from} -> ${step.to} (${step.file}) [${duration}ms]`);
|
||||
}
|
||||
|
||||
log?.(`Schema ${direction} completed successfully: database now at version ${target}`);
|
||||
return plan.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws unless the version a step left behind matches its target.
|
||||
* @param {{ file: string, to: number }} step
|
||||
* @param {number} actual
|
||||
*/
|
||||
export function assertStepResult(step, actual) {
|
||||
if (actual !== step.to) {
|
||||
throw new Error(`Schema step ${step.file} must end with version ${step.to}, found ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
16
src/providers/sqlite3/downgrade/2_to_1.js
Normal file
16
src/providers/sqlite3/downgrade/2_to_1.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Schema 2 → 1 (SQLite): drops posterminal `sort`, `enabled`, `clientversion`.
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(2);
|
||||
|
||||
exec(`
|
||||
ALTER TABLE posterminal DROP COLUMN clientversion;
|
||||
ALTER TABLE posterminal DROP COLUMN enabled;
|
||||
ALTER TABLE posterminal DROP COLUMN sort;
|
||||
`);
|
||||
|
||||
await setVersion(1);
|
||||
}
|
||||
|
||||
11
src/providers/sqlite3/downgrade/3_to_2.js
Normal file
11
src/providers/sqlite3/downgrade/3_to_2.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Schema 3 → 2 (SQLite): drops the `printeragent` table.
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(3);
|
||||
|
||||
exec(`DROP TABLE IF EXISTS printeragent;`);
|
||||
|
||||
await setVersion(2);
|
||||
}
|
||||
28
src/providers/sqlite3/downgrade/4_to_3.js
Normal file
28
src/providers/sqlite3/downgrade/4_to_3.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Schema 4 → 3 (SQLite):
|
||||
* - Drops agentprinter table
|
||||
* - Removes enabled from printeragent
|
||||
* - Restores clientserial, clientmodel, clientstate in printeragent
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(4);
|
||||
|
||||
exec(`DROP TABLE IF EXISTS agentprinter;`);
|
||||
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent DROP COLUMN enabled;`);
|
||||
} catch {}
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent ADD COLUMN clientserial TEXT;`);
|
||||
} catch {}
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent ADD COLUMN clientmodel TEXT;`);
|
||||
} catch {}
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent ADD COLUMN clientstate TEXT;`);
|
||||
} catch {}
|
||||
|
||||
await setVersion(3);
|
||||
}
|
||||
|
||||
13
src/providers/sqlite3/downgrade/5_to_4.js
Normal file
13
src/providers/sqlite3/downgrade/5_to_4.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Schema 5 → 4 (SQLite):
|
||||
* - Drops printerjob table
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(5);
|
||||
|
||||
exec(`DROP TABLE IF EXISTS printerjob;`);
|
||||
|
||||
await setVersion(4);
|
||||
}
|
||||
|
||||
15
src/providers/sqlite3/downgrade/6_to_5.js
Normal file
15
src/providers/sqlite3/downgrade/6_to_5.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Schema 6 → 5 (SQLite):
|
||||
* - Drops autoinvite table
|
||||
* - Drops companyuser.role
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function downgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(6);
|
||||
|
||||
exec(`DROP TABLE IF EXISTS autoinvite;`);
|
||||
|
||||
exec(`ALTER TABLE companyuser DROP COLUMN role;`);
|
||||
|
||||
await setVersion(5);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import Database from 'better-sqlite3';
|
||||
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||
import { logSlowQuery } from '../provider.js';
|
||||
import { SCHEMA_SCRIPTS } from './schema.js';
|
||||
import { upgradeSchema, downgradeSchema, readSchemaVersion } from './migrate.js';
|
||||
|
||||
const asyncTxStorage = new AsyncLocalStorage();
|
||||
|
||||
@@ -51,89 +52,15 @@ function normalizeInsertId(value) {
|
||||
import { dirname, join } from 'node:path';
|
||||
import { mkdirSync, mkdtempSync, promises as fsPromises } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import {
|
||||
extFromMimetype,
|
||||
safeWrite,
|
||||
findFileByHash,
|
||||
safeReadByHash,
|
||||
safeDeleteByHash,
|
||||
} from '../storage.js';
|
||||
|
||||
export function extFromMimetype(mimetype) {
|
||||
if (!mimetype) return '';
|
||||
const type = mimetype.trim().toLowerCase().split(';')[0];
|
||||
const map = {
|
||||
'application/pdf': '.pdf',
|
||||
'text/plain': '.txt',
|
||||
'text/csv': '.csv',
|
||||
'text/html': '.html',
|
||||
'text/markdown': '.md',
|
||||
'image/avif': '.avif',
|
||||
'image/png': '.png',
|
||||
'image/jpeg': '.jpg',
|
||||
'image/jpg': '.jpg',
|
||||
'image/webp': '.webp',
|
||||
'image/gif': '.gif',
|
||||
'image/svg+xml': '.svg',
|
||||
'application/json': '.json',
|
||||
'application/zip': '.zip',
|
||||
'application/xml': '.xml',
|
||||
'application/octet-stream': '.bin',
|
||||
};
|
||||
if (map[type]) return map[type];
|
||||
if (type.startsWith('image/')) return '.' + type.slice(6).replace('+xml', '');
|
||||
if (type.startsWith('text/')) return '.' + type.slice(5);
|
||||
return '';
|
||||
}
|
||||
|
||||
async function safeWrite(filePath, buffer) {
|
||||
if (buffer == null) return;
|
||||
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
||||
await fsPromises.writeFile(filePath, buf);
|
||||
}
|
||||
|
||||
async function findFileByHash(dir, hash, ext = '') {
|
||||
if (!dir || !hash) return null;
|
||||
if (ext) {
|
||||
const candidate = join(dir, hash + ext);
|
||||
try {
|
||||
await fsPromises.access(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
// fallback
|
||||
}
|
||||
}
|
||||
const candidateNoExt = join(dir, hash);
|
||||
try {
|
||||
await fsPromises.access(candidateNoExt);
|
||||
return candidateNoExt;
|
||||
} catch {
|
||||
// fallback
|
||||
}
|
||||
try {
|
||||
const files = await fsPromises.readdir(dir);
|
||||
const match = files.find((f) => f === hash || f.startsWith(hash + '.'));
|
||||
if (match) return join(dir, match);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function safeReadByHash(dir, hash, ext = '') {
|
||||
const filePath = await findFileByHash(dir, hash, ext);
|
||||
if (!filePath) return null;
|
||||
try {
|
||||
return await fsPromises.readFile(filePath);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function safeDeleteByHash(dir, hash) {
|
||||
if (!dir || !hash) return;
|
||||
try {
|
||||
const files = await fsPromises.readdir(dir);
|
||||
const matches = files.filter((f) => f === hash || f.startsWith(hash + '.'));
|
||||
await Promise.all(matches.map((f) => fsPromises.unlink(join(dir, f))));
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
}
|
||||
}
|
||||
export { extFromMimetype };
|
||||
|
||||
export class BetterSqlite3Executor {
|
||||
/**
|
||||
@@ -430,9 +357,14 @@ export class BetterSqlite3Provider extends BetterSqlite3Executor {
|
||||
|
||||
/**
|
||||
* Initializes the native SQLite schema.
|
||||
* @param {((message: string) => void) | { log?: (message: string) => void }} [options]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
async initialize(options = {}) {
|
||||
const log = typeof options === 'function' ? options : options?.log;
|
||||
// Versioned upgrades first, so schema.js never meets an outdated table shape.
|
||||
await upgradeSchema(this.db, log);
|
||||
|
||||
for (const script of SCHEMA_SCRIPTS) {
|
||||
if (script && script.trim()) {
|
||||
this.db.exec(script);
|
||||
@@ -494,11 +426,23 @@ export class BetterSqlite3Provider extends BetterSqlite3Executor {
|
||||
this.db.exec(`UPDATE category SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE updated_at IS NULL`);
|
||||
}
|
||||
|
||||
const paymentCols = this.db.pragma('table_info("payment")').map((c) => c.name);
|
||||
if (paymentCols.length > 0 && !paymentCols.includes('created_at')) {
|
||||
this.db.exec('ALTER TABLE payment ADD COLUMN created_at TEXT');
|
||||
this.db.exec('UPDATE payment SET created_at = (SELECT m.created_at FROM manifest m WHERE m.id = payment.manifest_id) WHERE EXISTS (SELECT 1 FROM manifest m WHERE m.id = payment.manifest_id)');
|
||||
this.db.exec('UPDATE payment SET created_at = strftime("%Y-%m-%dT%H:%M:%fZ", "now") WHERE created_at IS NULL');
|
||||
}
|
||||
|
||||
const customergroupCols = this.db.pragma('table_info("customergroup")').map((c) => c.name);
|
||||
if (customergroupCols.length > 0 && !customergroupCols.includes('updated_at')) {
|
||||
this.db.exec(`ALTER TABLE customergroup ADD COLUMN updated_at TEXT`);
|
||||
this.db.exec(`UPDATE customergroup SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE updated_at IS NULL`);
|
||||
}
|
||||
|
||||
const printeragentCols = this.db.pragma('table_info("printeragent")').map((c) => c.name);
|
||||
if (printeragentCols.length > 0 && !printeragentCols.includes('sort')) {
|
||||
this.db.exec(`ALTER TABLE printeragent ADD COLUMN sort INTEGER NOT NULL DEFAULT 0`);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -542,6 +486,24 @@ export class BetterSqlite3Provider extends BetterSqlite3Executor {
|
||||
}
|
||||
|
||||
/** Explicitly closes the database. */
|
||||
/**
|
||||
* Current `meta.version`, or `null` if the database has none.
|
||||
* @returns {Promise<number|null>}
|
||||
*/
|
||||
async getSchemaVersion() {
|
||||
return readSchemaVersion(this.db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs downgrade steps down to `target`. CLI only (`npm run downgrade`).
|
||||
* @param {number} target
|
||||
* @param {(message: string) => void} [log]
|
||||
* @returns {Promise<number>} Steps applied.
|
||||
*/
|
||||
async downgradeSchema(target, log) {
|
||||
return downgradeSchema(this.db, target, log);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.db.close();
|
||||
}
|
||||
|
||||
125
src/providers/sqlite3/migrate.js
Normal file
125
src/providers/sqlite3/migrate.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* SQLite schema versioning (see ../schemaMigrations.js).
|
||||
*
|
||||
* Step context: `{ db, exec, getVersion, assertVersion, setVersion }` where
|
||||
* `db` is the raw better-sqlite3 Database and `exec(sql)` runs DDL/DML.
|
||||
* Each step runs in `BEGIN IMMEDIATE … COMMIT` with foreign key enforcement
|
||||
* switched off (SQLite's recommended procedure for table rebuilds);
|
||||
* `PRAGMA foreign_key_check` must pass before commit.
|
||||
*/
|
||||
|
||||
import {
|
||||
BASE_SCHEMA_VERSION,
|
||||
assertStepResult,
|
||||
latestSchemaVersion,
|
||||
makeVersionContext,
|
||||
migrateSchema,
|
||||
parseSchemaVersion,
|
||||
} from '../schemaMigrations.js';
|
||||
|
||||
export const UPGRADE_DIR = new URL('./upgrade/', import.meta.url);
|
||||
export const DOWNGRADE_DIR = new URL('./downgrade/', import.meta.url);
|
||||
|
||||
/** Schema version a fresh database is created at. */
|
||||
export const SCHEMA_VERSION = latestSchemaVersion(UPGRADE_DIR);
|
||||
|
||||
function hasTable(db, name) {
|
||||
return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(name);
|
||||
}
|
||||
|
||||
function hasAnyTable(db) {
|
||||
return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads `meta.version`, or `null` when the meta table/row is missing.
|
||||
* @param {import('better-sqlite3').Database} db
|
||||
* @returns {number|null}
|
||||
*/
|
||||
export function readSchemaVersion(db) {
|
||||
if (!hasTable(db, 'meta')) return null;
|
||||
const row = db.prepare(`SELECT value FROM meta WHERE "key" = 'version'`).get();
|
||||
return row ? parseSchemaVersion(row.value) : null;
|
||||
}
|
||||
|
||||
function writeSchemaVersion(db, version) {
|
||||
db.prepare(`INSERT INTO meta ("key", value) VALUES ('version', ?)
|
||||
ON CONFLICT("key") DO UPDATE SET value = excluded.value`).run(String(version));
|
||||
}
|
||||
|
||||
function makeRunStep(db) {
|
||||
return async (step, fn) => {
|
||||
const fkWasOn = db.pragma('foreign_keys', { simple: true }) === 1;
|
||||
if (fkWasOn) db.pragma('foreign_keys = OFF');
|
||||
db.exec('BEGIN IMMEDIATE');
|
||||
try {
|
||||
const ctx = {
|
||||
db,
|
||||
exec: (sql) => db.exec(sql),
|
||||
...makeVersionContext(step.file, () => readSchemaVersion(db), (v) => writeSchemaVersion(db, v)),
|
||||
};
|
||||
await fn(ctx);
|
||||
assertStepResult(step, readSchemaVersion(db));
|
||||
const violations = db.pragma('foreign_key_check');
|
||||
if (violations.length > 0) {
|
||||
throw new Error(`Schema step ${step.file} left ${violations.length} foreign key violation(s)`);
|
||||
}
|
||||
db.exec('COMMIT');
|
||||
} catch (err) {
|
||||
if (db.inTransaction) db.exec('ROLLBACK');
|
||||
throw err;
|
||||
} finally {
|
||||
if (fkWasOn) db.pragma('foreign_keys = ON');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades an existing database to `SCHEMA_VERSION`. Must run BEFORE the
|
||||
* `schema.js` scripts so they never meet an outdated table shape.
|
||||
* Fresh (empty) databases are skipped — `INIT_META` stamps them.
|
||||
* A non-empty database without a version row is treated as version 1.
|
||||
* @param {import('better-sqlite3').Database} db
|
||||
* @param {(message: string) => void} [log]
|
||||
*/
|
||||
export async function upgradeSchema(db, log) {
|
||||
if (!hasAnyTable(db)) {
|
||||
log?.(`Fresh database detected; schema will initialize directly at version ${SCHEMA_VERSION}`);
|
||||
return 0;
|
||||
}
|
||||
let current = readSchemaVersion(db);
|
||||
if (current === null) {
|
||||
log?.(`Existing database has no version row; setting baseline version ${BASE_SCHEMA_VERSION}`);
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS meta ("key" TEXT PRIMARY KEY, value TEXT NOT NULL)`);
|
||||
writeSchemaVersion(db, BASE_SCHEMA_VERSION);
|
||||
current = BASE_SCHEMA_VERSION;
|
||||
}
|
||||
return migrateSchema({
|
||||
direction: 'upgrade',
|
||||
dirUrl: UPGRADE_DIR,
|
||||
current,
|
||||
target: SCHEMA_VERSION,
|
||||
runStep: makeRunStep(db),
|
||||
log,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Downgrades the database to `target` (CLI only, never at server start).
|
||||
* @param {import('better-sqlite3').Database} db
|
||||
* @param {number} target
|
||||
* @param {(message: string) => void} [log]
|
||||
*/
|
||||
export async function downgradeSchema(db, target, log) {
|
||||
const current = readSchemaVersion(db);
|
||||
if (current === null) throw new Error('Database has no schema version (meta table missing)');
|
||||
return migrateSchema({
|
||||
direction: 'downgrade',
|
||||
dirUrl: DOWNGRADE_DIR,
|
||||
current,
|
||||
target,
|
||||
runStep: makeRunStep(db),
|
||||
log,
|
||||
});
|
||||
}
|
||||
|
||||
15
src/providers/sqlite3/queries/agentprinter.js
Normal file
15
src/providers/sqlite3/queries/agentprinter.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as source from '../../mssql/queries/agentprinter.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'agentprinter');
|
||||
export const {
|
||||
AGENTPRINTER_COLUMNS,
|
||||
CREATE,
|
||||
DELETE_BY_COMPANY_AND_ID,
|
||||
FIND_BY_COMBO,
|
||||
GET_BY_ID,
|
||||
LIST_BY_COMPANY,
|
||||
LIST_BY_PRINTERAGENT,
|
||||
UPDATE,
|
||||
} = queries;
|
||||
|
||||
@@ -13,6 +13,7 @@ export const {
|
||||
SET_PARENT_SORT,
|
||||
SET_IS_PARENT, TOGGLE_PARENT, TOUCH_UPDATED_AT, TOUCH_OPTION_PARENT_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,
|
||||
UPDATE_UNITAMOUNT, UPDATE_UNIT_AND_AMOUNT, UPDATE_WEIGHT,
|
||||
} = queries;
|
||||
@@ -256,41 +257,20 @@ SELECT auth.*,
|
||||
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||
) THEN COALESCE((
|
||||
SELECT CASE
|
||||
WHEN MIN(COALESCE(comp_stock.stock, 0) / ala.count) < 0 THEN 0
|
||||
ELSE CAST(MIN(COALESCE(comp_stock.stock, 0) / ala.count) AS INTEGER)
|
||||
WHEN MIN(COALESCE(aws_comp.stock, 0) / ala.count) < 0 THEN 0
|
||||
ELSE CAST(MIN(COALESCE(aws_comp.stock, 0) / ala.count) AS INTEGER)
|
||||
END
|
||||
FROM articlelistarticle ala
|
||||
LEFT JOIN (
|
||||
SELECT ca.id AS comp_id, SUM(l.amount) AS stock
|
||||
FROM ledger l
|
||||
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||
INNER JOIN article ca ON (
|
||||
ma.sku = ca.sku
|
||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||
)
|
||||
WHERE m.company_id = a.company_id
|
||||
AND l.warehouse_id = w.id
|
||||
GROUP BY ca.id
|
||||
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||
LEFT JOIN articlewarehousesummary aws_comp
|
||||
ON aws_comp.article_id = ala.article_id AND aws_comp.warehouse_id = w.id
|
||||
WHERE ala.articlelist_id = a.articlelist_id
|
||||
), 0)
|
||||
WHEN a.articlelist_id IS NOT NULL THEN 0
|
||||
ELSE COALESCE(stock_calc.stock, 0)
|
||||
ELSE COALESCE(aws.stock, 0)
|
||||
END
|
||||
)), '[]') FROM warehouse w
|
||||
LEFT JOIN (
|
||||
SELECT l.warehouse_id, SUM(l.amount) AS stock
|
||||
FROM ledger l
|
||||
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||
WHERE m.company_id = a.company_id
|
||||
AND (
|
||||
ma.sku = a.sku
|
||||
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
||||
)
|
||||
GROUP BY l.warehouse_id
|
||||
) stock_calc ON stock_calc.warehouse_id = w.id
|
||||
LEFT JOIN articlewarehousesummary aws
|
||||
ON aws.article_id = a.id AND aws.warehouse_id = w.id
|
||||
WHERE w.company_id = a.company_id
|
||||
ORDER BY w.sort ASC, w.name ASC)),
|
||||
'undeliveredOrdersJson', json((
|
||||
@@ -309,15 +289,12 @@ SELECT auth.*,
|
||||
COALESCE(os.customer_name, TRIM(COALESCE(e.first_name || ' ', '') || COALESCE(e.name, ''))) AS customerName,
|
||||
ROUND(SUM(ma.amount) - COALESCE((
|
||||
SELECT SUM(ma_s.amount)
|
||||
FROM manifest m_s
|
||||
INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = m_s.id
|
||||
LEFT JOIN shipmentsummary ss ON ss.manifest_id = m_s.id
|
||||
WHERE m_s.company_id = m.company_id
|
||||
AND m_s."type" = 'shipment'
|
||||
FROM shipmentsummary ss
|
||||
INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = ss.manifest_id
|
||||
WHERE ss.company_id = m.company_id
|
||||
AND ss.order_id = m.id
|
||||
AND COALESCE(ss.state, 'new') != 'cancelled'
|
||||
AND m_s."number" NOT LIKE '%#cancelled#%'
|
||||
AND ma_s.sku = a.sku
|
||||
AND m_s."number" LIKE m."number" || COALESCE((SELECT prefix FROM numbers WHERE company_id = m.company_id AND "type" = 'shipment'), '-L-') || '%'
|
||||
), 0), 3) AS amount
|
||||
FROM manifest m
|
||||
INNER JOIN manifestarticle ma ON ma.manifest_id = m.id
|
||||
@@ -330,15 +307,12 @@ SELECT auth.*,
|
||||
GROUP BY m.id, m."number", m."date", os.customer_name, e.first_name, e.name
|
||||
HAVING (SUM(ma.amount) - COALESCE((
|
||||
SELECT SUM(ma_s.amount)
|
||||
FROM manifest m_s
|
||||
INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = m_s.id
|
||||
LEFT JOIN shipmentsummary ss ON ss.manifest_id = m_s.id
|
||||
WHERE m_s.company_id = m.company_id
|
||||
AND m_s."type" = 'shipment'
|
||||
FROM shipmentsummary ss
|
||||
INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = ss.manifest_id
|
||||
WHERE ss.company_id = m.company_id
|
||||
AND ss.order_id = m.id
|
||||
AND COALESCE(ss.state, 'new') != 'cancelled'
|
||||
AND m_s."number" NOT LIKE '%#cancelled#%'
|
||||
AND ma_s.sku = a.sku
|
||||
AND m_s."number" LIKE m."number" || COALESCE((SELECT prefix FROM numbers WHERE company_id = m.company_id AND "type" = 'shipment'), '-L-') || '%'
|
||||
), 0)) > 0
|
||||
ORDER BY m."date" ASC, m.id ASC
|
||||
) uo
|
||||
|
||||
5
src/providers/sqlite3/queries/autoinvite.js
Normal file
5
src/providers/sqlite3/queries/autoinvite.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/autoinvite.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'autoinvite');
|
||||
export const { ADD, LIST_BY_COMPANY, LIST_BY_EMAIL, GET_IN_COMPANY, GET_BY_COMPANY_EMAIL, DELETE } = queries;
|
||||
@@ -5,11 +5,32 @@ import { ARTICLE_LIST_COLUMNS, ARTICLE_LIST_JOINS } from './article.js';
|
||||
const queries = translateQueries(source, 'category');
|
||||
export const {
|
||||
CREATE, DELETE, GET_BY_PARENT_AND_NAME,
|
||||
GET_CATEGORY_ARTICLE_EANS,
|
||||
GET_CATEGORY_ARTICLE_WAREHOUSE_SUMMARIES,
|
||||
GET_NEXT_SIBLING_SORT, LIST_CHILD_IDS,
|
||||
LIST_CHILD_CATEGORIES,
|
||||
MOVE, UPDATE_SORT,
|
||||
} = queries;
|
||||
|
||||
export const GET_CATEGORY_ARTICLE_PICS = `
|
||||
SELECT
|
||||
a.id AS articleId,
|
||||
p.id AS picId,
|
||||
p.hash,
|
||||
NULL AS buffer
|
||||
FROM categoryarticle ca
|
||||
INNER JOIN article a ON a.id = ca.article_id
|
||||
INNER JOIN pic p ON p.id = (
|
||||
SELECT ap.pic_id FROM articlepic ap
|
||||
WHERE ap.article_id = a.id
|
||||
ORDER BY ap.sort ASC, ap.id ASC
|
||||
LIMIT 1
|
||||
)
|
||||
WHERE ca.category_id = @categoryId
|
||||
AND a.parentarticle_id IS NULL
|
||||
ORDER BY a.id ASC
|
||||
`;
|
||||
|
||||
export const RESOLVE_CATEGORY_ACCESS = `
|
||||
WITH RECURSIVE ancestors AS (
|
||||
SELECT id, parent_id
|
||||
@@ -66,7 +87,6 @@ FROM categoryarticle ca
|
||||
INNER JOIN article a ON a.id = ca.article_id
|
||||
${ARTICLE_LIST_JOINS}
|
||||
WHERE ca.category_id = @categoryId
|
||||
AND a.parentarticle_id IS NULL
|
||||
ORDER BY a.id
|
||||
`;
|
||||
|
||||
@@ -110,3 +130,74 @@ INNER JOIN article a ON a.id = ca.article_id
|
||||
WHERE ca.category_id IN (SELECT id FROM tree)
|
||||
AND a.parentarticle_id IS NULL
|
||||
`;
|
||||
|
||||
export const TOUCH_SUBTREE_CATEGORIES = `
|
||||
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
|
||||
)
|
||||
UPDATE category
|
||||
SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
WHERE id IN (SELECT id FROM cats)
|
||||
`;
|
||||
|
||||
export const TOUCH_SUBTREE_ARTICLES = `
|
||||
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
|
||||
)
|
||||
UPDATE article
|
||||
SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
WHERE id IN (
|
||||
SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats)
|
||||
UNION
|
||||
SELECT child.id FROM article child
|
||||
WHERE child.parentarticle_id IN (
|
||||
SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats)
|
||||
)
|
||||
UNION
|
||||
SELECT parent.parentarticle_id FROM article parent
|
||||
WHERE parent.id IN (
|
||||
SELECT ca.article_id FROM categoryarticle ca WHERE ca.category_id IN (SELECT id FROM cats)
|
||||
) AND parent.parentarticle_id IS NOT NULL
|
||||
)
|
||||
`;
|
||||
|
||||
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
|
||||
`;
|
||||
@@ -2,4 +2,18 @@ import * as source from '../../mssql/queries/companyuser.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'companyuser');
|
||||
export const { ADD, DELETE, GET_BY_EMAIL, GET_COMPANY_ACCESS } = queries;
|
||||
export const {
|
||||
ADD,
|
||||
DELETE,
|
||||
GET_BY_EMAIL,
|
||||
GET_COMPANY_ACCESS,
|
||||
LIST_BY_COMPANY,
|
||||
GET_IN_COMPANY,
|
||||
GET_MEMBERSHIP,
|
||||
COUNT_ADMINS,
|
||||
SET_ROLE,
|
||||
CLEAR_ENTITYHISTORY_USER,
|
||||
CLEAR_COMPANYHISTORY_USER,
|
||||
IS_MEMBER,
|
||||
CLEAR_SELECTED_COMPANY,
|
||||
} = queries;
|
||||
|
||||
@@ -7,8 +7,8 @@ export const {
|
||||
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,
|
||||
LIST_CUSTOMERS_BY_COMPANY, LIST_SUPPLIERS_BY_COMPANY,
|
||||
NUMBER_TAKEN_IN_COMPANY, POS_GROUP_NAME, SUPPLIER_GROUP_NAME, UPDATE,
|
||||
UPDATE_NAME,
|
||||
NUMBER_TAKEN_IN_COMPANY, POS_GROUP_NAME, SUPPLIER_GROUP_NAME,
|
||||
TOUCH_UPDATED_AT, UPDATE, UPDATE_NAME,
|
||||
} = queries;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
|
||||
@@ -3,6 +3,7 @@ SELECT
|
||||
ma.id,
|
||||
ma.sku,
|
||||
ma.amount AS lineAmount,
|
||||
ma.pu AS linePu,
|
||||
ma.net,
|
||||
ma.rebate,
|
||||
ma.taxrate,
|
||||
|
||||
@@ -4,8 +4,8 @@ import { translateQueries } from './translate.js';
|
||||
const queries = translateQueries(source, 'manifestDoc');
|
||||
export const {
|
||||
CHECK_ARTICLE_IS_PARENT, COUNT_MANIFESTS, CREATE_LINE, CREATE_MANIFEST, DELETE_LINES_BY_MANIFEST,
|
||||
DELETE_MANIFEST, GET_MANIFEST_HEADER, GET_MANIFEST_LINES, LIST_MANIFESTS,
|
||||
ORDER_TYPE, QUOTE_TYPE, UPDATE_MANIFEST,
|
||||
DELETE_MANIFEST, GET_ARTICLE_SUPPLIER_LINK, GET_MANIFEST_CONTEXT, GET_MANIFEST_HEADER,
|
||||
GET_MANIFEST_LINES, LIST_MANIFESTS, ORDER_TYPE, QUOTE_TYPE, RETOURE_TYPE, RETURN_TYPE, UPDATE_ARTICLE_SUPPLIER_LINK, UPDATE_MANIFEST,
|
||||
} = queries;
|
||||
|
||||
export const LIST_MANIFESTS_PAGED = `
|
||||
|
||||
@@ -10,7 +10,8 @@ export const {
|
||||
COUNT_CUSTOMER_GROUPS, COUNT_COMPOSITE_PRODUCTS, MAX_ORDER_ID,
|
||||
LIST_POS_PRODUCT_CATEGORIES, LIST_POS_CUSTOMER_GROUP_IDS, LIST_POS_PRODUCT_PRICES,
|
||||
LIST_POS_MANDATED_OPTIONS, LIST_POS_PARENT_OPTIONS,
|
||||
TOUCH_LASTSEEN, RECORD_OBITUARY, COUNT_DELETED_ENTITIES,
|
||||
TOUCH_LASTSEEN, SET_CLIENTVERSION, RECORD_OBITUARY, RECORD_SYNC_QUEUE, SEED_SYNC_QUEUE_FOR_TERMINAL,
|
||||
COUNT_DELETED_ENTITIES,
|
||||
} = queries;
|
||||
|
||||
export const SET_ON = `
|
||||
@@ -50,13 +51,19 @@ SELECT
|
||||
cg.name,
|
||||
CASE WHEN cg.id = co.poscustomergroup_id THEN '1' ELSE '0' END AS standard,
|
||||
'0.000000' AS discountPercent,
|
||||
CAST(strftime('%s', cg.updated_at) AS INTEGER) AS lastChanged
|
||||
FROM customergroup cg
|
||||
sq.syncId AS lastChanged
|
||||
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 company co ON co.id = pt.company_id
|
||||
WHERE cg.name <> 'Lieferanten'
|
||||
AND CAST(strftime('%s', cg.updated_at) AS INTEGER) > @cursor
|
||||
ORDER BY lastChanged ASC, cg.id ASC
|
||||
ORDER BY sq.syncId ASC
|
||||
LIMIT @limit
|
||||
`;
|
||||
|
||||
@@ -116,13 +123,19 @@ SELECT
|
||||
ORDER BY ec.sort ASC, ec.id ASC
|
||||
LIMIT 1
|
||||
) AS email,
|
||||
CAST(strftime('%s', e.updated_at) AS INTEGER) AS lastChanged
|
||||
FROM entity e
|
||||
sq.syncId AS lastChanged
|
||||
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 posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
|
||||
WHERE cg.name <> 'Lieferanten'
|
||||
AND CAST(strftime('%s', e.updated_at) AS INTEGER) > @cursor
|
||||
ORDER BY lastChanged ASC, e.id ASC
|
||||
ORDER BY sq.syncId ASC
|
||||
LIMIT @limit
|
||||
`;
|
||||
|
||||
@@ -141,16 +154,21 @@ SELECT
|
||||
ORDER BY cp.sort ASC, cp.id ASC
|
||||
LIMIT 1
|
||||
) AS imgHash,
|
||||
CAST(strftime('%s', c.updated_at) AS INTEGER) AS lastChanged
|
||||
FROM category c
|
||||
INNER JOIN posterminalcategory pc ON pc.category_id = c.id
|
||||
sq.syncId AS lastChanged
|
||||
FROM (
|
||||
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 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
|
||||
WHERE pc.posterminal_id = @posterminalId
|
||||
AND (co.root_category_id IS NULL OR c.id <> co.root_category_id)
|
||||
AND CAST(strftime('%s', c.updated_at) AS INTEGER) > @cursor
|
||||
ORDER BY lastChanged ASC, c.id ASC
|
||||
WHERE (co.root_category_id IS NULL OR c.id <> co.root_category_id)
|
||||
ORDER BY sq.syncId ASC
|
||||
LIMIT @limit
|
||||
`;
|
||||
|
||||
@@ -162,7 +180,7 @@ SELECT
|
||||
COALESCE(pr.net, 0) AS netPrice,
|
||||
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
||||
a.created_at AS createdAt,
|
||||
(CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) AS lastChanged,
|
||||
sq.syncId AS lastChanged,
|
||||
COALESCE(
|
||||
(
|
||||
SELECT p.hash
|
||||
@@ -265,7 +283,14 @@ SELECT
|
||||
co.id AS companyId,
|
||||
co.root_category_id AS rootCategoryId,
|
||||
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 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
|
||||
@@ -279,8 +304,7 @@ WHERE EXISTS (
|
||||
WHERE ca.article_id = a.id
|
||||
AND pc.posterminal_id = @posterminalId
|
||||
)
|
||||
AND (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) > @cursor
|
||||
ORDER BY (CAST(strftime('%s', a.updated_at) AS INTEGER) * 1000000 + (a.id % 1000000)) ASC
|
||||
ORDER BY sq.syncId ASC
|
||||
LIMIT @limit
|
||||
`;
|
||||
|
||||
|
||||
19
src/providers/sqlite3/queries/printeragent.js
Normal file
19
src/providers/sqlite3/queries/printeragent.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as source from '../../mssql/queries/printeragent.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'printeragent');
|
||||
export const {
|
||||
CREATE,
|
||||
DELETE_BY_COMPANY_AND_ID,
|
||||
FIND_BY_PAIRINGPIN,
|
||||
FIND_BY_TOKEN,
|
||||
GET_BY_COMPANY_AND_ID,
|
||||
GET_BY_ID,
|
||||
LIST_BY_COMPANY,
|
||||
PAIR,
|
||||
PRINTERAGENT_COLUMNS,
|
||||
TOUCH_LASTSEEN,
|
||||
TOUCH_LASTSEEN_AND_VERSION,
|
||||
UPDATE,
|
||||
UPDATE_STATE,
|
||||
} = queries;
|
||||
18
src/providers/sqlite3/queries/printerjob.js
Normal file
18
src/providers/sqlite3/queries/printerjob.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as source from '../../mssql/queries/printerjob.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'printerjob');
|
||||
export const {
|
||||
PRINTERJOB_COLUMNS,
|
||||
CREATE,
|
||||
GET_BY_ID_FOR_COMPANY,
|
||||
GET_BY_ID_FOR_AGENT,
|
||||
LIST_BY_COMPANY,
|
||||
LIST_BY_COMPANY_AND_AGENTPRINTER,
|
||||
LIST_BY_AGENT,
|
||||
LIST_BY_AGENT_AND_STATE,
|
||||
UPDATE_STATE_FOR_AGENT,
|
||||
UPDATE_STATE_FOR_COMPANY,
|
||||
DELETE_BY_COMPANY_AND_ID,
|
||||
} = queries;
|
||||
|
||||
8
src/providers/sqlite3/queries/retoure.js
Normal file
8
src/providers/sqlite3/queries/retoure.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as source from '../../mssql/queries/retoure.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'retoure');
|
||||
export const {
|
||||
GET_RETOURE_HEADER,
|
||||
RETOURE_TYPE,
|
||||
} = queries;
|
||||
10
src/providers/sqlite3/queries/return.js
Normal file
10
src/providers/sqlite3/queries/return.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as source from '../../mssql/queries/return.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'return');
|
||||
export const {
|
||||
GET_RETURN_HEADER,
|
||||
GET_RETURN_LINES,
|
||||
LIST_RETURNS,
|
||||
RETURN_TYPE,
|
||||
} = queries;
|
||||
@@ -31,7 +31,9 @@ export function sqliteQuery(sql) {
|
||||
if (typeof sql !== 'string') return sql;
|
||||
|
||||
let result = quoteIdentifiers(sql)
|
||||
.replace(/\bWITH\s*\(\s*(?:NOLOCK|UPDLOCK|ROWLOCK|HOLDLOCK|READPAST)(?:\s*,\s*(?:NOLOCK|UPDLOCK|ROWLOCK|HOLDLOCK|READPAST))*\s*\)/gi, '')
|
||||
.replace(/\bISNULL\s*\(/gi, 'IFNULL(')
|
||||
.replace(/\bLEN\s*\(/gi, 'LENGTH(')
|
||||
.replace(/\bSYSUTCDATETIME\s*\(\s*\)/gi, "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')")
|
||||
.replace(/\bDATEDIFF_BIG\s*\(\s*second\s*,\s*'1970-01-01'\s*,\s*([^)]+?)\s*\)/gi, "CAST(strftime('%s', $1) AS INTEGER)")
|
||||
.replace(/\bCAST\s*\(([^()]+?)\s+AS\s+INT\s*\)/gi, 'CAST($1 AS INTEGER)')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { SCHEMA_VERSION } from './migrate.js';
|
||||
|
||||
const UTC_NOW = "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
||||
|
||||
export const INIT_META = `
|
||||
@@ -5,7 +7,7 @@ CREATE TABLE IF NOT EXISTS meta (
|
||||
"key" TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
INSERT OR IGNORE INTO meta ("key", value) VALUES ('version', '1');
|
||||
INSERT OR IGNORE INTO meta ("key", value) VALUES ('version', '${SCHEMA_VERSION}');
|
||||
`;
|
||||
|
||||
export const INIT_LANGUAGE = `
|
||||
@@ -243,6 +245,8 @@ CREATE TABLE IF NOT EXISTS article (
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_article_parentarticle_parentsort
|
||||
ON article(parentarticle_id, parentsort)
|
||||
WHERE parentarticle_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS IX_article_sku ON article (sku);
|
||||
CREATE INDEX IF NOT EXISTS IX_article_parentarticle_id ON article (parentarticle_id);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLEEAN = `
|
||||
@@ -417,6 +421,10 @@ CREATE TABLE IF NOT EXISTS articlesupplier (
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_articlesupplier_article_entity
|
||||
ON articlesupplier(article_id, entity_id);
|
||||
CREATE INDEX IF NOT EXISTS IX_articlesupplier_sku
|
||||
ON articlesupplier(sku, article_id);
|
||||
CREATE INDEX IF NOT EXISTS IX_articlesupplier_entity
|
||||
ON articlesupplier(entity_id);
|
||||
`;
|
||||
|
||||
export const INIT_MANIFEST = `
|
||||
@@ -434,6 +442,8 @@ CREATE TABLE IF NOT EXISTS manifest (
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_manifest_company_type_number
|
||||
ON manifest (company_id, "type", "number");
|
||||
CREATE INDEX IF NOT EXISTS IX_manifest_entity_id
|
||||
ON manifest (entity_id);
|
||||
`;
|
||||
|
||||
export const INIT_MANIFESTARTICLE = `
|
||||
@@ -451,6 +461,10 @@ CREATE TABLE IF NOT EXISTS manifestarticle (
|
||||
shippable INTEGER NOT NULL DEFAULT 1,
|
||||
FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_manifestarticle_manifest_id
|
||||
ON manifestarticle (manifest_id);
|
||||
CREATE INDEX IF NOT EXISTS IX_manifestarticle_sku
|
||||
ON manifestarticle (sku);
|
||||
`;
|
||||
|
||||
export const INIT_WAREHOUSE = `
|
||||
@@ -479,6 +493,10 @@ CREATE TABLE IF NOT EXISTS ledger (
|
||||
FOREIGN KEY (manifestarticle_id) REFERENCES manifestarticle(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (warehouse_id) REFERENCES warehouse(id) ON DELETE NO ACTION
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_ledger_manifestarticle_warehouse
|
||||
ON ledger (manifestarticle_id, warehouse_id);
|
||||
CREATE INDEX IF NOT EXISTS IX_ledger_warehouse_id
|
||||
ON ledger (warehouse_id);
|
||||
`;
|
||||
|
||||
export const INIT_NUMBERS = `
|
||||
@@ -494,11 +512,24 @@ CREATE TABLE IF NOT EXISTS numbers (
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_AUTOINVITE = `
|
||||
CREATE TABLE IF NOT EXISTS autoinvite (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
company_id INTEGER NOT NULL,
|
||||
email TEXT NOT NULL COLLATE NOCASE,
|
||||
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'user')),
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
UNIQUE (company_id, email),
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_COMPANYUSER = `
|
||||
CREATE TABLE IF NOT EXISTS companyuser (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
google_auth_id INTEGER NOT NULL,
|
||||
company_id INTEGER NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'user')),
|
||||
UNIQUE (google_auth_id, company_id),
|
||||
FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
@@ -613,6 +644,9 @@ CREATE TABLE IF NOT EXISTS posterminal (
|
||||
sync_root_articles INTEGER NOT NULL DEFAULT 1,
|
||||
lastseen_at TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
clientversion TEXT,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (root_category_id) REFERENCES category(id) ON DELETE SET NULL
|
||||
);
|
||||
@@ -644,6 +678,18 @@ CREATE INDEX IF NOT EXISTS IX_posterminalobituary_posterminal_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 = `
|
||||
CREATE TABLE IF NOT EXISTS printer (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -659,6 +705,59 @@ CREATE TABLE IF NOT EXISTS printer (
|
||||
`;
|
||||
|
||||
|
||||
export const INIT_PRINTERAGENT = `
|
||||
CREATE TABLE IF NOT EXISTS printeragent (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
company_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
token TEXT,
|
||||
pairingpin TEXT,
|
||||
paired_at TEXT,
|
||||
agentversion TEXT,
|
||||
lastseen_at TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_printeragent_token
|
||||
ON printeragent(token) WHERE token IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_printeragent_pairingpin
|
||||
ON printeragent(pairingpin) WHERE pairingpin IS NOT NULL;
|
||||
`;
|
||||
|
||||
export const INIT_AGENTPRINTER = `
|
||||
CREATE TABLE IF NOT EXISTS agentprinter (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
printeragent_id INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
model TEXT,
|
||||
serial TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (printeragent_id) REFERENCES printeragent(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_agentprinter_printeragent_id
|
||||
ON agentprinter(printeragent_id);
|
||||
`;
|
||||
|
||||
export const INIT_PRINTERJOB = `
|
||||
CREATE TABLE IF NOT EXISTS printerjob (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
agentprinter_id INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
state TEXT NOT NULL DEFAULT 'pending',
|
||||
data TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
completed_at TEXT,
|
||||
FOREIGN KEY (agentprinter_id) REFERENCES agentprinter(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_printerjob_agentprinter_id
|
||||
ON printerjob(agentprinter_id);
|
||||
CREATE INDEX IF NOT EXISTS IX_printerjob_state
|
||||
ON printerjob(state);
|
||||
`;
|
||||
|
||||
export const INIT_LABELTEMPLATE = `
|
||||
CREATE TABLE IF NOT EXISTS labeltemplate (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -813,9 +912,12 @@ CREATE TABLE IF NOT EXISTS payment (
|
||||
manifest_id INTEGER NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_payment_manifest_id
|
||||
ON payment (manifest_id);
|
||||
`;
|
||||
|
||||
export const INIT_CARRIER = `
|
||||
@@ -981,6 +1083,73 @@ CREATE INDEX IF NOT EXISTS IX_deliverysummary_company_supplier
|
||||
ON deliverysummary (company_id, supplier_id);
|
||||
`;
|
||||
|
||||
export const INIT_RETURNSUMMARY = `
|
||||
CREATE TABLE IF NOT EXISTS returnsummary (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
company_id INTEGER NOT NULL,
|
||||
manifest_id INTEGER NOT NULL UNIQUE,
|
||||
return_number TEXT NOT NULL,
|
||||
external_number TEXT,
|
||||
return_date TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
supplier_id INTEGER NULL,
|
||||
supplier_name TEXT NULL,
|
||||
positions INTEGER NOT NULL DEFAULT 0,
|
||||
gross_total REAL NOT NULL DEFAULT 0,
|
||||
net_total REAL NOT NULL DEFAULT 0,
|
||||
total_quantity REAL NOT NULL DEFAULT 0,
|
||||
stock_positions INTEGER NOT NULL DEFAULT 0,
|
||||
booked_positions INTEGER NOT NULL DEFAULT 0,
|
||||
unbooked_positions INTEGER NOT NULL DEFAULT 0,
|
||||
stock_quantity REAL NOT NULL DEFAULT 0,
|
||||
booked_quantity REAL NOT NULL DEFAULT 0,
|
||||
unbooked_quantity REAL NOT NULL DEFAULT 0,
|
||||
is_booked INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (supplier_id) REFERENCES entity(id) ON DELETE SET NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_returnsummary_company_date
|
||||
ON returnsummary (company_id, return_date DESC, manifest_id DESC);
|
||||
CREATE INDEX IF NOT EXISTS IX_returnsummary_company_number
|
||||
ON returnsummary (company_id, return_number);
|
||||
CREATE INDEX IF NOT EXISTS IX_returnsummary_company_booked
|
||||
ON returnsummary (company_id, is_booked);
|
||||
CREATE INDEX IF NOT EXISTS IX_returnsummary_company_supplier
|
||||
ON returnsummary (company_id, supplier_id);
|
||||
`;
|
||||
|
||||
export const INIT_RETOURESUMMARY = `
|
||||
CREATE TABLE IF NOT EXISTS retouresummary (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
company_id INTEGER NOT NULL,
|
||||
manifest_id INTEGER NOT NULL UNIQUE,
|
||||
retoure_number TEXT NOT NULL,
|
||||
retoure_date TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
entity_id INTEGER NULL,
|
||||
customer_name TEXT NULL,
|
||||
positions INTEGER NOT NULL DEFAULT 0,
|
||||
gross_total REAL NOT NULL DEFAULT 0,
|
||||
paid_total REAL NOT NULL DEFAULT 0,
|
||||
ordered_quantity REAL NOT NULL DEFAULT 0,
|
||||
shipped_quantity REAL NOT NULL DEFAULT 0,
|
||||
is_paid INTEGER NOT NULL DEFAULT 0,
|
||||
is_shipped INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_retouresummary_company_date
|
||||
ON retouresummary (company_id, retoure_date DESC, manifest_id DESC);
|
||||
CREATE INDEX IF NOT EXISTS IX_retouresummary_company_shipped
|
||||
ON retouresummary (company_id, is_shipped);
|
||||
CREATE INDEX IF NOT EXISTS IX_retouresummary_company_paid
|
||||
ON retouresummary (company_id, is_paid);
|
||||
CREATE INDEX IF NOT EXISTS IX_retouresummary_company_number
|
||||
ON retouresummary (company_id, retoure_number);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLEWAREHOUSESUMMARY = `
|
||||
CREATE TABLE IF NOT EXISTS articlewarehousesummary (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -1110,6 +1279,7 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_LEDGER,
|
||||
INIT_NUMBERS,
|
||||
INIT_COMPANYUSER,
|
||||
INIT_AUTOINVITE,
|
||||
INIT_MODELCONVERSATION,
|
||||
INIT_MODELMESSAGE,
|
||||
INIT_MODELVFS,
|
||||
@@ -1120,7 +1290,11 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_POSTERMINAL,
|
||||
INIT_POSTERMINALCATEGORY,
|
||||
INIT_POSTERMINALOBITUARY,
|
||||
INIT_POSTERMINALSYNCQUEUE,
|
||||
INIT_PRINTER,
|
||||
INIT_PRINTERAGENT,
|
||||
INIT_AGENTPRINTER,
|
||||
INIT_PRINTERJOB,
|
||||
INIT_LABELTEMPLATE,
|
||||
INIT_FILE,
|
||||
INIT_MANIFESTATTACHMENT,
|
||||
@@ -1134,6 +1308,8 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_QUOTESUMMARY,
|
||||
INIT_SHIPMENTSUMMARY,
|
||||
INIT_DELIVERYSUMMARY,
|
||||
INIT_RETURNSUMMARY,
|
||||
INIT_RETOURESUMMARY,
|
||||
INIT_ARTICLEWAREHOUSESUMMARY,
|
||||
INIT_MCP_CLIENT,
|
||||
INIT_MCP_AUTH_CODE,
|
||||
|
||||
16
src/providers/sqlite3/upgrade/1_to_2.js
Normal file
16
src/providers/sqlite3/upgrade/1_to_2.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Schema 1 → 2 (SQLite): posterminal gets `sort`, `enabled`, `clientversion`.
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(1);
|
||||
|
||||
exec(`
|
||||
ALTER TABLE posterminal ADD COLUMN sort INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE posterminal ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1;
|
||||
ALTER TABLE posterminal ADD COLUMN clientversion TEXT;
|
||||
`);
|
||||
|
||||
await setVersion(2);
|
||||
}
|
||||
|
||||
13
src/providers/sqlite3/upgrade/2_to_3.js
Normal file
13
src/providers/sqlite3/upgrade/2_to_3.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { INIT_PRINTERAGENT } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 2 → 3 (SQLite): adds the `printeragent` table.
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(2);
|
||||
|
||||
exec(INIT_PRINTERAGENT);
|
||||
|
||||
await setVersion(3);
|
||||
}
|
||||
30
src/providers/sqlite3/upgrade/3_to_4.js
Normal file
30
src/providers/sqlite3/upgrade/3_to_4.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { INIT_AGENTPRINTER } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 3 → 4 (SQLite):
|
||||
* - Drops clientstate, clientmodel, clientserial from printeragent
|
||||
* - Adds enabled to printeragent
|
||||
* - Creates agentprinter table
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(3);
|
||||
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent DROP COLUMN clientstate;`);
|
||||
} catch {}
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent DROP COLUMN clientmodel;`);
|
||||
} catch {}
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent DROP COLUMN clientserial;`);
|
||||
} catch {}
|
||||
try {
|
||||
exec(`ALTER TABLE printeragent ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1;`);
|
||||
} catch {}
|
||||
|
||||
exec(INIT_AGENTPRINTER);
|
||||
|
||||
await setVersion(4);
|
||||
}
|
||||
|
||||
15
src/providers/sqlite3/upgrade/4_to_5.js
Normal file
15
src/providers/sqlite3/upgrade/4_to_5.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { INIT_PRINTERJOB } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 4 → 5 (SQLite):
|
||||
* - Creates printerjob table
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(4);
|
||||
|
||||
exec(INIT_PRINTERJOB);
|
||||
|
||||
await setVersion(5);
|
||||
}
|
||||
|
||||
19
src/providers/sqlite3/upgrade/5_to_6.js
Normal file
19
src/providers/sqlite3/upgrade/5_to_6.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { INIT_AUTOINVITE } from '../schema.js';
|
||||
|
||||
/**
|
||||
* Schema 5 → 6 (SQLite):
|
||||
* - Adds companyuser.role ('admin' | 'user'); existing members become 'admin'
|
||||
* (they had full access before roles existed)
|
||||
* - Creates autoinvite table
|
||||
* @param {{ exec: (sql: string) => void, assertVersion: (v: number) => Promise<void>, setVersion: (v: number) => Promise<void> }} ctx
|
||||
*/
|
||||
export default async function upgrade({ exec, assertVersion, setVersion }) {
|
||||
await assertVersion(5);
|
||||
|
||||
exec(`ALTER TABLE companyuser ADD COLUMN role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'user'));`);
|
||||
exec(`UPDATE companyuser SET role = 'admin';`);
|
||||
|
||||
exec(INIT_AUTOINVITE);
|
||||
|
||||
await setVersion(6);
|
||||
}
|
||||
89
src/providers/storage.js
Normal file
89
src/providers/storage.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Shared filesystem storage utilities for database providers.
|
||||
*/
|
||||
|
||||
import { join } from 'node:path';
|
||||
import { promises as fsPromises } from 'node:fs';
|
||||
|
||||
export async function safeWrite(filePath, buffer) {
|
||||
if (buffer == null) return;
|
||||
const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
|
||||
await fsPromises.writeFile(filePath, buf);
|
||||
}
|
||||
|
||||
export async function findFileByHash(dir, hash, ext = '') {
|
||||
if (!dir || !hash) return null;
|
||||
if (ext) {
|
||||
const candidate = join(dir, hash + ext);
|
||||
try {
|
||||
await fsPromises.access(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
// fallback
|
||||
}
|
||||
}
|
||||
const candidateNoExt = join(dir, hash);
|
||||
try {
|
||||
await fsPromises.access(candidateNoExt);
|
||||
return candidateNoExt;
|
||||
} catch {
|
||||
// fallback
|
||||
}
|
||||
try {
|
||||
const files = await fsPromises.readdir(dir);
|
||||
const match = files.find((f) => f === hash || f.startsWith(hash + '.'));
|
||||
if (match) return join(dir, match);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function safeReadByHash(dir, hash, ext = '') {
|
||||
const filePath = await findFileByHash(dir, hash, ext);
|
||||
if (!filePath) return null;
|
||||
try {
|
||||
return await fsPromises.readFile(filePath);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function safeDeleteByHash(dir, hash) {
|
||||
if (!dir || !hash) return;
|
||||
try {
|
||||
const files = await fsPromises.readdir(dir);
|
||||
const matches = files.filter((f) => f === hash || f.startsWith(hash + '.'));
|
||||
await Promise.all(matches.map((f) => fsPromises.unlink(join(dir, f))));
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export function extFromMimetype(mimetype) {
|
||||
if (!mimetype) return '';
|
||||
const type = mimetype.trim().toLowerCase().split(';')[0];
|
||||
const map = {
|
||||
'application/pdf': '.pdf',
|
||||
'text/plain': '.txt',
|
||||
'text/csv': '.csv',
|
||||
'text/html': '.html',
|
||||
'text/markdown': '.md',
|
||||
'image/avif': '.avif',
|
||||
'image/png': '.png',
|
||||
'image/jpeg': '.jpg',
|
||||
'image/jpg': '.jpg',
|
||||
'image/webp': '.webp',
|
||||
'image/gif': '.gif',
|
||||
'image/svg+xml': '.svg',
|
||||
'application/json': '.json',
|
||||
'application/zip': '.zip',
|
||||
'application/xml': '.xml',
|
||||
'application/octet-stream': '.bin',
|
||||
};
|
||||
if (map[type]) return map[type];
|
||||
if (type.startsWith('image/')) return '.' + type.slice(6).replace('+xml', '');
|
||||
if (type.startsWith('text/')) return '.' + type.slice(5);
|
||||
return '';
|
||||
}
|
||||
16
src/store/agentprinter/_shared.js
Normal file
16
src/store/agentprinter/_shared.js
Normal file
@@ -0,0 +1,16 @@
|
||||
export function shape(row) {
|
||||
if (!row) return null;
|
||||
return {
|
||||
id: row.id,
|
||||
printeragentId: row.printeragentId,
|
||||
type: row.type,
|
||||
model: row.model ?? null,
|
||||
serial: row.serial ?? null,
|
||||
enabled: Boolean(row.enabled),
|
||||
sort: row.sort,
|
||||
};
|
||||
}
|
||||
|
||||
export const AGENTPRINTER_TYPEDEF =
|
||||
'{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}';
|
||||
|
||||
35
src/store/agentprinter/delete.js
Normal file
35
src/store/agentprinter/delete.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
DELETE_BY_COMPANY_AND_ID as MSSQL_DELETE,
|
||||
} from '../../providers/mssql/queries/agentprinter.js';
|
||||
import {
|
||||
DELETE_BY_COMPANY_AND_ID as SQLITE_DELETE,
|
||||
} from '../../providers/sqlite3/queries/agentprinter.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function del(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
|
||||
|
||||
/**
|
||||
* Deletes an agent printer by id for the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The agent printer id.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number}>}
|
||||
*/
|
||||
return async (googleAuthId, id, tx) => {
|
||||
const numericId = Number(id);
|
||||
if (!Number.isInteger(numericId) || numericId <= 0) {
|
||||
throw new Error('Agentprinter not found');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
await runner.query(sql, { id: numericId, companyId });
|
||||
return { id: numericId };
|
||||
};
|
||||
}
|
||||
|
||||
55
src/store/agentprinter/findOrCreate.js
Normal file
55
src/store/agentprinter/findOrCreate.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
FIND_BY_COMBO as MSSQL_FIND,
|
||||
CREATE as MSSQL_CREATE,
|
||||
} from '../../providers/mssql/queries/agentprinter.js';
|
||||
import {
|
||||
FIND_BY_COMBO as SQLITE_FIND,
|
||||
CREATE as SQLITE_CREATE,
|
||||
} from '../../providers/sqlite3/queries/agentprinter.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { shape } from './_shared.js';
|
||||
|
||||
export default function findOrCreate(provider) {
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const findSql = isSqlite ? SQLITE_FIND : MSSQL_FIND;
|
||||
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
|
||||
|
||||
/**
|
||||
* Finds or creates an agentprinter by (printeragentId, type, model, serial) combo.
|
||||
*
|
||||
* @param {number} printeragentId - The parent printer agent id.
|
||||
* @param {object} input
|
||||
* @param {string} input.type - Printer type ("zpl", "esc/pos", etc.).
|
||||
* @param {string|null} [input.model] - Discovered model name.
|
||||
* @param {string|null} [input.serial] - Discovered serial number.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{created: boolean, agentprinter: {id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}}>}
|
||||
*/
|
||||
return async (printeragentId, input = {}, tx) => {
|
||||
const pId = Number(printeragentId);
|
||||
if (!Number.isInteger(pId) || pId <= 0) {
|
||||
throw new Error('Printeragent not found');
|
||||
}
|
||||
|
||||
const type = typeof input.type === 'string' && input.type.trim() ? input.type.trim().toLowerCase() : 'zpl';
|
||||
const model = input.model != null && String(input.model).trim() ? String(input.model).trim() : null;
|
||||
const serial = input.serial != null && String(input.serial).trim() ? String(input.serial).trim() : null;
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const existing = await runner.query(findSql, { printeragentId: pId, type, model, serial });
|
||||
if (existing.rows.length > 0) {
|
||||
return { created: false, agentprinter: shape(existing.rows[0]) };
|
||||
}
|
||||
|
||||
const created = await runner.query(createSql, {
|
||||
printeragentId: pId,
|
||||
type,
|
||||
model,
|
||||
serial,
|
||||
enabled: 1,
|
||||
sort: 0,
|
||||
});
|
||||
return { created: true, agentprinter: shape(created.rows[0]) };
|
||||
};
|
||||
}
|
||||
|
||||
36
src/store/agentprinter/getById.js
Normal file
36
src/store/agentprinter/getById.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
GET_BY_ID as MSSQL_GET,
|
||||
} from '../../providers/mssql/queries/agentprinter.js';
|
||||
import {
|
||||
GET_BY_ID as SQLITE_GET,
|
||||
} from '../../providers/sqlite3/queries/agentprinter.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { shape } from './_shared.js';
|
||||
|
||||
export default function getById(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const sql = provider?.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||
|
||||
/**
|
||||
* Retrieves an agent printer by id, scoped to the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The agent printer id.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}|null>}
|
||||
*/
|
||||
return async (googleAuthId, id, tx) => {
|
||||
const numericId = Number(id);
|
||||
if (!Number.isInteger(numericId) || numericId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(sql, { id: numericId, companyId });
|
||||
return result.rows[0] ? shape(result.rows[0]) : null;
|
||||
};
|
||||
}
|
||||
|
||||
41
src/store/agentprinter/list.js
Normal file
41
src/store/agentprinter/list.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY,
|
||||
LIST_BY_PRINTERAGENT as MSSQL_LIST_BY_PRINTERAGENT,
|
||||
} from '../../providers/mssql/queries/agentprinter.js';
|
||||
import {
|
||||
LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY,
|
||||
LIST_BY_PRINTERAGENT as SQLITE_LIST_BY_PRINTERAGENT,
|
||||
} from '../../providers/sqlite3/queries/agentprinter.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { shape } from './_shared.js';
|
||||
|
||||
export default function list(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const listCompanySql = isSqlite ? SQLITE_LIST_BY_COMPANY : MSSQL_LIST_BY_COMPANY;
|
||||
const listAgentSql = isSqlite ? SQLITE_LIST_BY_PRINTERAGENT : MSSQL_LIST_BY_PRINTERAGENT;
|
||||
|
||||
/**
|
||||
* Lists agent printers of the user's selected company, optionally filtered by printer agent.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} [printeragentId] - Optional printer agent id filter.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<Array<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}>>}
|
||||
*/
|
||||
return async (googleAuthId, printeragentId, tx) => {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const aId = printeragentId != null ? Number(printeragentId) : null;
|
||||
let result;
|
||||
if (aId && Number.isInteger(aId) && aId > 0) {
|
||||
result = await runner.query(listAgentSql, { companyId, printeragentId: aId });
|
||||
} else {
|
||||
result = await runner.query(listCompanySql, { companyId });
|
||||
}
|
||||
return result.rows.map(shape);
|
||||
};
|
||||
}
|
||||
|
||||
68
src/store/agentprinter/order.js
Normal file
68
src/store/agentprinter/order.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
LIST_BY_COMPANY as MSSQL_LIST_BY_COMPANY,
|
||||
LIST_BY_PRINTERAGENT as MSSQL_LIST_BY_AGENT,
|
||||
UPDATE as MSSQL_UPDATE,
|
||||
} from '../../providers/mssql/queries/agentprinter.js';
|
||||
import {
|
||||
LIST_BY_COMPANY as SQLITE_LIST_BY_COMPANY,
|
||||
LIST_BY_PRINTERAGENT as SQLITE_LIST_BY_AGENT,
|
||||
UPDATE as SQLITE_UPDATE,
|
||||
} from '../../providers/sqlite3/queries/agentprinter.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function order(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const listCompanySql = isSqlite ? SQLITE_LIST_BY_COMPANY : MSSQL_LIST_BY_COMPANY;
|
||||
const listAgentSql = isSqlite ? SQLITE_LIST_BY_AGENT : MSSQL_LIST_BY_AGENT;
|
||||
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
|
||||
|
||||
/**
|
||||
* Reorders agent printers for a given printer agent or globally across the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} input
|
||||
* @param {number} [input.printeragentId] - Optional printer agent id. If omitted, reorders all printers in company.
|
||||
* @param {number[]} input.agentprinterIds - Complete ordered list of agent printer IDs.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{agentprinterIds: number[]}>}
|
||||
*/
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
const hasAgent = input.printeragentId != null;
|
||||
let pId;
|
||||
if (hasAgent) {
|
||||
pId = Number(input.printeragentId);
|
||||
if (!Number.isInteger(pId) || pId <= 0) {
|
||||
throw new Error('Printeragent not found');
|
||||
}
|
||||
}
|
||||
|
||||
const ids = input.agentprinterIds;
|
||||
if (!Array.isArray(ids) || ids.length === 0 || ids.some((id) => !Number.isInteger(Number(id)) || Number(id) <= 0)) {
|
||||
throw new Error('Invalid agentprinter order');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const existing = hasAgent
|
||||
? await runner.query(listAgentSql, { companyId, printeragentId: pId })
|
||||
: await runner.query(listCompanySql, { companyId });
|
||||
const existingIds = new Set(existing.rows.map((r) => r.id));
|
||||
|
||||
if (ids.length !== existingIds.size || ids.some((id) => !existingIds.has(Number(id)))) {
|
||||
throw new Error('Invalid agentprinter order');
|
||||
}
|
||||
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = Number(ids[i]);
|
||||
const current = existing.rows.find((r) => r.id === id);
|
||||
const enabled = current ? (current.enabled ? 1 : 0) : 1;
|
||||
await runner.query(updateSql, { id, companyId, sort: i + 1, enabled });
|
||||
}
|
||||
|
||||
return { agentprinterIds: ids.map(Number) };
|
||||
};
|
||||
}
|
||||
|
||||
63
src/store/agentprinter/update.js
Normal file
63
src/store/agentprinter/update.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
GET_BY_ID as MSSQL_GET,
|
||||
UPDATE as MSSQL_UPDATE,
|
||||
} from '../../providers/mssql/queries/agentprinter.js';
|
||||
import {
|
||||
GET_BY_ID as SQLITE_GET,
|
||||
UPDATE as SQLITE_UPDATE,
|
||||
} from '../../providers/sqlite3/queries/agentprinter.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { shape } from './_shared.js';
|
||||
|
||||
export default function update(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const getSql = isSqlite ? SQLITE_GET : MSSQL_GET;
|
||||
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
|
||||
|
||||
/**
|
||||
* Updates enabled and/or sort of an agent printer for the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} input
|
||||
* @param {number} input.id - Agent printer id.
|
||||
* @param {boolean} [input.enabled] - Whether the printer is enabled.
|
||||
* @param {number} [input.sort] - Display order integer.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, printeragentId: number, type: string, model: string|null, serial: string|null, enabled: boolean, sort: number}>}
|
||||
*/
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
const id = Number(input.id);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw new Error('Agentprinter not found');
|
||||
}
|
||||
|
||||
if (input.enabled === undefined && input.sort === undefined) {
|
||||
throw new Error('Invalid agentprinter');
|
||||
}
|
||||
if (input.enabled !== undefined && typeof input.enabled !== 'boolean') {
|
||||
throw new Error('Invalid agentprinter');
|
||||
}
|
||||
if (input.sort !== undefined && !Number.isInteger(input.sort)) {
|
||||
throw new Error('Invalid agentprinter');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const existing = await runner.query(getSql, { id, companyId });
|
||||
if (!existing.rows[0]) {
|
||||
throw new Error('Agentprinter not found');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
await runner.query(updateSql, { id, companyId, sort, enabled });
|
||||
|
||||
const updated = await runner.query(getSql, { id, companyId });
|
||||
return shape(updated.rows[0]);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -273,6 +273,8 @@ export default function addChild(provider) {
|
||||
|
||||
// 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.ENQUEUE_SYNC_BY_ARTICLE, { articleId: childId });
|
||||
await runner.query(queries.ENQUEUE_SYNC_BY_ARTICLE, { articleId: parentId });
|
||||
|
||||
return load(googleAuthId, childSku, runner);
|
||||
};
|
||||
|
||||
@@ -8,8 +8,14 @@ import {
|
||||
} from '../../providers/sqlite3/queries/articlepic.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 { TOUCH_UPDATED_AT as MSSQL_TOUCH } from '../../providers/mssql/queries/article.js';
|
||||
import { TOUCH_UPDATED_AT as SQLITE_TOUCH } from '../../providers/sqlite3/queries/article.js';
|
||||
import {
|
||||
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 newFullHash from '../../util/newFullHash.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 addArticlePicSql = isSqlite ? SQLITE_ADD_ARTICLEPIC : MSSQL_ADD_ARTICLEPIC;
|
||||
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.
|
||||
@@ -57,14 +64,12 @@ export default function addImage(provider) {
|
||||
if (existingPic.rows[0]) {
|
||||
picId = existingPic.rows[0].id;
|
||||
} else {
|
||||
if (isSqlite) {
|
||||
if (runner.writePicFull) {
|
||||
await runner.writePicFull(fullHash, processed.full.buffer);
|
||||
await runner.writePicThumb(fullHash, processed.thumb.buffer);
|
||||
} else if (provider?.writePicFull) {
|
||||
await provider.writePicFull(fullHash, processed.full.buffer);
|
||||
await provider.writePicThumb(fullHash, processed.thumb.buffer);
|
||||
}
|
||||
if (runner.writePicFull) {
|
||||
await runner.writePicFull(fullHash, processed.full.buffer);
|
||||
await runner.writePicThumb(fullHash, processed.thumb.buffer);
|
||||
} else if (provider?.writePicFull) {
|
||||
await provider.writePicFull(fullHash, processed.full.buffer);
|
||||
await provider.writePicThumb(fullHash, processed.thumb.buffer);
|
||||
}
|
||||
|
||||
const picResult = await runner.query(createPicSql, {
|
||||
@@ -79,8 +84,6 @@ export default function addImage(provider) {
|
||||
smallthumbh: processed.smallThumb.h,
|
||||
smallthumbbinary: processed.smallThumb.buffer,
|
||||
source: '',
|
||||
fullbinary: processed.full.buffer,
|
||||
thumbbinary: processed.thumb.buffer,
|
||||
});
|
||||
|
||||
picId = picResult.rows[0]?.id ?? picResult.insertId;
|
||||
@@ -111,6 +114,7 @@ export default function addImage(provider) {
|
||||
}
|
||||
|
||||
await runner.query(touchArticleSql, { articleId });
|
||||
await runner.query(enqueueSyncSql, { articleId });
|
||||
|
||||
return {
|
||||
picId,
|
||||
|
||||
@@ -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
|
||||
@@ -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 children = await list(googleAuthId, parentSku, runner);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user