Rewrite store JSDoc, split quote/order into modules, and generate typed API docs

- Rewrite JSDoc across all store methods to document the actual async
  methods (real params, returns, throws) instead of the internal wrappers
- Split quote/order into individual module files backed by shared
  manifestDoc helpers; remove the createStore factory and thin wrappers
- Deduplicate manifest line helpers into src/store/manifestLines.js
- Extend generate-api-doc.js to render parameter/return/throws types and
  emit per-namespace API.<module>.md files with an API.md index
- Regenerate all API docs
This commit is contained in:
seb
2026-08-07 06:23:17 +02:00
parent 396389457f
commit aefd97a171
127 changed files with 3956 additions and 1684 deletions

View File

@@ -29,7 +29,9 @@ Wenn ein Vertrag unklar oder fehlt: **nicht weiterwursteln**. Human fragen, Vors
| Repo | Pfad | Inhalt |
|------|------|--------|
| **dieses** | `README.md` | Store-Methoden, Signaturen, Returns — Vertrag für dashback |
| **dieses** | `README.md` | Store-Übersicht, Konventionen, HTTP-Mapping — Vertrag für dashback |
| **dieses** | `API.md` | Store-API-Index (TOC → `API.<modul>.md`) |
| **dieses** | `API.<modul>.md` | pro Namespace: Methoden, Signaturen, JSDoc-Beispiele (`npm run api-doc`) |
| **dieses** | `src/schema.js`, `src/store/` | Schema + Implementierung |
| dashback | `../dashback/docs/api/README.md` | HTTP-Routen-Index |
| dashback | `../dashback/docs/api/routes/` | eine Markdown-Datei pro Endpoint |
@@ -38,6 +40,6 @@ Wenn ein Vertrag unklar oder fehlt: **nicht weiterwursteln**. Human fragen, Vors
## Was der Human / dashback von dir erwarten
1. **README.md aktuell halten**, wenn Store-Methoden oder Returns sich ändern — dashback baut Services darauf.
1. **README.md** und **`API.md` / `API.<modul>.md`** aktuell halten, wenn Store-Methoden oder Returns sich ändern (`npm run api-doc`) — dashback baut Services darauf.
2. Schema-Änderungen so, dass `npm run reset-db` den Zielzustand abbildet; Live-DB nur manuell per `sqlcmd` (Human).
3. Bei API-Bedarf aus dem Frontend: dashback-Docs/Logs lesen; fehlende HTTP-Schicht dem Human melden, nicht in dashback implementieren.

View File

@@ -8,9 +8,22 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Adds an image to an article by its SKU, processing it into AVIF variants.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `image` | `Buffer` | The source image buffer. |
**Returns**
`Promise<{picId: number, sort: number, full: {w: number, h: number, size: number}, thumb: {w: number, h: number}, fullHash: string, thumbHash: string}>` — The added image info.
**Example**
```js
const fn = addImage(pool);
const result = await fn(42, 'SKU-1', imageBuffer);
const result = await store.article.addImage(42, 'SKU-1', imageBuffer);
// => { picId, sort, full: { w, h, size }, thumb: { w, h }, fullHash, thumbHash }
```
@@ -20,9 +33,23 @@ const result = await fn(42, 'SKU-1', imageBuffer);
Creates a new article under a given parent category, allocating a SKU and a default unit.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The article creation input. |
| `input.googleAuthId` | `number` | The id of the Google auth user. |
| `input.parentId` | `number` | The id of the parent category. |
| `input.name` | `string` | The article name. |
**Returns**
`Promise<{id: number, parentId: number, name: string, sku: string}>` — The created article.
**Example**
```js
const fn = createUnderParent(pool);
const article = await fn({ googleAuthId: 42, parentId: 5, name: 'Cola' });
const article = await store.article.createUnderParent({ googleAuthId: 42, parentId: 5, name: 'Cola' });
// => { id, parentId, name, sku }
```
@@ -32,9 +59,25 @@ const article = await fn({ googleAuthId: 42, parentId: 5, name: 'Cola' });
Deletes an article by its SKU, refusing when it still has children.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the article has children or is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 'SKU-1');
await store.article.delete(42, 'SKU-1');
```
## `article.deleteImage`
@@ -43,9 +86,26 @@ await fn(42, 'SKU-1');
Deletes an image from an article by its SKU and full image hash.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `fullHash` | `string` | The full image hash to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the image is not found.
**Example**
```js
const fn = deleteImage(pool);
await fn(42, 'SKU-1', 'abc123');
await store.article.deleteImage(42, 'SKU-1', 'abc123');
```
## `article.getBySku`
@@ -54,9 +114,25 @@ await fn(42, 'SKU-1', 'abc123');
Returns the full detail of an article by its SKU, including unit, brand, categories, photos, EANs and prices.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
**Returns**
`Promise<{article: object}>` — The full article detail.
**Throws**
`Error` — If the article is not found or the user is not authorized.
**Example**
```js
const fn = getBySku(pool);
const article = await fn(42, 'SKU-1');
const article = await store.article.getBySku(42, 'SKU-1');
// => { article: { id, sku, name, unit, brand, categoryIds, photos, eans, prices, ... } }
```
@@ -66,9 +142,21 @@ const article = await fn(42, 'SKU-1');
Returns the change history of an article by its SKU.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
**Returns**
`Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>` — The article history entries.
**Example**
```js
const fn = getHistoryBySku(pool);
const history = await fn(42, 'SKU-1');
const history = await store.article.getHistoryBySku(42, 'SKU-1');
// => [{ id, companyuserId, date, text }]
```
@@ -78,9 +166,25 @@ const history = await fn(42, 'SKU-1');
Returns articles for export, optionally scoped to a category, including category breadcrumbs and image hashes.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `[categoryId]` | `number|null` | Optional category id to scope the export to; defaults to the company root. |
**Returns**
`Promise<Array<object>>` — The articles for export.
**Throws**
`Error` — If the category is not reachable from the selected company.
**Example**
```js
const fn = listExport(pool);
const articles = await fn(42, null);
const articles = await store.article.listExport(42, null);
// => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
```
@@ -88,11 +192,22 @@ const articles = await fn(42, null);
*Source: `src/store/article/listSearchIndex.js`*
Returns a lightweight search index of all articles for a company.
Returns a lightweight search index of all articles for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, sku: string, name: string, eans: Array<string>, categoryIds: Array<number>}>>` — The article search index.
**Example**
```js
const fn = listSearchIndex(pool);
const articles = await fn(42);
const articles = await store.article.listSearchIndex(42);
// => [{ id, sku, name, eans, categoryIds }]
```
@@ -102,9 +217,27 @@ const articles = await fn(42);
Reorders the images of an article by its SKU.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `input` | `object` | The reorder input. |
| `input.fullHashes` | `Array<string>` | The full image hashes in their new order. |
**Returns**
`Promise<{fullHashes: Array<string>}>` — The applied order.
**Throws**
`Error` — If the order is invalid.
**Example**
```js
const fn = orderImages(pool);
const result = await fn(42, 'SKU-1', { fullHashes: ['a', 'b'] });
const result = await store.article.orderImages(42, 'SKU-1', { fullHashes: ['a', 'b'] });
// => { fullHashes: ['a', 'b'] }
```
@@ -114,9 +247,26 @@ const result = await fn(42, 'SKU-1', { fullHashes: ['a', 'b'] });
Sets (or clears) the brand of an article by its SKU.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `brandId` | `number|null` | The brand id, or `null` to clear the brand. |
**Returns**
`Promise<object>` — The updated article.
**Throws**
`Error` — If the brand or article is not found.
**Example**
```js
const fn = setBrandIdBySku(pool);
const article = await fn(42, 'SKU-1', 3);
const article = await store.article.setBrandIdBySku(42, 'SKU-1', 3);
// => { id, sku, name, ... }
```
@@ -126,9 +276,26 @@ const article = await fn(42, 'SKU-1', 3);
Replaces the category membership of an article by its SKU.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `categoryIds` | `Array<number>` | The category ids the article should belong to (at least one). |
**Returns**
`Promise<object>` — The updated article.
**Throws**
`Error` — If the category ids are invalid or the article is not found.
**Example**
```js
const fn = setCategoryIdsBySku(pool);
const article = await fn(42, 'SKU-1', [5, 9]);
const article = await store.article.setCategoryIdsBySku(42, 'SKU-1', [5, 9]);
// => { id, sku, name, ... }
```
@@ -138,9 +305,26 @@ const article = await fn(42, 'SKU-1', [5, 9]);
Replaces the EAN list of an article by its SKU.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `eans` | `Array<string>` | The EANs to set (must be unique, non-empty strings). |
**Returns**
`Promise<object>` — The updated article.
**Throws**
`Error` — If the EANs are invalid or the article is not found.
**Example**
```js
const fn = setEansBySku(pool);
const article = await fn(42, 'SKU-1', ['4001234567890']);
const article = await store.article.setEansBySku(42, 'SKU-1', ['4001234567890']);
// => { id, sku, name, ... }
```
@@ -150,9 +334,26 @@ const article = await fn(42, 'SKU-1', ['4001234567890']);
Replaces the price list of an article by its SKU.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `prices` | `Array<{price: number, tax: number, minmultiplier?: number}>` | The price entries (minmultiplier defaults to 1). |
**Returns**
`Promise<object>` — The updated article.
**Throws**
`Error` — If the prices are invalid or the article is not found.
**Example**
```js
const fn = setPricesBySku(pool);
const article = await fn(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
const article = await store.article.setPricesBySku(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
// => { id, sku, name, ... }
```
@@ -162,9 +363,26 @@ const article = await fn(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }
Sets the weight of an article by its SKU.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `weight` | `number` | The weight (must be >= 0). |
**Returns**
`Promise<object>` — The updated article.
**Throws**
`Error` — If the weight is invalid or the article is not found.
**Example**
```js
const fn = setWeightBySku(pool);
const article = await fn(42, 'SKU-1', 1.5);
const article = await store.article.setWeightBySku(42, 'SKU-1', 1.5);
// => { id, sku, name, ... }
```
@@ -174,8 +392,28 @@ const article = await fn(42, 'SKU-1', 1.5);
Updates an article's name and/or unit (SI and amount), recording history.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `patch` | `object` | The update patch. |
| `[patch.name]` | `string` | The new article name. |
| `[patch.unitSi]` | `string` | The new unit SI code. |
| `[patch.unitamount]` | `number` | The new unit amount (must be > 0). |
**Returns**
`Promise<object>` — The updated article.
**Throws**
`Error` — If no valid field is provided or the article is not found.
**Example**
```js
const fn = updateBySku(pool);
const article = await fn(42, 'SKU-1', { name: 'Cola 1L', unitSi: 'l', unitamount: 1 });
const article = await store.article.updateBySku(42, 'SKU-1', { name: 'Cola 1L', unitSi: 'l', unitamount: 1 });
// => { id, sku, name, ... }
```

View File

@@ -6,22 +6,49 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
*Source: `src/store/brand/delete.js`*
Deletes a brand for a company by its id.
Deletes a brand for the user's selected company by its id.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the brand to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the brand is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 3);
await store.brand.delete(42, 3);
```
## `brand.list`
*Source: `src/store/brand/list.js`*
Lists the brands of a company.
Lists the brands of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, name: string, gsprpinfo: string|null}>>` — The brands.
**Example**
```js
const fn = list(pool);
const brands = await fn(42);
const brands = await store.brand.list(42);
// => [{ id, name, gsprpinfo }]
```
@@ -29,10 +56,28 @@ const brands = await fn(42);
*Source: `src/store/brand/upsert.js`*
Creates or updates a brand for a company, keyed by name.
Creates or updates a brand for the user's selected company, keyed by name.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The brand input. |
| `input.name` | `string` | The brand name. |
| `[input.gsprpinfo]` | `string` | Optional GSPRP info. |
**Returns**
`Promise<{id: number, name: string, gsprpinfo: string|null}>` — The upserted brand.
**Throws**
`Error` — If the brand is invalid.
**Example**
```js
const fn = upsert(pool);
const brand = await fn(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
const brand = await store.brand.upsert(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
// => { id, name, gsprpinfo }
```

View File

@@ -8,9 +8,22 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Adds an image to a category, processing it into AVIF full and thumb variants.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `categoryId` | `number` | The id of the category. |
| `image` | `Buffer` | The source image buffer. |
**Returns**
`Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>` — The added image info.
**Example**
```js
const fn = addImage(pool);
const result = await fn(42, 5, imageBuffer);
const result = await store.category.addImage(42, 5, imageBuffer);
// => { id, fullHash, thumbW, thumbH, thumbHash }
```
@@ -20,9 +33,23 @@ const result = await fn(42, 5, imageBuffer);
Creates a new category under a given parent category.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The category creation input. |
| `input.googleAuthId` | `number` | The id of the Google auth user. |
| `input.parentId` | `number` | The id of the parent category. |
| `input.name` | `string` | The category name. |
**Returns**
`Promise<{id: number, parentId: number, sort: number, name: string, slug: string}>` — The created category.
**Example**
```js
const fn = createUnderParent(pool);
const category = await fn({ googleAuthId: 42, parentId: 5, name: 'Drinks' });
const category = await store.category.createUnderParent({ googleAuthId: 42, parentId: 5, name: 'Drinks' });
// => { id, parentId, sort, name, slug }
```
@@ -32,9 +59,26 @@ const category = await fn({ googleAuthId: 42, parentId: 5, name: 'Drinks' });
Deletes a category, refusing to delete the company's root category.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The delete input. |
| `input.googleAuthId` | `number` | The id of the Google auth user. |
| `input.id` | `number` | The id of the category to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the category is the root category.
**Example**
```js
const fn = del(pool);
await fn({ googleAuthId: 42, id: 5 });
await store.category.delete({ googleAuthId: 42, id: 5 });
```
## `category.deleteImage`
@@ -43,10 +87,26 @@ await fn({ googleAuthId: 42, id: 5 });
Deletes the image of a category.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `categoryId` | `number` | The id of the category. |
**Returns**
`Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null}>` — The cleared image info.
**Throws**
`Error` — If the image is not found.
**Example**
```js
const fn = deleteImage(pool);
const result = await fn(42, 5);
// => { id, thumbW: null, thumbH: null, thumbHash: null }
const result = await store.category.deleteImage(42, 5);
// => { id: 5, thumbW: null, thumbH: null, thumbHash: null }
```
## `category.getCategoryArticles`
@@ -55,9 +115,21 @@ const result = await fn(42, 5);
Returns the articles belonging to a single category.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `categoryId` | `number` | The id of the category. |
**Returns**
`Promise<Array<object>>` — The articles in the category.
**Example**
```js
const fn = getCategoryArticles(pool);
const articles = await fn(42, 5);
const articles = await store.category.getCategoryArticles(42, 5);
// => [{ id, sku, name, ... }]
```
@@ -65,11 +137,22 @@ const articles = await fn(42, 5);
*Source: `src/store/category/getTreeForCompany.js`*
Returns the full category tree for a company together with its articles and the posterminal ids assigned to each category.
Returns the full category tree for the user's selected company together with its articles and the posterminal ids assigned to each category.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<{languageId: number, tree: Array<object>, articles: Array<object>}>` — The category tree, articles and language.
**Example**
```js
const fn = getTreeForCompany(pool);
const tree = await fn(42);
const tree = await store.category.getTreeForCompany(42);
// => { languageId, tree: [{ id, posterminalIds, ... }], articles: [...] }
```
@@ -79,9 +162,27 @@ const tree = await fn(42);
Moves a category to a new parent, preventing cycles and cross-company moves.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The move input. |
| `input.googleAuthId` | `number` | The id of the Google auth user. |
| `input.id` | `number` | The id of the category to move. |
| `input.parentId` | `number` | The id of the new parent category. |
**Returns**
`Promise<{id: number, parentId: number, sort: number}>` — The moved category.
**Throws**
`Error` — If the move would create a cycle or cross companies.
**Example**
```js
const fn = move(pool);
const result = await fn({ googleAuthId: 42, id: 5, parentId: 9 });
const result = await store.category.move({ googleAuthId: 42, id: 5, parentId: 9 });
// => { id, parentId, sort }
```
@@ -91,9 +192,27 @@ const result = await fn({ googleAuthId: 42, id: 5, parentId: 9 });
Reorders the child categories of a given parent.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The reorder input. |
| `input.googleAuthId` | `number` | The id of the Google auth user. |
| `input.parentId` | `number` | The id of the parent category. |
| `input.categoryIds` | `Array<number>` | The child category ids in their new order. |
**Returns**
`Promise<{categoryIds: Array<number>}>` — The applied order.
**Throws**
`Error` — If the order is invalid.
**Example**
```js
const fn = order(pool);
const result = await fn({ googleAuthId: 42, parentId: 5, categoryIds: [3, 1, 2] });
const result = await store.category.order({ googleAuthId: 42, parentId: 5, categoryIds: [3, 1, 2] });
// => { categoryIds: [3, 1, 2] }
```
@@ -103,8 +222,26 @@ const result = await fn({ googleAuthId: 42, parentId: 5, categoryIds: [3, 1, 2]
Renames a category and updates its slug.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The rename input. |
| `input.googleAuthId` | `number` | The id of the Google auth user. |
| `input.id` | `number` | The id of the category to rename. |
| `input.name` | `string` | The new category name. |
**Returns**
`Promise<{id: number, name: string, slug: string}>` — The renamed category.
**Throws**
`Error` — If the name is empty or the category is not found.
**Example**
```js
const fn = rename(pool);
const result = await fn({ googleAuthId: 42, id: 5, name: 'Soft Drinks' });
const result = await store.category.rename({ googleAuthId: 42, id: 5, name: 'Soft Drinks' });
// => { id, name, slug }
```

View File

@@ -8,6 +8,27 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Creates a new company together with its root category, POS customer group, default language and tax rates, and links it to the given Google auth user.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The company creation input. |
| `input.iso639` | `string` | The ISO 639 language code for the company. |
| `input.name` | `string` | The company name (must be unique). |
| `input.taxrate` | `number` | The standard tax rate in percent. |
| `input.reducedtaxrate` | `number` | The reduced tax rate in percent. |
| `input.googleAuthId` | `number` | The id of the Google auth user to link. |
**Returns**
`Promise<{id: number, rootCategoryId: number, languageId: number, taxrateId: number, reducedtaxrateId: number, taxfreerateId: number, posCustomerGroupId: number}>` — The created company ids.
**Throws**
`Error` — If the company name is already taken.
**Example**
```js
const company = await store.company.create({
iso639: 'de',
@@ -25,6 +46,18 @@ const company = await store.company.create({
Deletes a company by its id.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `companyId` | `number` | The id of the company to delete. |
**Returns**
`Promise<void>`
**Example**
```js
await store.company.delete(1);
```
@@ -35,6 +68,19 @@ await store.company.delete(1);
Returns a single company by its id.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `id` | `number` | The id of the company. |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<object|undefined>` — The company row, or `undefined` if not found.
**Example**
```js
const company = await store.company.getById(1);
// => { id, name, ... }
@@ -46,6 +92,19 @@ const company = await store.company.getById(1);
Returns a single company by its name.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | The company name. |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<object|undefined>` — The company row, or `undefined` if not found.
**Example**
```js
const company = await store.company.getByName('Acme');
// => { id, name, ... }
@@ -57,6 +116,18 @@ const company = await store.company.getByName('Acme');
Lists the companies accessible to the given Google auth user.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<object>>` — The accessible company rows.
**Example**
```js
const companies = await store.company.list(42);
// => [{ id, name, ... }]
@@ -68,6 +139,27 @@ const companies = await store.company.list(42);
Updates an existing company's name, language and tax rates.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `id` | `number` | The id of the company to update. |
| `input` | `object` | The update input. |
| `input.iso639` | `string` | The ISO 639 language code. |
| `input.name` | `string` | The new company name (must be unique). |
| `input.taxrate` | `number` | The standard tax rate in percent. |
| `input.reducedtaxrate` | `number` | The reduced tax rate in percent. |
**Returns**
`Promise<{id: number, rootCategoryId: number, languageId: number, taxrateId: number, reducedtaxrateId: number, taxfreerateId: number}>` — The updated company ids.
**Throws**
`Error` — If the company is not found or the name is taken.
**Example**
```js
const result = await store.company.update(1, { iso639: 'de', name: 'Acme', taxrate: 19, reducedtaxrate: 7 });
// => { id, rootCategoryId, languageId, taxrateId, reducedtaxrateId, taxfreerateId }

View File

@@ -6,22 +6,52 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
*Source: `src/store/companysetting/delete.js`*
Deletes a company setting by key.
Deletes a company setting by key for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `key` | `string` | The setting key. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the setting is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 'theme');
await store.companysetting.delete(42, 'theme');
```
## `companysetting.get`
*Source: `src/store/companysetting/get.js`*
Returns a single company setting by key.
Returns a single company setting by key for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `key` | `string` | The setting key. |
| `[options]` | `object` | Optional options. |
| `[options.frontendOnly]` | `boolean` | Only return the setting if it is frontend-visible. |
**Returns**
`Promise<{key: string, value: string, frontend: boolean}|undefined>` — The setting, or `undefined` if not found.
**Example**
```js
const fn = get(pool);
const setting = await fn(42, 'theme');
const setting = await store.companysetting.get(42, 'theme');
// => { key, value, frontend }
```
@@ -29,11 +59,24 @@ const setting = await fn(42, 'theme');
*Source: `src/store/companysetting/list.js`*
Lists the settings of a company, optionally only frontend-visible ones.
Lists the settings of the user's selected company, optionally only frontend-visible ones.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `[options]` | `object` | Optional options. |
| `[options.frontendOnly]` | `boolean` | Only return frontend-visible settings. |
**Returns**
`Promise<Array<{key: string, value: string, frontend: boolean}>>` — The settings.
**Example**
```js
const fn = list(pool);
const settings = await fn(42, { frontendOnly: true });
const settings = await store.companysetting.list(42, { frontendOnly: true });
// => [{ key, value, frontend }]
```
@@ -41,10 +84,29 @@ const settings = await fn(42, { frontendOnly: true });
*Source: `src/store/companysetting/set.js`*
Sets (upserts) a company setting by key.
Sets (upserts) a company setting by key for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The setting input. |
| `input.key` | `string` | The setting key. |
| `input.value` | `string` | The setting value. |
| `[input.frontend]` | `boolean` | Whether the setting is frontend-visible. |
**Returns**
`Promise<{key: string, value: string, frontend: boolean}>` — The upserted setting.
**Throws**
`Error` — If the input is invalid.
**Example**
```js
const fn = set(pool);
const setting = await fn(42, { key: 'theme', value: 'dark', frontend: true });
const setting = await store.companysetting.set(42, { key: 'theme', value: 'dark', frontend: true });
// => { key, value, frontend }
```

View File

@@ -8,6 +8,19 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Adds a company user linking a company to a Google auth account.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `companyId` | `number` | The id of the company. |
| `googleAuthId` | `number` | The id of the Google auth account. |
**Returns**
`Promise<number>` — The id of the created company user link.
**Example**
```js
const id = await store.companyuser.add(1, 42);
// => 7
@@ -19,6 +32,18 @@ const id = await store.companyuser.add(1, 42);
Deletes a company user link by its id.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `id` | `number` | The id of the company user link to delete. |
**Returns**
`Promise<void>`
**Example**
```js
await store.companyuser.delete(7);
```
@@ -29,6 +54,18 @@ await store.companyuser.delete(7);
Returns the company user ids associated with the given email address.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `email` | `string` | The email address. |
**Returns**
`Promise<Array<number>>` — The company user ids.
**Example**
```js
const ids = await store.companyuser.getByEmail('user@example.com');
// => [1, 2, 3]

View File

@@ -6,11 +6,28 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
*Source: `src/store/customergroup/create.js`*
Creates a new customer group for a company.
Creates a new customer group for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The customer group input. |
| `input.name` | `string` | The customer group name (must not be a reserved name). |
**Returns**
`Promise<{id: number, name: string, sort: number}>` — The created customer group.
**Throws**
`Error` — If the name is empty or reserved.
**Example**
```js
const fn = create(pool);
const group = await fn(42, { name: 'Wholesale' });
const group = await store.customergroup.create(42, { name: 'Wholesale' });
// => { id, name, sort }
```
@@ -18,22 +35,49 @@ const group = await fn(42, { name: 'Wholesale' });
*Source: `src/store/customergroup/delete.js`*
Deletes a customer group for a company by its id.
Deletes a customer group for the user's selected company by its id.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the customer group to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the customer group is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 5);
await store.customergroup.delete(42, 5);
```
## `customergroup.list`
*Source: `src/store/customergroup/list.js`*
Lists the customer groups of a company.
Lists the customer groups of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, name: string, sort: number}>>` — The customer groups.
**Example**
```js
const fn = list(pool);
const groups = await fn(42);
const groups = await store.customergroup.list(42);
// => [{ id, name, sort }]
```
@@ -41,11 +85,28 @@ const groups = await fn(42);
*Source: `src/store/customergroup/order.js`*
Reorders the customer groups of a company.
Reorders the customer groups of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The reorder input. |
| `input.customergroupIds` | `Array<number>` | The customer group ids in their new order. |
**Returns**
`Promise<{customergroupIds: Array<number>}>` — The applied order.
**Throws**
`Error` — If the order is invalid.
**Example**
```js
const fn = order(pool);
const result = await fn(42, { customergroupIds: [3, 1, 2] });
const result = await store.customergroup.order(42, { customergroupIds: [3, 1, 2] });
// => { customergroupIds: [3, 1, 2] }
```
@@ -53,10 +114,28 @@ const result = await fn(42, { customergroupIds: [3, 1, 2] });
*Source: `src/store/customergroup/update.js`*
Updates the name of a customer group for a company.
Updates the name of a customer group for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the customer group to update. |
| `input` | `object` | The update input. |
| `input.name` | `string` | The new customer group name. |
**Returns**
`Promise<{id: number, name: string, sort: number}>` — The updated customer group.
**Throws**
`Error` — If the customer group is not found or the name is empty.
**Example**
```js
const fn = update(pool);
const group = await fn(42, 5, { name: 'Retail' });
const group = await store.customergroup.update(42, 5, { name: 'Retail' });
// => { id, name, sort }
```

View File

@@ -6,11 +6,31 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
*Source: `src/store/delivery/create.js`*
Creates a new delivery for a company with its lines.
Creates a new delivery 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 delivery input. |
| `body.number` | `string` | The delivery number. |
| `body.date` | `string` | The delivery date. |
| `body.supplierId` | `number` | The id of the supplier. |
| `[body.lines]` | `Array<object>` | The delivery lines. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number, date: string, lines: Array<object>}>` — The created delivery.
**Throws**
`Error` — If the delivery is invalid.
**Example**
```js
const fn = create(pool);
const delivery = await fn(42, { number: 'D-1', date: '2026-01-01', supplierId: 5, lines: [...] });
const delivery = await store.delivery.create(42, { number: 'D-1', date: '2026-01-01', supplierId: 5, lines: [...] });
// => { id, number, type, entityId, date, lines: [...] }
```
@@ -18,22 +38,54 @@ const delivery = await fn(42, { number: 'D-1', date: '2026-01-01', supplierId: 5
*Source: `src/store/delivery/delete.js`*
Deletes a delivery by its id within a company.
Deletes a delivery 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 delivery to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the delivery is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 5);
await store.delivery.delete(42, 5);
```
## `delivery.getById`
*Source: `src/store/delivery/getById.js`*
Returns a single delivery by its id within a company.
Returns a single delivery 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 delivery. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number, date: string, lines: Array<object>}>` — The delivery.
**Throws**
`Error` — If the delivery is not found.
**Example**
```js
const fn = getById(pool);
const delivery = await fn(42, 5);
const delivery = await store.delivery.getById(42, 5);
// => { id, number, type, entityId, date, lines: [...] }
```
@@ -41,11 +93,22 @@ const delivery = await fn(42, 5);
*Source: `src/store/delivery/list.js`*
Lists the deliveries of a company.
Lists the deliveries of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, number: string, type: string, entityId: number, date: string}>>` — The deliveries.
**Example**
```js
const fn = list(pool);
const deliveries = await fn(42);
const deliveries = await store.delivery.list(42);
// => [{ id, number, type, entityId, date }]
```
@@ -55,8 +118,29 @@ const deliveries = await fn(42);
Updates a delivery's header and/or lines.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the delivery to update. |
| `patch` | `object` | The update patch. |
| `[patch.number]` | `string` | The new delivery number. |
| `[patch.date]` | `string` | The new delivery date. |
| `[patch.supplierId]` | `number` | The new supplier id. |
| `[patch.lines]` | `Array<object>` | The new delivery lines. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number, date: string, lines: Array<object>}>` — The updated delivery.
**Throws**
`Error` — If the delivery is not found.
**Example**
```js
const fn = update(pool);
const delivery = await fn(42, 5, { number: 'D-2', lines: [...] });
const delivery = await store.delivery.update(42, 5, { number: 'D-2', lines: [...] });
// => { id, number, type, entityId, date, lines: [...] }
```

View File

@@ -8,9 +8,29 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Creates a new entity (customer) in a customer group, recording history. Optional `number`; if omitted, allocates via `getnextnumberwithprefixandpadding` (`type` `entity`).
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The entity creation input. |
| `input.customergroupId` | `number` | The id of the customer group. |
| `input.name` | `string` | The entity name. |
| `[input.firstName]` | `string` | The entity first name. |
| `[input.number]` | `string` | Optional explicit customer number. |
**Returns**
`Promise<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>` — The created entity.
**Throws**
`Error` — If the name is empty.
**Example**
```js
const fn = create(pool);
const entity = await fn(42, { customergroupId: 5, name: 'Doe', firstName: 'John' });
const entity = await store.entity.create(42, { customergroupId: 5, name: 'Doe', firstName: 'John' });
// => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
```
@@ -18,22 +38,54 @@ const entity = await fn(42, { customergroupId: 5, name: 'Doe', firstName: 'John'
*Source: `src/store/entity/delete.js`*
Deletes an entity by its id within a company.
Deletes an entity 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 entity to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the entity is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 5);
await store.entity.delete(42, 5);
```
## `entity.getById`
*Source: `src/store/entity/getById.js`*
Returns a single entity by its id within a company.
Returns a single entity 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 entity. |
**Returns**
`Promise<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>` — The entity.
**Throws**
`Error` — If the entity is not found.
**Example**
```js
const fn = getById(pool);
const entity = await fn(42, 5);
const entity = await store.entity.getById(42, 5);
// => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
```
@@ -41,13 +93,31 @@ const entity = await fn(42, 5);
*Source: `src/store/entity/isNumberFree.js`*
Returns whether a customer number is free within the company. Pass `excludeEntityId` when editing so the entity's own number counts as free.
Returns whether a customer number is free within the user's selected company. Pass `excludeEntityId` when editing so the entity's own number counts as free.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `number` | `string` | The customer number to check. |
| `[options]` | `object` | Optional options. |
| `[options.excludeEntityId]` | `number` | An entity id to exclude from the check. |
**Returns**
`Promise<boolean>` — Whether the number is free.
**Throws**
`Error` — If the number is empty.
**Example**
```js
const fn = isNumberFree(pool);
const free = await fn(42, 'C-100');
const free = await store.entity.isNumberFree(42, 'C-100');
// => true
const freeOnEdit = await fn(42, 'C-100', { excludeEntityId: 5 });
const freeOnEdit = await store.entity.isNumberFree(42, 'C-100', { excludeEntityId: 5 });
// => true
```
@@ -55,11 +125,29 @@ const freeOnEdit = await fn(42, 'C-100', { excludeEntityId: 5 });
*Source: `src/store/entity/list.js`*
Lists the entities (customers) of a company, optionally filtered by customer group or restricted to the 'customer' role.
Lists the entities (customers) of the user's selected company, optionally filtered by customer group or restricted to the 'customer' role.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `[options]` | `object` | Optional options. |
| `[options.customergroupId]` | `number` | Filter by customer group id. |
| `[options.role]` | `string` | Restrict to `'customer'` role if set. |
**Returns**
`Promise<Array<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>>` — The entities.
**Throws**
`Error` — If the role is invalid.
**Example**
```js
const fn = list(pool);
const entities = await fn(42, { customergroupId: 5 });
const entities = await store.entity.list(42, { customergroupId: 5 });
// => [{ id, customergroupId, firstName, name, number, createdAt, updatedAt }]
```
@@ -67,11 +155,22 @@ const entities = await fn(42, { customergroupId: 5 });
*Source: `src/store/entity/peekNextNumber.js`*
Returns the next customer number that would be allocated for the company (`numbers` type `entity`) without consuming it.
Returns the next customer number that would be allocated for the user's selected company (`numbers` type `entity`) without consuming it.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<string>` — The next entity number.
**Example**
```js
const fn = peekNextNumber(pool);
const number = await fn(42);
const number = await store.entity.peekNextNumber(42);
// => '1'
```
@@ -81,8 +180,28 @@ const number = await fn(42);
Updates name, first name, and/or customer number of an entity, recording history. Optional `number` sets the customer number explicitly (does not auto-allocate).
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the entity to update. |
| `patch` | `object` | The update patch. |
| `[patch.name]` | `string` | The new entity name. |
| `[patch.firstName]` | `string` | The new first name. |
| `[patch.number]` | `string` | The new customer number. |
**Returns**
`Promise<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>` — The updated entity.
**Throws**
`Error` — If no valid field is provided or the entity is not found.
**Example**
```js
const fn = update(pool);
const entity = await fn(42, 5, { name: 'Doe', firstName: 'Jane', number: 'C-42' });
const entity = await store.entity.update(42, 5, { name: 'Doe', firstName: 'Jane', number: 'C-42' });
// => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
```

View File

@@ -8,9 +8,35 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Creates a new address for an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
| `input` | `object` | The address input. |
| `input.addressType` | `string` | The address type (e.g. `'billing'`). |
| `[input.firstName]` | `string` | The first name. |
| `[input.name]` | `string` | The name. |
| `[input.street]` | `string` | The street. |
| `[input.streetNumber]` | `string` | The street number. |
| `[input.zip]` | `string` | The zip code. |
| `[input.city]` | `string` | The city. |
| `[input.county]` | `string` | The county. |
| `[input.sort]` | `number` | Optional sort order. |
**Returns**
`Promise<object>` — The created address.
**Throws**
`Error` — If the address type is missing.
**Example**
```js
const fn = create(pool);
const address = await fn(42, 5, { addressType: 'billing', street: 'Main St', city: 'Berlin' });
const address = await store.entityaddress.create(42, 5, { addressType: 'billing', street: 'Main St', city: 'Berlin' });
// => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
```
@@ -20,9 +46,26 @@ const address = await fn(42, 5, { addressType: 'billing', street: 'Main St', cit
Deletes an address of an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
| `id` | `number` | The id of the address to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the address is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 5, 9);
await store.entityaddress.delete(42, 5, 9);
```
## `entityaddress.list`
@@ -31,9 +74,21 @@ await fn(42, 5, 9);
Lists the addresses of an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
**Returns**
`Promise<Array<object>>` — The addresses.
**Example**
```js
const fn = list(pool);
const addresses = await fn(42, 5);
const addresses = await store.entityaddress.list(42, 5);
// => [{ id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }]
```
@@ -43,8 +98,26 @@ const addresses = await fn(42, 5);
Updates an address of an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
| `id` | `number` | The id of the address to update. |
| `patch` | `object` | The update patch (any subset of address fields). |
**Returns**
`Promise<object>` — The updated address.
**Throws**
`Error` — If the address is not found.
**Example**
```js
const fn = update(pool);
const address = await fn(42, 5, 9, { city: 'Hamburg' });
const address = await store.entityaddress.update(42, 5, 9, { city: 'Hamburg' });
// => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
```

View File

@@ -8,9 +8,29 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Creates a new contact for an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
| `input` | `object` | The contact input. |
| `input.contactType` | `string` | The contact type (e.g. `'email'`). |
| `input.contact` | `string` | The contact value. |
| `[input.sort]` | `number` | Optional sort order. |
**Returns**
`Promise<{id: number, entityId: number, contactType: string, contact: string, sort: number}>` — The created contact.
**Throws**
`Error` — If the contact type or value is missing.
**Example**
```js
const fn = create(pool);
const contact = await fn(42, 5, { contactType: 'email', contact: 'a@b.c' });
const contact = await store.entitycontact.create(42, 5, { contactType: 'email', contact: 'a@b.c' });
// => { id, entityId, contactType, contact, sort }
```
@@ -20,9 +40,26 @@ const contact = await fn(42, 5, { contactType: 'email', contact: 'a@b.c' });
Deletes a contact of an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
| `id` | `number` | The id of the contact to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the contact is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 5, 9);
await store.entitycontact.delete(42, 5, 9);
```
## `entitycontact.list`
@@ -31,9 +68,21 @@ await fn(42, 5, 9);
Lists the contacts of an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
**Returns**
`Promise<Array<{id: number, entityId: number, contactType: string, contact: string, sort: number}>>` — The contacts.
**Example**
```js
const fn = list(pool);
const contacts = await fn(42, 5);
const contacts = await store.entitycontact.list(42, 5);
// => [{ id, entityId, contactType, contact, sort }]
```
@@ -43,8 +92,26 @@ const contacts = await fn(42, 5);
Updates a contact of an entity.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `entityId` | `number` | The id of the entity. |
| `id` | `number` | The id of the contact to update. |
| `patch` | `object` | The update patch (any subset of contact fields). |
**Returns**
`Promise<{id: number, entityId: number, contactType: string, contact: string, sort: number}>` — The updated contact.
**Throws**
`Error` — If the contact is not found.
**Example**
```js
const fn = update(pool);
const contact = await fn(42, 5, 9, { contact: 'b@c.d' });
const contact = await store.entitycontact.update(42, 5, 9, { contact: 'b@c.d' });
// => { id, entityId, contactType, contact, sort }
```

View File

@@ -8,9 +8,20 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Adds a new allowed Google auth email.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `email` | `string` | The email address to allow. |
**Returns**
`Promise<number>` — The id of the created Google auth row.
**Example**
```js
const fn = add(pool);
const id = await fn('user@example.com');
const id = await store.google_auth.add('user@example.com');
// => 1
```
@@ -20,9 +31,20 @@ const id = await fn('user@example.com');
Returns a Google auth row by email.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `email` | `string` | The email address. |
**Returns**
`Promise<object|undefined>` — The Google auth row, or `undefined` if not found.
**Example**
```js
const fn = getByEmail(pool);
const auth = await fn('user@example.com');
const auth = await store.google_auth.getByEmail('user@example.com');
// => { id, email, ... }
```
@@ -32,9 +54,20 @@ const auth = await fn('user@example.com');
Returns a Google auth row by its id.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth row. |
**Returns**
`Promise<object|undefined>` — The Google auth row, or `undefined` if not found.
**Example**
```js
const fn = getById(pool);
const auth = await fn(42);
const auth = await store.google_auth.getById(42);
// => { id, email, ... }
```
@@ -44,9 +77,14 @@ const auth = await fn(42);
Lists all allowed Google auth emails.
**Returns**
`Promise<Array<{id: number, email: string}>>` — The allowed emails.
**Example**
```js
const fn = listAllowedEmails(pool);
const emails = await fn();
const emails = await store.google_auth.listAllowedEmails();
// => [{ id, email }]
```
@@ -56,9 +94,20 @@ const emails = await fn();
Removes an allowed Google auth email.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `email` | `string` | The email address to remove. |
**Returns**
`Promise<void>`
**Example**
```js
const fn = removeAllowedEmail(pool);
await fn('user@example.com');
await store.google_auth.removeAllowedEmail('user@example.com');
```
## `google_auth.setProfile`
@@ -67,9 +116,28 @@ await fn('user@example.com');
Updates the profile (picture, picture binary, sub) of a Google auth user.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The profile input. |
| `[input.picture]` | `string` | The picture URL. |
| `[input.pictureBinary]` | `Buffer` | The picture binary. |
| `[input.sub]` | `string` | The Google sub identifier. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the user is not found or the picture binary is invalid.
**Example**
```js
const fn = setProfile(pool);
await fn(42, { picture: 'url', pictureBinary: buffer, sub: 'sub' });
await store.google_auth.setProfile(42, { picture: 'url', pictureBinary: buffer, sub: 'sub' });
```
## `google_auth.setSelectedCompany`
@@ -78,7 +146,19 @@ await fn(42, { picture: 'url', pictureBinary: buffer, sub: 'sub' });
Sets the currently selected company for a Google auth user.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `companyId` | `number` | The id of the company to select. |
**Returns**
`Promise<void>`
**Example**
```js
const fn = setSelectedCompany(pool);
await fn(42, 1);
await store.google_auth.setSelectedCompany(42, 1);
```

View File

@@ -8,6 +8,19 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Creates a new language by its ISO 639 code.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `iso639` | `string` | The ISO 639 language code (e.g. `'de'`). |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<number>` — The id of the created language.
**Example**
```js
const id = await store.lang.create('de');
// => 1
@@ -19,6 +32,18 @@ const id = await store.lang.create('de');
Deletes a language by its id.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `id` | `number` | The id of the language to delete. |
**Returns**
`Promise<void>`
**Example**
```js
await store.lang.delete(1);
```
@@ -29,6 +54,19 @@ await store.lang.delete(1);
Returns the id of a language by ISO code, creating it if it does not exist.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `iso639` | `string` | The ISO 639 language code (e.g. `'de'`). |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<number>` — The id of the language.
**Example**
```js
const id = await store.lang.ensure('de');
// => 1
@@ -40,6 +78,19 @@ const id = await store.lang.ensure('de');
Returns a language row by its ISO 639 code.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `iso639` | `string` | The ISO 639 language code (e.g. `'de'`). |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<{id: number, iso639: string}>` — The language row, or `undefined` if not found.
**Example**
```js
const lang = await store.lang.getByIso639('de');
// => { id: 1, iso639: 'de' }
@@ -51,6 +102,12 @@ const lang = await store.lang.getByIso639('de');
Lists all languages.
**Returns**
`Promise<Array<{id: number, iso639: string}>>` — All language rows.
**Example**
```js
const languages = await store.lang.list();
// => [{ id: 1, iso639: 'de' }]

2
API.md
View File

@@ -21,6 +21,8 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- [entitycontact](API.entitycontact.md)
- [supplier](API.supplier.md)
- [delivery](API.delivery.md)
- [quote](API.quote.md)
- [order](API.order.md)
- [posterminal](API.posterminal.md)
- [google_auth](API.google_auth.md)
- [pic](API.pic.md)

146
API.order.md Normal file
View File

@@ -0,0 +1,146 @@
# order
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
## `order.create`
*Source: `src/store/order/create.js`*
Creates a new order 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 order input. |
| `body.number` | `string` | The order number. |
| `body.date` | `string` | The order date. |
| `[body.entityId]` | `number` | The id of the customer entity. |
| `[body.lines]` | `Array<object>` | The order lines. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>` — The created order.
**Throws**
`Error` — If the order is invalid.
**Example**
```js
const order = await store.order.create(42, { number: 'B-2026-001', date: '2026-09-15', entityId: 12, lines: [...] });
// => { id, number, type, entityId, date, lines: [...] }
```
## `order.delete`
*Source: `src/store/order/delete.js`*
Deletes an order 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 order to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the order is not found.
**Example**
```js
await store.order.delete(42, 5);
```
## `order.getById`
*Source: `src/store/order/getById.js`*
Returns a single order 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 order. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>` — The order with its lines.
**Throws**
`Error` — If the order is not found.
**Example**
```js
const order = await store.order.getById(42, 5);
// => { id, number, type, entityId, date, lines: [...] }
```
## `order.list`
*Source: `src/store/order/list.js`*
Lists the orders of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, number: string, type: string, entityId: number|null, date: string}>>` — The orders.
**Example**
```js
const orders = await store.order.list(42);
// => [{ id, number, type, entityId, date }]
```
## `order.update`
*Source: `src/store/order/update.js`*
Updates an order's header and/or lines.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the order to update. |
| `patch` | `object` | The update patch. |
| `[patch.number]` | `string` | The new order number. |
| `[patch.date]` | `string` | The new order date. |
| `[patch.entityId]` | `number` | The new customer entity id. |
| `[patch.lines]` | `Array<object>` | The new order lines. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>` — The updated order.
**Throws**
`Error` — If the order is not found.
**Example**
```js
const order = await store.order.update(42, 5, { number: 'B-2026-002', lines: [...] });
// => { id, number, type, entityId, date, lines: [...] }
```

View File

@@ -8,9 +8,25 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Returns the full-size image (AVIF) for a given hash.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `fullHash` | `string` | The full image hash. |
**Returns**
`Promise<{buffer: Buffer, w: number, h: number, size: number, contentType: string}>` — The full image.
**Throws**
`Error` — If the image is not found.
**Example**
```js
const fn = getImageByHash(pool);
const image = await fn(42, 'abc123');
const image = await store.pic.getImageByHash(42, 'abc123');
// => { buffer, w, h, size, contentType: 'image/avif' }
```
@@ -20,8 +36,24 @@ const image = await fn(42, 'abc123');
Returns the thumbnail image (AVIF) for a given hash.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `fullHash` | `string` | The full image hash. |
**Returns**
`Promise<{buffer: Buffer, w: number, h: number, contentType: string}>` — The thumbnail image.
**Throws**
`Error` — If the image is not found.
**Example**
```js
const fn = getThumbByHash(pool);
const thumb = await fn(42, 'abc123');
const thumb = await store.pic.getThumbByHash(42, 'abc123');
// => { buffer, w, h, contentType: 'image/avif' }
```

View File

@@ -6,22 +6,49 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
*Source: `src/store/posterminal/delete.js`*
Deletes a posterminal by its id within a company.
Deletes a posterminal 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 posterminal to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the posterminal is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 5);
await store.posterminal.delete(42, 5);
```
## `posterminal.list`
*Source: `src/store/posterminal/list.js`*
Lists the posterminals of a company.
Lists the posterminals of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, name: string, token: string, pairingpin: string}>>` — The posterminals.
**Example**
```js
const fn = list(pool);
const terminals = await fn(42);
const terminals = await store.posterminal.list(42);
// => [{ id, name, token, pairingpin }]
```
@@ -31,9 +58,26 @@ const terminals = await fn(42);
Removes a category assignment from a posterminal (turns it off).
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `posterminalId` | `number` | The id of the posterminal. |
| `categoryId` | `number` | The id of the category to remove. |
**Returns**
`Promise<{id: number, categoryIds: Array<number>}>` — The posterminal with its remaining category ids.
**Throws**
`Error` — If the posterminal is not found.
**Example**
```js
const fn = setOff(pool);
const result = await fn(42, 5, 9);
const result = await store.posterminal.setOff(42, 5, 9);
// => { id, categoryIds: [...] }
```
@@ -43,9 +87,25 @@ const result = await fn(42, 5, 9);
Removes all category assignments from a posterminal.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `posterminalId` | `number` | The id of the posterminal. |
**Returns**
`Promise<{id: number, categoryIds: Array<number>}>` — The posterminal with an empty category list.
**Throws**
`Error` — If the posterminal is not found.
**Example**
```js
const fn = setOffAll(pool);
const result = await fn(42, 5);
const result = await store.posterminal.setOffAll(42, 5);
// => { id, categoryIds: [] }
```
@@ -55,9 +115,26 @@ const result = await fn(42, 5);
Assigns a category to a posterminal (turns it on for that category).
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `posterminalId` | `number` | The id of the posterminal. |
| `categoryId` | `number` | The id of the category to assign. |
**Returns**
`Promise<{id: number, categoryIds: Array<number>}>` — The posterminal with its category ids.
**Throws**
`Error` — If the posterminal is not found.
**Example**
```js
const fn = setOn(pool);
const result = await fn(42, 5, 9);
const result = await store.posterminal.setOn(42, 5, 9);
// => { id, categoryIds: [9, ...] }
```
@@ -65,11 +142,27 @@ const result = await fn(42, 5, 9);
*Source: `src/store/posterminal/setOnAll.js`*
Assigns all categories of a company to a posterminal.
Assigns all categories of the user's selected company to a posterminal.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `posterminalId` | `number` | The id of the posterminal. |
**Returns**
`Promise<{id: number, categoryIds: Array<number>}>` — The posterminal with all category ids.
**Throws**
`Error` — If the posterminal is not found.
**Example**
```js
const fn = setOnAll(pool);
const result = await fn(42, 5);
const result = await store.posterminal.setOnAll(42, 5);
// => { id, categoryIds: [...] }
```
@@ -77,10 +170,30 @@ const result = await fn(42, 5);
*Source: `src/store/posterminal/upsert.js`*
Creates or updates a posterminal for a company.
Creates or updates a posterminal for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The posterminal input. |
| `input.name` | `string` | The posterminal name. |
| `input.pairingpin` | `string` | The pairing pin. |
| `[input.token]` | `string` | Optional token. |
| `[input.id]` | `number` | Optional id to update an existing posterminal. |
**Returns**
`Promise<{id: number, name: string, token: string, pairingpin: string}>` — The upserted posterminal.
**Throws**
`Error` — If the posterminal is invalid.
**Example**
```js
const fn = upsert(pool);
const terminal = await fn(42, { name: 'Kasse 1', pairingpin: '1234', token: 'abc' });
const terminal = await store.posterminal.upsert(42, { name: 'Kasse 1', pairingpin: '1234', token: 'abc' });
// => { id, name, token, pairingpin }
```

146
API.quote.md Normal file
View File

@@ -0,0 +1,146 @@
# quote
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
## `quote.create`
*Source: `src/store/quote/create.js`*
Creates a new quote 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 quote input. |
| `body.number` | `string` | The quote number. |
| `body.date` | `string` | The quote date. |
| `[body.entityId]` | `number` | The id of the customer entity. |
| `[body.lines]` | `Array<object>` | The quote lines. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>` — The created quote.
**Throws**
`Error` — If the quote is invalid.
**Example**
```js
const quote = await store.quote.create(42, { number: 'A-2026-001', date: '2026-09-01', entityId: 12, lines: [...] });
// => { id, number, type, entityId, date, lines: [...] }
```
## `quote.delete`
*Source: `src/store/quote/delete.js`*
Deletes a quote 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 quote to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the quote is not found.
**Example**
```js
await store.quote.delete(42, 5);
```
## `quote.getById`
*Source: `src/store/quote/getById.js`*
Returns a single quote 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 quote. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>` — The quote with its lines.
**Throws**
`Error` — If the quote is not found.
**Example**
```js
const quote = await store.quote.getById(42, 5);
// => { id, number, type, entityId, date, lines: [...] }
```
## `quote.list`
*Source: `src/store/quote/list.js`*
Lists the quotes of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, number: string, type: string, entityId: number|null, date: string}>>` — The quotes.
**Example**
```js
const quotes = await store.quote.list(42);
// => [{ id, number, type, entityId, date }]
```
## `quote.update`
*Source: `src/store/quote/update.js`*
Updates a quote's header and/or lines.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the quote to update. |
| `patch` | `object` | The update patch. |
| `[patch.number]` | `string` | The new quote number. |
| `[patch.date]` | `string` | The new quote date. |
| `[patch.entityId]` | `number` | The new customer entity id. |
| `[patch.lines]` | `Array<object>` | The new quote lines. |
**Returns**
`Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>` — The updated quote.
**Throws**
`Error` — If the quote is not found.
**Example**
```js
const quote = await store.quote.update(42, 5, { number: 'A-2026-002', lines: [...] });
// => { id, number, type, entityId, date, lines: [...] }
```

View File

@@ -6,11 +6,28 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
*Source: `src/store/supplier/create.js`*
Creates a new supplier for a company, recording history.
Creates a new supplier for the user's selected company, recording history.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The supplier input. |
| `input.name` | `string` | The supplier name. |
**Returns**
`Promise<{id: number, name: string, createdAt: string, updatedAt: string}>` — The created supplier.
**Throws**
`Error` — If the name is empty.
**Example**
```js
const fn = create(pool);
const supplier = await fn(42, { name: 'Acme Supplies' });
const supplier = await store.supplier.create(42, { name: 'Acme Supplies' });
// => { id, name, createdAt, updatedAt }
```
@@ -18,22 +35,49 @@ const supplier = await fn(42, { name: 'Acme Supplies' });
*Source: `src/store/supplier/delete.js`*
Deletes a supplier by its id within a company.
Deletes a supplier 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 supplier to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the supplier is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 5);
await store.supplier.delete(42, 5);
```
## `supplier.list`
*Source: `src/store/supplier/list.js`*
Lists the suppliers of a company.
Lists the suppliers of the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, name: string, createdAt: string, updatedAt: string}>>` — The suppliers.
**Example**
```js
const fn = list(pool);
const suppliers = await fn(42);
const suppliers = await store.supplier.list(42);
// => [{ id, name, createdAt, updatedAt }]
```
@@ -43,8 +87,26 @@ const suppliers = await fn(42);
Updates the name of a supplier, recording history.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `id` | `number` | The id of the supplier to update. |
| `input` | `object` | The update input. |
| `input.name` | `string` | The new supplier name. |
**Returns**
`Promise<{id: number, name: string, createdAt: string, updatedAt: string}>` — The updated supplier.
**Throws**
`Error` — If the supplier is not found or the name is empty.
**Example**
```js
const fn = update(pool);
const supplier = await fn(42, 5, { name: 'Acme GmbH' });
const supplier = await store.supplier.update(42, 5, { name: 'Acme GmbH' });
// => { id, name, createdAt, updatedAt }
```

View File

@@ -8,6 +8,19 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
Creates a new tax rate.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `rate` | `number` | The tax rate in percent (e.g. `19`). |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<number>` — The id of the created tax rate.
**Example**
```js
const id = await store.tax.create(19);
// => 1
@@ -19,6 +32,19 @@ const id = await store.tax.create(19);
Returns the id of a tax rate, creating it if it does not exist.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `rate` | `number` | The tax rate in percent (e.g. `19`). |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<number>` — The id of the tax rate.
**Example**
```js
const id = await store.tax.ensure(19);
// => 1
@@ -30,6 +56,19 @@ const id = await store.tax.ensure(19);
Returns a tax row by its rate.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `rate` | `number` | The tax rate in percent (e.g. `19`). |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
`Promise<{id: number, rate: number}>` — The tax row, or `undefined` if not found.
**Example**
```js
const tax = await store.tax.getByRate(19);
// => { id: 1, rate: 19 }
@@ -41,6 +80,12 @@ const tax = await store.tax.getByRate(19);
Lists all tax rates.
**Returns**
`Promise<Array<{id: number, rate: number}>>` — All tax rate rows.
**Example**
```js
const taxes = await store.tax.list();
// => [{ id: 1, rate: 19 }]

View File

@@ -6,22 +6,49 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin
*Source: `src/store/unit/delete.js`*
Deletes a unit for a company by its SI code.
Deletes a unit for the user's selected company by its SI code.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `si` | `string` | The SI code of the unit to delete. |
**Returns**
`Promise<void>`
**Throws**
`Error` — If the unit is not found.
**Example**
```js
const fn = del(pool);
await fn(42, 'l');
await store.unit.delete(42, 'l');
```
## `unit.list`
*Source: `src/store/unit/list.js`*
Lists the units available for a company.
Lists the units available for the user's selected company.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
**Returns**
`Promise<Array<{id: number, si: string, basesi: string, basesifactor: number, symbol: string, name: string}>>` — The units.
**Example**
```js
const fn = list(pool);
const units = await fn(42);
const units = await store.unit.list(42);
// => [{ id, si, basesi, basesifactor, symbol, name }]
```
@@ -29,10 +56,31 @@ const units = await fn(42);
*Source: `src/store/unit/upsert.js`*
Creates or updates a unit for a company, keyed by its SI code.
Creates or updates a unit for the user's selected company, keyed by its SI code.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `input` | `object` | The unit input. |
| `input.si` | `string` | The SI code. |
| `input.basesi` | `string` | The base SI code. |
| `input.basesifactor` | `number` | The base SI factor (must be > 0). |
| `input.symbol` | `string` | The unit symbol. |
| `input.name` | `string` | The unit name. |
**Returns**
`Promise<{id: number, si: string, basesi: string, basesifactor: number, symbol: string, name: string}>` — The upserted unit.
**Throws**
`Error` — If the unit is invalid.
**Example**
```js
const fn = upsert(pool);
const unit = await fn(42, { si: 'l', basesi: 'm3', basesifactor: 0.001, symbol: 'L', name: 'Liter' });
const unit = await store.unit.upsert(42, { si: 'l', basesi: 'm3', basesifactor: 0.001, symbol: 'L', name: 'Liter' });
// => { id, si, basesi, basesifactor, symbol, name }
```

View File

@@ -58,13 +58,27 @@ const INTERNAL_FILES = new Set([
'peekNumber.js',
'shape.js',
'createStore.js',
'manifestLines.js',
'index.js',
]);
/**
* Strip the outer braces from a JSDoc type (e.g. `{number}` -> `number`),
* preserving nested braces inside generics.
* @param {string} type
* @returns {string}
*/
function stripBraces(type) {
if (type.startsWith('{') && type.endsWith('}')) {
return type.slice(1, -1);
}
return type;
}
/**
* Extract the leading JSDoc block from a source string.
* @param {string} source
* @returns {{ description: string, example: string } | null}
* @returns {{ description: string, example: string, params: Array<object>, returns: string|null, throws: string|null } | null}
*/
function extractJsDoc(source) {
const match = source.match(/\/\*\*([\s\S]*?)\*\//);
@@ -77,27 +91,53 @@ function extractJsDoc(source) {
const description = [];
const example = [];
const params = [];
let returns = null;
let throws = null;
let inExample = false;
for (const line of lines) {
if (line.trim() === '@example') {
const trimmed = line.trim();
if (trimmed === '@example') {
inExample = true;
continue;
}
if (line.trim().startsWith('@')) {
if (trimmed.startsWith('@')) {
inExample = false;
const paramMatch = trimmed.match(/^@param\s+(\{.*\})\s+(\S+)\s*[-–]?\s*(.*)$/);
if (paramMatch) {
params.push({
type: stripBraces(paramMatch[1]),
name: paramMatch[2],
description: paramMatch[3].trim(),
});
continue;
}
const returnsMatch = trimmed.match(/^@returns\s+(\{.*\})\s*[-–]?\s*(.*)$/);
if (returnsMatch) {
returns = { type: stripBraces(returnsMatch[1]), description: returnsMatch[2].trim() };
continue;
}
const throwsMatch = trimmed.match(/^@throws\s+(\{.*\})\s*[-–]?\s*(.*)$/);
if (throwsMatch) {
throws = { type: stripBraces(throwsMatch[1]), description: throwsMatch[2].trim() };
continue;
}
continue;
}
if (inExample) {
example.push(line);
} else if (line.trim()) {
description.push(line.trim());
} else if (trimmed) {
description.push(trimmed);
}
}
return {
description: description.join(' ').trim(),
example: example.join('\n').trim(),
params,
returns,
throws,
};
}
@@ -146,6 +186,9 @@ function buildModules() {
file: rel.join('/'),
description: doc.description,
example: doc.example,
params: doc.params,
returns: doc.returns,
throws: doc.throws,
});
}
@@ -170,7 +213,31 @@ function renderSection(moduleName, entries) {
lines.push(entry.description);
lines.push('');
}
if (entry.params && entry.params.length) {
lines.push('**Parameters**');
lines.push('');
lines.push('| Name | Type | Description |');
lines.push('| --- | --- | --- |');
for (const p of entry.params) {
lines.push(`| \`${p.name}\` | \`${p.type}\` | ${p.description} |`);
}
lines.push('');
}
if (entry.returns) {
lines.push('**Returns**');
lines.push('');
lines.push(`\`${entry.returns.type}\`${entry.returns.description ? ` — ${entry.returns.description}` : ''}`);
lines.push('');
}
if (entry.throws) {
lines.push('**Throws**');
lines.push('');
lines.push(`\`${entry.throws.type}\`${entry.throws.description ? ` — ${entry.throws.description}` : ''}`);
lines.push('');
}
if (entry.example) {
lines.push('**Example**');
lines.push('');
lines.push('```js');
lines.push(entry.example);
lines.push('```');

View File

@@ -68,8 +68,16 @@ import deliveryGetById from './store/delivery/getById.js';
import deliveryCreate from './store/delivery/create.js';
import deliveryUpdate from './store/delivery/update.js';
import deliveryDelete from './store/delivery/delete.js';
import createQuoteStore from './store/quote/index.js';
import createOrderStore from './store/order/index.js';
import quoteList from './store/quote/list.js';
import quoteGetById from './store/quote/getById.js';
import quoteCreate from './store/quote/create.js';
import quoteUpdate from './store/quote/update.js';
import quoteDelete from './store/quote/delete.js';
import orderList from './store/order/list.js';
import orderGetById from './store/order/getById.js';
import orderCreate from './store/order/create.js';
import orderUpdate from './store/order/update.js';
import orderDelete from './store/order/delete.js';
import posterminalList from './store/posterminal/list.js';
import posterminalUpsert from './store/posterminal/upsert.js';
import posterminalDelete from './store/posterminal/delete.js';
@@ -227,8 +235,21 @@ export default class LibDashStore {
delete: deliveryDelete(pool),
};
this.quote = createQuoteStore(pool);
this.order = createOrderStore(pool);
this.quote = {
list: quoteList(pool),
getById: quoteGetById(pool),
create: quoteCreate(pool),
update: quoteUpdate(pool),
delete: quoteDelete(pool),
};
this.order = {
list: orderList(pool),
getById: orderGetById(pool),
create: orderCreate(pool),
update: orderUpdate(pool),
delete: orderDelete(pool),
};
this.posterminal = {
list: posterminalList(pool),

View File

@@ -5,20 +5,21 @@ import newFullHash from '../../util/newFullHash.js';
import request from '../request.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Adds an image to an article by its SKU, processing it into AVIF variants.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that adds an article image.
*
* @example
* const fn = addImage(pool);
* const result = await fn(42, 'SKU-1', imageBuffer);
* // => { picId, sort, full: { w, h, size }, thumb: { w, h }, fullHash, thumbHash }
*/
export default function addImage(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Adds an image to an article by its SKU, processing it into AVIF variants.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Buffer} image - The source image buffer.
* @returns {Promise<{picId: number, sort: number, full: {w: number, h: number, size: number}, thumb: {w: number, h: number}, fullHash: string, thumbHash: string}>} The added image info.
*
* @example
* const result = await store.article.addImage(42, 'SKU-1', imageBuffer);
* // => { picId, sort, full: { w, h, size }, thumb: { w, h }, fullHash, thumbHash }
*/
return async (googleAuthId, sku, image) => {
const { articleId } = await resolve(googleAuthId, sku);

View File

@@ -7,23 +7,25 @@ import nextSku from '../numbers/nextSku.js';
import request from '../request.js';
import slugify from '../slugify.js';
/**
* Creates a new article under a given parent category, allocating a SKU and
* a default unit.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates an article.
*
* @example
* const fn = createUnderParent(pool);
* const article = await fn({ googleAuthId: 42, parentId: 5, name: 'Cola' });
* // => { id, parentId, name, sku }
*/
export default function createUnderParent(pool) {
const access = resolveCategoryAccess(pool);
const ensureUnit = ensureDefaultUnit(pool);
const allocateSku = nextSku(pool);
/**
* Creates a new article under a given parent category, allocating a SKU and
* a default unit.
*
* @param {object} input - The article creation input.
* @param {number} input.googleAuthId - The id of the Google auth user.
* @param {number} input.parentId - The id of the parent category.
* @param {string} input.name - The article name.
* @returns {Promise<{id: number, parentId: number, name: string, sku: string}>} The created article.
*
* @example
* const article = await store.article.createUnderParent({ googleAuthId: 42, parentId: 5, name: 'Cola' });
* // => { id, parentId, name, sku }
*/
return async ({ googleAuthId, parentId, name }) => {
const { companyId, languageId } = await access(googleAuthId, parentId);

View File

@@ -11,19 +11,20 @@ import {
import resolveArticleBySku from './resolveArticleBySku.js';
import request from '../request.js';
/**
* Deletes an article by its SKU, refusing when it still has children.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes an article.
*
* @example
* const fn = del(pool);
* await fn(42, 'SKU-1');
*/
export default function del(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Deletes an article by its SKU, refusing when it still has children.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @returns {Promise<void>}
* @throws {Error} If the article has children or is not found.
*
* @example
* await store.article.delete(42, 'SKU-1');
*/
return async (googleAuthId, sku) => {
const { articleId, languageId } = await resolve(googleAuthId, sku);

View File

@@ -1,19 +1,21 @@
import { DELETE_BY_ARTICLE_AND_HASH } from '../../sql/pic.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Deletes an image from an article by its SKU and full image hash.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes an article image.
*
* @example
* const fn = deleteImage(pool);
* await fn(42, 'SKU-1', 'abc123');
*/
export default function deleteImage(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Deletes an image from an article by its SKU and full image hash.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {string} fullHash - The full image hash to delete.
* @returns {Promise<void>}
* @throws {Error} If the image is not found.
*
* @example
* await store.article.deleteImage(42, 'SKU-1', 'abc123');
*/
return async (googleAuthId, sku, fullHash) => {
const { articleId } = await resolve(googleAuthId, sku);

View File

@@ -6,19 +6,20 @@ function parseJsonArray(raw) {
return Array.isArray(arr) ? arr : [];
}
/**
* Returns the full detail of an article by its SKU, including unit, brand,
* categories, photos, EANs and prices.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches an article by SKU.
*
* @example
* const fn = getBySku(pool);
* const article = await fn(42, 'SKU-1');
* // => { article: { id, sku, name, unit, brand, categoryIds, photos, eans, prices, ... } }
*/
export default function getBySku(pool) {
/**
* Returns the full detail of an article by its SKU, including unit, brand,
* categories, photos, EANs and prices.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @returns {Promise<{article: object}>} The full article detail.
* @throws {Error} If the article is not found or the user is not authorized.
*
* @example
* const article = await store.article.getBySku(42, 'SKU-1');
* // => { article: { id, sku, name, unit, brand, categoryIds, photos, eans, prices, ... } }
*/
return async (googleAuthId, sku) => {
const t0 = performance.now();
const result = await pool

View File

@@ -1,20 +1,20 @@
import { LIST_BY_ARTICLE_ID } from '../../sql/articlehistory.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Returns the change history of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches the article history.
*
* @example
* const fn = getHistoryBySku(pool);
* const history = await fn(42, 'SKU-1');
* // => [{ id, companyuserId, date, text }]
*/
export default function getHistoryBySku(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Returns the change history of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @returns {Promise<Array<{id: number, companyuserId: number|null, date: string, text: string}>>} The article history entries.
*
* @example
* const history = await store.article.getHistoryBySku(42, 'SKU-1');
* // => [{ id, companyuserId, date, text }]
*/
return async (googleAuthId, sku) => {
const { articleId } = await resolve(googleAuthId, sku);

View File

@@ -10,22 +10,23 @@ function parseJsonArray(raw) {
return Array.isArray(arr) ? arr : [];
}
/**
* Returns articles for export, optionally scoped to a category, including
* category breadcrumbs and image hashes.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists articles for export.
*
* @example
* const fn = listExport(pool);
* const articles = await fn(42, null);
* // => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
*/
export default function listExport(pool) {
const companyAccess = resolveSelectedCompanyAccess(pool);
const categoryAccess = resolveCategoryAccess(pool);
/**
* Returns articles for export, optionally scoped to a category, including
* category breadcrumbs and image hashes.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number|null} [categoryId] - Optional category id to scope the export to; defaults to the company root.
* @returns {Promise<Array<object>>} The articles for export.
* @throws {Error} If the category is not reachable from the selected company.
*
* @example
* const articles = await store.article.listExport(42, null);
* // => [{ id, sku, name, slug, unitSi, net, taxRate, eans, categoryBreadcrumbs, fullHashes, ... }]
*/
return async (googleAuthId, categoryId) => {
const { languageId, posCustomerGroupId, rootCategoryId } =
await companyAccess(googleAuthId);

View File

@@ -1,20 +1,19 @@
import { SEARCH_ARTICLES } from '../../sql/article.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Returns a lightweight search index of all articles for a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists the article search index.
*
* @example
* const fn = listSearchIndex(pool);
* const articles = await fn(42);
* // => [{ id, sku, name, eans, categoryIds }]
*/
export default function listSearchIndex(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Returns a lightweight search index of all articles for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<Array<{id: number, sku: string, name: string, eans: Array<string>, categoryIds: Array<number>}>>} The article search index.
*
* @example
* const articles = await store.article.listSearchIndex(42);
* // => [{ id, sku, name, eans, categoryIds }]
*/
return async (googleAuthId) => {
const { companyId, rootCategoryId, languageId } = await access(googleAuthId);

View File

@@ -6,20 +6,23 @@ import {
import resolveArticleBySku from './resolveArticleBySku.js';
import request from '../request.js';
/**
* Reorders the images of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that reorders article images.
*
* @example
* const fn = orderImages(pool);
* const result = await fn(42, 'SKU-1', { fullHashes: ['a', 'b'] });
* // => { fullHashes: ['a', 'b'] }
*/
export default function orderImages(pool) {
const resolve = resolveArticleBySku(pool);
/**
* Reorders the images of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {object} input - The reorder input.
* @param {Array<string>} input.fullHashes - The full image hashes in their new order.
* @returns {Promise<{fullHashes: Array<string>}>} The applied order.
* @throws {Error} If the order is invalid.
*
* @example
* const result = await store.article.orderImages(42, 'SKU-1', { fullHashes: ['a', 'b'] });
* // => { fullHashes: ['a', 'b'] }
*/
return async (googleAuthId, sku, { fullHashes }) => {
if (!Array.isArray(fullHashes)) {
throw new Error('Invalid image order');

View File

@@ -5,21 +5,23 @@ import {
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Sets (or clears) the brand of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the article brand.
*
* @example
* const fn = setBrandIdBySku(pool);
* const article = await fn(42, 'SKU-1', 3);
* // => { id, sku, name, ... }
*/
export default function setBrandIdBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Sets (or clears) the brand of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {number|null} brandId - The brand id, or `null` to clear the brand.
* @returns {Promise<object>} The updated article.
* @throws {Error} If the brand or article is not found.
*
* @example
* const article = await store.article.setBrandIdBySku(42, 'SKU-1', 3);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, brandId) => {
const { articleId, languageId, companyId } = await resolve(
googleAuthId,

View File

@@ -7,22 +7,24 @@ import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Replaces the category membership of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the article categories.
*
* @example
* const fn = setCategoryIdsBySku(pool);
* const article = await fn(42, 'SKU-1', [5, 9]);
* // => { id, sku, name, ... }
*/
export default function setCategoryIdsBySku(pool) {
const resolve = resolveArticleBySku(pool);
const categoryAccess = resolveCategoryAccess(pool);
const load = queryBySku(pool);
/**
* Replaces the category membership of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Array<number>} categoryIds - The category ids the article should belong to (at least one).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the category ids are invalid or the article is not found.
*
* @example
* const article = await store.article.setCategoryIdsBySku(42, 'SKU-1', [5, 9]);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, categoryIds) => {
if (!Array.isArray(categoryIds)) {
throw new Error('Invalid categoryIds');

View File

@@ -6,21 +6,23 @@ import request from '../request.js';
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Replaces the EAN list of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets article EANs.
*
* @example
* const fn = setEansBySku(pool);
* const article = await fn(42, 'SKU-1', ['4001234567890']);
* // => { id, sku, name, ... }
*/
export default function setEansBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Replaces the EAN list of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Array<string>} eans - The EANs to set (must be unique, non-empty strings).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the EANs are invalid or the article is not found.
*
* @example
* const article = await store.article.setEansBySku(42, 'SKU-1', ['4001234567890']);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, eans) => {
if (!Array.isArray(eans)) {
throw new Error('Invalid eans');

View File

@@ -42,22 +42,24 @@ function normalizePrices(prices) {
return normalized;
}
/**
* Replaces the price list of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets article prices.
*
* @example
* const fn = setPricesBySku(pool);
* const article = await fn(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
* // => { id, sku, name, ... }
*/
export default function setPricesBySku(pool) {
const resolve = resolveArticleBySku(pool);
const ensureTax = taxEnsure(pool);
const load = queryBySku(pool);
/**
* Replaces the price list of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Array<{price: number, tax: number, minmultiplier?: number}>} prices - The price entries (minmultiplier defaults to 1).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the prices are invalid or the article is not found.
*
* @example
* const article = await store.article.setPricesBySku(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, prices) => {
const normalized = normalizePrices(prices);

View File

@@ -2,21 +2,23 @@ import { UPDATE_WEIGHT } from '../../sql/article.js';
import queryBySku from './queryBySku.js';
import resolveArticleBySku from './resolveArticleBySku.js';
/**
* Sets the weight of an article by its SKU.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the article weight.
*
* @example
* const fn = setWeightBySku(pool);
* const article = await fn(42, 'SKU-1', 1.5);
* // => { id, sku, name, ... }
*/
export default function setWeightBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Sets the weight of an article by its SKU.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {number} weight - The weight (must be >= 0).
* @returns {Promise<object>} The updated article.
* @throws {Error} If the weight is invalid or the article is not found.
*
* @example
* const article = await store.article.setWeightBySku(42, 'SKU-1', 1.5);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, weight) => {
if (!Number.isFinite(weight) || weight < 0) {
throw new Error('Invalid weight');

View File

@@ -20,21 +20,26 @@ function formatUnitLabel(amount, symbol) {
return `${Number(amount)}${sym}`;
}
/**
* Updates an article's name and/or unit (SI and amount), recording history.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates an article by SKU.
*
* @example
* const fn = updateBySku(pool);
* const article = await fn(42, 'SKU-1', { name: 'Cola 1L', unitSi: 'l', unitamount: 1 });
* // => { id, sku, name, ... }
*/
export default function updateBySku(pool) {
const resolve = resolveArticleBySku(pool);
const load = queryBySku(pool);
/**
* Updates an article's name and/or unit (SI and amount), recording history.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {object} patch - The update patch.
* @param {string} [patch.name] - The new article name.
* @param {string} [patch.unitSi] - The new unit SI code.
* @param {number} [patch.unitamount] - The new unit amount (must be > 0).
* @returns {Promise<object>} The updated article.
* @throws {Error} If no valid field is provided or the article is not found.
*
* @example
* const article = await store.article.updateBySku(42, 'SKU-1', { name: 'Cola 1L', unitSi: 'l', unitamount: 1 });
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, patch = {}) => {
const hasName = Object.prototype.hasOwnProperty.call(patch, 'name');
const hasUnitamount = Object.prototype.hasOwnProperty.call(patch, 'unitamount');

View File

@@ -1,19 +1,20 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/brand.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a brand for a company by its id.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes a brand.
*
* @example
* const fn = del(pool);
* await fn(42, 3);
*/
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Deletes a brand for the user's selected company by its id.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the brand to delete.
* @returns {Promise<void>}
* @throws {Error} If the brand is not found.
*
* @example
* await store.brand.delete(42, 3);
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Brand not found');

View File

@@ -1,20 +1,19 @@
import { LIST_BY_COMPANY } from '../../sql/brand.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Lists the brands of a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists brands.
*
* @example
* const fn = list(pool);
* const brands = await fn(42);
* // => [{ id, name, gsprpinfo }]
*/
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Lists the brands of the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<Array<{id: number, name: string, gsprpinfo: string|null}>>} The brands.
*
* @example
* const brands = await store.brand.list(42);
* // => [{ id, name, gsprpinfo }]
*/
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);

View File

@@ -5,20 +5,23 @@ import {
} from '../../sql/brand.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Creates or updates a brand for a company, keyed by name.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that upserts a brand.
*
* @example
* const fn = upsert(pool);
* const brand = await fn(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
* // => { id, name, gsprpinfo }
*/
export default function upsert(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Creates or updates a brand for the user's selected company, keyed by name.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The brand input.
* @param {string} input.name - The brand name.
* @param {string} [input.gsprpinfo] - Optional GSPRP info.
* @returns {Promise<{id: number, name: string, gsprpinfo: string|null}>} The upserted brand.
* @throws {Error} If the brand is invalid.
*
* @example
* const brand = await store.brand.upsert(42, { name: 'Coca-Cola', gsprpinfo: 'x' });
* // => { id, name, gsprpinfo }
*/
return async (googleAuthId, input = {}) => {
const name = input.name?.trim();
if (!name) {

View File

@@ -5,20 +5,21 @@ import newFullHash from '../../util/newFullHash.js';
import request from '../request.js';
import resolveCategoryAccess from './resolveCategoryAccess.js';
/**
* Adds an image to a category, processing it into AVIF full and thumb variants.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that adds a category image.
*
* @example
* const fn = addImage(pool);
* const result = await fn(42, 5, imageBuffer);
* // => { id, fullHash, thumbW, thumbH, thumbHash }
*/
export default function addImage(pool) {
const access = resolveCategoryAccess(pool);
/**
* Adds an image to a category, processing it into AVIF full and thumb variants.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} categoryId - The id of the category.
* @param {Buffer} image - The source image buffer.
* @returns {Promise<{id: number, fullHash: string, thumbW: number, thumbH: number, thumbHash: string}>} The added image info.
*
* @example
* const result = await store.category.addImage(42, 5, imageBuffer);
* // => { id, fullHash, thumbW, thumbH, thumbHash }
*/
return async (googleAuthId, categoryId, image) => {
await access(googleAuthId, categoryId);

View File

@@ -7,20 +7,22 @@ import resolveCategoryAccess from './resolveCategoryAccess.js';
import request from '../request.js';
import slugify from '../slugify.js';
/**
* Creates a new category under a given parent category.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates a category.
*
* @example
* const fn = createUnderParent(pool);
* const category = await fn({ googleAuthId: 42, parentId: 5, name: 'Drinks' });
* // => { id, parentId, sort, name, slug }
*/
export default function createUnderParent(pool) {
const access = resolveCategoryAccess(pool);
/**
* Creates a new category under a given parent category.
*
* @param {object} input - The category creation input.
* @param {number} input.googleAuthId - The id of the Google auth user.
* @param {number} input.parentId - The id of the parent category.
* @param {string} input.name - The category name.
* @returns {Promise<{id: number, parentId: number, sort: number, name: string, slug: string}>} The created category.
*
* @example
* const category = await store.category.createUnderParent({ googleAuthId: 42, parentId: 5, name: 'Drinks' });
* // => { id, parentId, sort, name, slug }
*/
return async ({ googleAuthId, parentId, name }) => {
const { languageId } = await access(googleAuthId, parentId);

View File

@@ -1,19 +1,21 @@
import { DELETE } from '../../sql/category.js';
import resolveCategoryAccess from './resolveCategoryAccess.js';
/**
* Deletes a category, refusing to delete the company's root category.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes a category.
*
* @example
* const fn = del(pool);
* await fn({ googleAuthId: 42, id: 5 });
*/
export default function del(pool) {
const access = resolveCategoryAccess(pool);
/**
* Deletes a category, refusing to delete the company's root category.
*
* @param {object} input - The delete input.
* @param {number} input.googleAuthId - The id of the Google auth user.
* @param {number} input.id - The id of the category to delete.
* @returns {Promise<void>}
* @throws {Error} If the category is the root category.
*
* @example
* await store.category.delete({ googleAuthId: 42, id: 5 });
*/
return async ({ googleAuthId, id }) => {
const { rootCategoryId } = await access(googleAuthId, id);

View File

@@ -1,20 +1,21 @@
import { DELETE_BY_CATEGORY } from '../../sql/categorypic.js';
import resolveCategoryAccess from './resolveCategoryAccess.js';
/**
* Deletes the image of a category.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes a category image.
*
* @example
* const fn = deleteImage(pool);
* const result = await fn(42, 5);
* // => { id, thumbW: null, thumbH: null, thumbHash: null }
*/
export default function deleteImage(pool) {
const access = resolveCategoryAccess(pool);
/**
* Deletes the image of a category.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} categoryId - The id of the category.
* @returns {Promise<{id: number, thumbW: null, thumbH: null, thumbHash: null}>} The cleared image info.
* @throws {Error} If the image is not found.
*
* @example
* const result = await store.category.deleteImage(42, 5);
* // => { id: 5, thumbW: null, thumbH: null, thumbHash: null }
*/
return async (googleAuthId, categoryId) => {
await access(googleAuthId, categoryId);

View File

@@ -1,21 +1,21 @@
import resolveCategoryAccess from './resolveCategoryAccess.js';
import queryCategoryArticles from './queryCategoryArticles.js';
/**
* Returns the articles belonging to a single category.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches the articles of a category.
*
* @example
* const fn = getCategoryArticles(pool);
* const articles = await fn(42, 5);
* // => [{ id, sku, name, ... }]
*/
export default function getCategoryArticles(pool) {
const access = resolveCategoryAccess(pool);
const articles = queryCategoryArticles(pool);
/**
* Returns the articles belonging to a single category.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} categoryId - The id of the category.
* @returns {Promise<Array<object>>} The articles in the category.
*
* @example
* const articles = await store.category.getCategoryArticles(42, 5);
* // => [{ id, sku, name, ... }]
*/
return async (googleAuthId, categoryId) => {
const { languageId, posCustomerGroupId } = await access(
googleAuthId,

View File

@@ -3,23 +3,22 @@ import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyA
import queryCategoryArticles from './queryCategoryArticles.js';
import getCategoryPosterminalIds from '../posterminal/getCategoryPosterminalIds.js';
/**
* Returns the full category tree for a company together with its articles and
* the posterminal ids assigned to each category.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches the category tree.
*
* @example
* const fn = getTreeForCompany(pool);
* const tree = await fn(42);
* // => { languageId, tree: [{ id, posterminalIds, ... }], articles: [...] }
*/
export default function getTreeForCompany(pool) {
const access = resolveSelectedCompanyAccess(pool);
const articles = queryCategoryArticles(pool);
const posterminalIds = getCategoryPosterminalIds(pool);
/**
* Returns the full category tree for the user's selected company together
* with its articles and the posterminal ids assigned to each category.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<{languageId: number, tree: Array<object>, articles: Array<object>}>} The category tree, articles and language.
*
* @example
* const tree = await store.category.getTreeForCompany(42);
* // => { languageId, tree: [{ id, posterminalIds, ... }], articles: [...] }
*/
return async (googleAuthId) => {
const { companyId, rootCategoryId, languageId, posCustomerGroupId } =
await access(googleAuthId);

View File

@@ -6,20 +6,23 @@ import {
import request from '../request.js';
import resolveCategoryAccess from './resolveCategoryAccess.js';
/**
* Moves a category to a new parent, preventing cycles and cross-company moves.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that moves a category.
*
* @example
* const fn = move(pool);
* const result = await fn({ googleAuthId: 42, id: 5, parentId: 9 });
* // => { id, parentId, sort }
*/
export default function move(pool) {
const access = resolveCategoryAccess(pool);
/**
* Moves a category to a new parent, preventing cycles and cross-company moves.
*
* @param {object} input - The move input.
* @param {number} input.googleAuthId - The id of the Google auth user.
* @param {number} input.id - The id of the category to move.
* @param {number} input.parentId - The id of the new parent category.
* @returns {Promise<{id: number, parentId: number, sort: number}>} The moved category.
* @throws {Error} If the move would create a cycle or cross companies.
*
* @example
* const result = await store.category.move({ googleAuthId: 42, id: 5, parentId: 9 });
* // => { id, parentId, sort }
*/
return async ({ googleAuthId, id, parentId }) => {
// Source category auth (throws if missing / not authorized).
const source = await access(googleAuthId, id);

View File

@@ -5,20 +5,23 @@ import {
import request from '../request.js';
import resolveCategoryAccess from './resolveCategoryAccess.js';
/**
* Reorders the child categories of a given parent.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that reorders categories.
*
* @example
* const fn = order(pool);
* const result = await fn({ googleAuthId: 42, parentId: 5, categoryIds: [3, 1, 2] });
* // => { categoryIds: [3, 1, 2] }
*/
export default function order(pool) {
const access = resolveCategoryAccess(pool);
/**
* Reorders the child categories of a given parent.
*
* @param {object} input - The reorder input.
* @param {number} input.googleAuthId - The id of the Google auth user.
* @param {number} input.parentId - The id of the parent category.
* @param {Array<number>} input.categoryIds - The child category ids in their new order.
* @returns {Promise<{categoryIds: Array<number>}>} The applied order.
* @throws {Error} If the order is invalid.
*
* @example
* const result = await store.category.order({ googleAuthId: 42, parentId: 5, categoryIds: [3, 1, 2] });
* // => { categoryIds: [3, 1, 2] }
*/
return async ({ googleAuthId, parentId, categoryIds }) => {
if (!Array.isArray(categoryIds)) {
throw new Error('Invalid category order');

View File

@@ -2,20 +2,23 @@ import { UPDATE_BY_CATEGORY_AND_LANGUAGE } from '../../sql/categorytext.js';
import resolveCategoryAccess from './resolveCategoryAccess.js';
import slugify from '../slugify.js';
/**
* Renames a category and updates its slug.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that renames a category.
*
* @example
* const fn = rename(pool);
* const result = await fn({ googleAuthId: 42, id: 5, name: 'Soft Drinks' });
* // => { id, name, slug }
*/
export default function rename(pool) {
const access = resolveCategoryAccess(pool);
/**
* Renames a category and updates its slug.
*
* @param {object} input - The rename input.
* @param {number} input.googleAuthId - The id of the Google auth user.
* @param {number} input.id - The id of the category to rename.
* @param {string} input.name - The new category name.
* @returns {Promise<{id: number, name: string, slug: string}>} The renamed category.
* @throws {Error} If the name is empty or the category is not found.
*
* @example
* const result = await store.category.rename({ googleAuthId: 42, id: 5, name: 'Soft Drinks' });
* // => { id, name, slug }
*/
return async ({ googleAuthId, id, name }) => {
if (!name?.trim()) {
throw new Error('Name required');

View File

@@ -1,19 +1,20 @@
import { DELETE_BY_COMPANY_AND_KEY } from '../../sql/companysetting.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a company setting by key.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes a company setting.
*
* @example
* const fn = del(pool);
* await fn(42, 'theme');
*/
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Deletes a company setting by key for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} key - The setting key.
* @returns {Promise<void>}
* @throws {Error} If the setting is not found.
*
* @example
* await store.companysetting.delete(42, 'theme');
*/
return async (googleAuthId, key) => {
if (!key?.trim()) {
throw new Error('Setting not found');

View File

@@ -2,20 +2,22 @@ import { GET_BY_COMPANY_AND_KEY } from '../../sql/companysetting.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeSetting from './shapeSetting.js';
/**
* Returns a single company setting by key.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches a company setting.
*
* @example
* const fn = get(pool);
* const setting = await fn(42, 'theme');
* // => { key, value, frontend }
*/
export default function get(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Returns a single company setting by key for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} key - The setting key.
* @param {object} [options] - Optional options.
* @param {boolean} [options.frontendOnly] - Only return the setting if it is frontend-visible.
* @returns {Promise<{key: string, value: string, frontend: boolean}|undefined>} The setting, or `undefined` if not found.
*
* @example
* const setting = await store.companysetting.get(42, 'theme');
* // => { key, value, frontend }
*/
return async (googleAuthId, key, { frontendOnly = false } = {}) => {
if (!key?.trim()) {
return undefined;

View File

@@ -5,20 +5,21 @@ import {
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeSetting from './shapeSetting.js';
/**
* Lists the settings of a company, optionally only frontend-visible ones.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists company settings.
*
* @example
* const fn = list(pool);
* const settings = await fn(42, { frontendOnly: true });
* // => [{ key, value, frontend }]
*/
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Lists the settings of the user's selected company, optionally only frontend-visible ones.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} [options] - Optional options.
* @param {boolean} [options.frontendOnly] - Only return frontend-visible settings.
* @returns {Promise<Array<{key: string, value: string, frontend: boolean}>>} The settings.
*
* @example
* const settings = await store.companysetting.list(42, { frontendOnly: true });
* // => [{ key, value, frontend }]
*/
return async (googleAuthId, { frontendOnly = false } = {}) => {
const { companyId } = await access(googleAuthId);

View File

@@ -2,20 +2,24 @@ import { GET_BY_COMPANY_AND_KEY, UPSERT } from '../../sql/companysetting.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shapeSetting from './shapeSetting.js';
/**
* Sets (upserts) a company setting by key.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets a company setting.
*
* @example
* const fn = set(pool);
* const setting = await fn(42, { key: 'theme', value: 'dark', frontend: true });
* // => { key, value, frontend }
*/
export default function set(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Sets (upserts) a company setting by key for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The setting input.
* @param {string} input.key - The setting key.
* @param {string} input.value - The setting value.
* @param {boolean} [input.frontend] - Whether the setting is frontend-visible.
* @returns {Promise<{key: string, value: string, frontend: boolean}>} The upserted setting.
* @throws {Error} If the input is invalid.
*
* @example
* const setting = await store.companysetting.set(42, { key: 'theme', value: 'dark', frontend: true });
* // => { key, value, frontend }
*/
return async (googleAuthId, input = {}) => {
const key = input.key?.trim();
if (!key) {

View File

@@ -1,14 +1,16 @@
import { GET_BY_ID } from '../../sql/google_auth.js';
import resolveCompanyAccess from './resolveCompanyAccess.js';
/**
* Resolves the user's selected company and checks company membership.
* @param {object} pool
* @returns {(googleAuthId: number) => Promise<object>}
*/
export default function resolveSelectedCompanyAccess(pool) {
const access = resolveCompanyAccess(pool);
/**
* Resolves the user's selected company and checks company membership.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<{companyId: number, rootCategoryId: number, languageId: number, posCustomerGroupId: number|null, companyuserId: number}>} The resolved company access context.
* @throws {Error} If the user is not authorized or has no company selected.
*/
return async (googleAuthId) => {
if (!Number.isFinite(googleAuthId)) {
throw new Error('Not authorized');

View File

@@ -8,20 +8,22 @@ function shape(row) {
return { id: row.id, name: row.name, sort: row.sort };
}
/**
* Creates a new customer group for a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates a customer group.
*
* @example
* const fn = create(pool);
* const group = await fn(42, { name: 'Wholesale' });
* // => { id, name, sort }
*/
export default function create(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Creates a new customer group for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The customer group input.
* @param {string} input.name - The customer group name (must not be a reserved name).
* @returns {Promise<{id: number, name: string, sort: number}>} The created customer group.
* @throws {Error} If the name is empty or reserved.
*
* @example
* const group = await store.customergroup.create(42, { name: 'Wholesale' });
* // => { id, name, sort }
*/
return async (googleAuthId, { name } = {}) => {
const cleaned = name?.trim();
if (!cleaned) {

View File

@@ -1,19 +1,20 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/customergroup.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a customer group for a company by its id.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes a customer group.
*
* @example
* const fn = del(pool);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Deletes a customer group for the user's selected company by its id.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the customer group to delete.
* @returns {Promise<void>}
* @throws {Error} If the customer group is not found.
*
* @example
* await store.customergroup.delete(42, 5);
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Customer group not found');

View File

@@ -1,20 +1,19 @@
import { LIST_BY_COMPANY } from '../../sql/customergroup.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Lists the customer groups of a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists customer groups.
*
* @example
* const fn = list(pool);
* const groups = await fn(42);
* // => [{ id, name, sort }]
*/
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Lists the customer groups of the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<Array<{id: number, name: string, sort: number}>>} The customer groups.
*
* @example
* const groups = await store.customergroup.list(42);
* // => [{ id, name, sort }]
*/
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);

View File

@@ -5,20 +5,22 @@ import {
import request from '../request.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Reorders the customer groups of a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that reorders customer groups.
*
* @example
* const fn = order(pool);
* const result = await fn(42, { customergroupIds: [3, 1, 2] });
* // => { customergroupIds: [3, 1, 2] }
*/
export default function order(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Reorders the customer groups of the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The reorder input.
* @param {Array<number>} input.customergroupIds - The customer group ids in their new order.
* @returns {Promise<{customergroupIds: Array<number>}>} The applied order.
* @throws {Error} If the order is invalid.
*
* @example
* const result = await store.customergroup.order(42, { customergroupIds: [3, 1, 2] });
* // => { customergroupIds: [3, 1, 2] }
*/
return async (googleAuthId, { customergroupIds } = {}) => {
if (!Array.isArray(customergroupIds)) {
throw new Error('Invalid customer group order');

View File

@@ -8,20 +8,23 @@ function shape(row) {
return { id: row.id, name: row.name, sort: row.sort };
}
/**
* Updates the name of a customer group for a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates a customer group.
*
* @example
* const fn = update(pool);
* const group = await fn(42, 5, { name: 'Retail' });
* // => { id, name, sort }
*/
export default function update(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Updates the name of a customer group for the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the customer group to update.
* @param {object} input - The update input.
* @param {string} input.name - The new customer group name.
* @returns {Promise<{id: number, name: string, sort: number}>} The updated customer group.
* @throws {Error} If the customer group is not found or the name is empty.
*
* @example
* const group = await store.customergroup.update(42, 5, { name: 'Retail' });
* // => { id, name, sort }
*/
return async (googleAuthId, id, { name } = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Customer group not found');

View File

@@ -2,31 +2,33 @@ import { CREATE_MANIFEST, DELIVERY_TYPE } from '../../sql/delivery.js';
import request from '../request.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import taxEnsure from '../tax/ensure.js';
import {
loadDelivery,
normalizeLines,
parseDeliveryDate,
} from './helpers.js';
import { assertSupplier, insertLines } from './writeLines.js';
import { insertLines, normalizeLines, parseDate } from '../manifestLines.js';
import { loadDelivery } from './helpers.js';
import { assertSupplier } from './writeLines.js';
/**
* Creates a new delivery for a company with its lines.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates a delivery.
*
* @example
* const fn = create(pool);
* const delivery = await fn(42, { number: 'D-1', date: '2026-01-01', supplierId: 5, lines: [...] });
* // => { id, number, type, entityId, date, lines: [...] }
*/
export default function create(pool) {
const access = resolveSelectedCompanyAccess(pool);
const ensureTax = taxEnsure(pool);
/**
* Creates a new delivery for the user's selected company with its lines.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} body - The delivery input.
* @param {string} body.number - The delivery number.
* @param {string} body.date - The delivery date.
* @param {number} body.supplierId - The id of the supplier.
* @param {Array<object>} [body.lines] - The delivery lines.
* @returns {Promise<{id: number, number: string, type: string, entityId: number, date: string, lines: Array<object>}>} The created delivery.
* @throws {Error} If the delivery is invalid.
*
* @example
* const delivery = await store.delivery.create(42, { number: 'D-1', date: '2026-01-01', supplierId: 5, lines: [...] });
* // => { id, number, type, entityId, date, lines: [...] }
*/
return async (googleAuthId, body = {}) => {
const number = body.number?.trim?.() ? body.number.trim() : null;
const date = parseDeliveryDate(body.date);
const date = parseDate(body.date);
if (!number || !date) {
throw new Error('Invalid delivery');
}

View File

@@ -1,19 +1,20 @@
import { DELETE_MANIFEST, DELIVERY_TYPE } from '../../sql/delivery.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes a delivery by its id within a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes a delivery.
*
* @example
* const fn = del(pool);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Deletes a delivery by its id within the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the delivery to delete.
* @returns {Promise<void>}
* @throws {Error} If the delivery is not found.
*
* @example
* await store.delivery.delete(42, 5);
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Delivery not found');

View File

@@ -1,20 +1,21 @@
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import { loadDelivery } from './helpers.js';
/**
* Returns a single delivery by its id within a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches a delivery by id.
*
* @example
* const fn = getById(pool);
* const delivery = await fn(42, 5);
* // => { id, number, type, entityId, date, lines: [...] }
*/
export default function getById(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Returns a single delivery by its id within the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the delivery.
* @returns {Promise<{id: number, number: string, type: string, entityId: number, date: string, lines: Array<object>}>} The delivery.
* @throws {Error} If the delivery is not found.
*
* @example
* const delivery = await store.delivery.getById(42, 5);
* // => { id, number, type, entityId, date, lines: [...] }
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Delivery not found');

View File

@@ -4,6 +4,7 @@ import {
GET_DELIVERY_LINES,
} from '../../sql/delivery.js';
import request from '../request.js';
import { shapeLine } from '../manifestLines.js';
export function shapeSummary(row) {
return {
@@ -15,19 +16,6 @@ export function shapeSummary(row) {
};
}
export function shapeLine(row) {
return {
id: row.id,
articleId: row.articleId ?? null,
sku: row.sku ?? null,
amount: row.amount,
net: row.net,
rebate: row.rebate,
tax: row.tax,
comment: row.comment ?? null,
};
}
export async function loadDelivery(pool, tx, companyId, id) {
const header = await request(pool, tx)
.input('id', id)
@@ -49,50 +37,3 @@ export async function loadDelivery(pool, tx, companyId, id) {
lines: lines.recordset.map(shapeLine),
};
}
export function parseDeliveryDate(value) {
if (value instanceof Date && !Number.isNaN(value.getTime())) {
return value;
}
if (typeof value === 'string' && value.trim()) {
const d = new Date(value);
if (!Number.isNaN(d.getTime())) {
return d;
}
}
return null;
}
export function normalizeLines(lines) {
if (!Array.isArray(lines)) {
throw new Error('Invalid lines');
}
return lines.map((entry) => {
const sku = entry?.sku?.trim?.() ? entry.sku.trim() : null;
if (!sku) {
throw new Error('Invalid lines');
}
const amount = entry?.amount;
const net = entry?.net;
const rebate =
entry?.rebate === undefined || entry?.rebate === null ? 0 : entry.rebate;
const tax = entry?.tax;
const comment =
entry?.comment === undefined || entry?.comment === null
? null
: String(entry.comment);
if (
!Number.isFinite(amount) ||
!Number.isFinite(net) ||
!Number.isFinite(rebate) ||
!Number.isFinite(tax)
) {
throw new Error('Invalid lines');
}
return { sku, amount, net, rebate, tax, comment };
});
}

View File

@@ -2,20 +2,19 @@ import { DELIVERY_TYPE, LIST_DELIVERIES } from '../../sql/delivery.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import { shapeSummary } from './helpers.js';
/**
* Lists the deliveries of a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists deliveries.
*
* @example
* const fn = list(pool);
* const deliveries = await fn(42);
* // => [{ id, number, type, entityId, date }]
*/
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Lists the deliveries of the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<Array<{id: number, number: string, type: string, entityId: number, date: string}>>} The deliveries.
*
* @example
* const deliveries = await store.delivery.list(42);
* // => [{ id, number, type, entityId, date }]
*/
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);

View File

@@ -7,29 +7,31 @@ import {
import request from '../request.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import taxEnsure from '../tax/ensure.js';
import {
loadDelivery,
normalizeLines,
parseDeliveryDate,
shapeSummary,
} from './helpers.js';
import { assertSupplier, insertLines } from './writeLines.js';
import { insertLines, normalizeLines, parseDate } from '../manifestLines.js';
import { loadDelivery, shapeSummary } from './helpers.js';
import { assertSupplier } from './writeLines.js';
/**
* Updates a delivery's header and/or lines.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates a delivery.
*
* @example
* const fn = update(pool);
* const delivery = await fn(42, 5, { number: 'D-2', lines: [...] });
* // => { id, number, type, entityId, date, lines: [...] }
*/
export default function update(pool) {
const access = resolveSelectedCompanyAccess(pool);
const ensureTax = taxEnsure(pool);
/**
* Updates a delivery's header and/or lines.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the delivery to update.
* @param {object} patch - The update patch.
* @param {string} [patch.number] - The new delivery number.
* @param {string} [patch.date] - The new delivery date.
* @param {number} [patch.supplierId] - The new supplier id.
* @param {Array<object>} [patch.lines] - The new delivery lines.
* @returns {Promise<{id: number, number: string, type: string, entityId: number, date: string, lines: Array<object>}>} The updated delivery.
* @throws {Error} If the delivery is not found.
*
* @example
* const delivery = await store.delivery.update(42, 5, { number: 'D-2', lines: [...] });
* // => { id, number, type, entityId, date, lines: [...] }
*/
return async (googleAuthId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Delivery not found');
@@ -81,7 +83,7 @@ export default function update(pool) {
let date = summary.date;
if (hasDate) {
date = parseDeliveryDate(patch.date);
date = parseDate(patch.date);
if (!date) {
throw new Error('Invalid delivery');
}

View File

@@ -1,7 +1,3 @@
import {
CREATE_LINE,
RESOLVE_ARTICLE_ID_BY_SKU,
} from '../../sql/delivery.js';
import { GET_SUPPLIER_BY_ID, SUPPLIER_GROUP_NAME } from '../../sql/entity.js';
import request from '../request.js';
@@ -24,42 +20,3 @@ export async function assertSupplier(pool, tx, companyId, supplierId) {
}
return supplierId;
}
async function resolveArticleId(pool, tx, rootCategoryId, sku) {
const result = await request(pool, tx)
.input('rootCategoryId', rootCategoryId)
.input('sku', sku)
.query(RESOLVE_ARTICLE_ID_BY_SKU);
const articleId = result.recordset[0]?.id;
if (!articleId) {
throw new Error('Article not found');
}
return articleId;
}
export async function insertLines(
pool,
tx,
{ manifestId, rootCategoryId, lines, ensureTax },
) {
for (const line of lines) {
const articleId = await resolveArticleId(
pool,
tx,
rootCategoryId,
line.sku,
);
const taxId = await ensureTax(line.tax, tx);
await request(pool, tx)
.input('manifestId', manifestId)
.input('articleId', articleId)
.input('amount', line.amount)
.input('net', line.net)
.input('comment', line.comment)
.input('rebate', line.rebate)
.input('taxId', taxId)
.query(CREATE_LINE);
}
}

View File

@@ -6,24 +6,29 @@ import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAcc
import nextNumber from '../numbers/nextNumber.js';
import shape from './shape.js';
/**
* Creates a new entity (customer) in a customer group, recording history.
* Optional `number`; if omitted, allocates via `getnextnumberwithprefixandpadding`
* (`type` `entity`).
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates an entity.
*
* @example
* const fn = create(pool);
* const entity = await fn(42, { customergroupId: 5, name: 'Doe', firstName: 'John' });
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
export default function create(pool) {
const access = resolveSelectedCompanyAccess(pool);
const groupAccess = resolveCustomerGroupAccess(pool);
const allocateNumber = nextNumber(pool);
/**
* Creates a new entity (customer) in a customer group, recording history.
* Optional `number`; if omitted, allocates via `getnextnumberwithprefixandpadding`
* (`type` `entity`).
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The entity creation input.
* @param {number} input.customergroupId - The id of the customer group.
* @param {string} input.name - The entity name.
* @param {string} [input.firstName] - The entity first name.
* @param {string} [input.number] - Optional explicit customer number.
* @returns {Promise<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>} The created entity.
* @throws {Error} If the name is empty.
*
* @example
* const entity = await store.entity.create(42, { customergroupId: 5, name: 'Doe', firstName: 'John' });
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
return async (googleAuthId, { customergroupId, name, firstName = null, number } = {}) => {
const cleaned = name?.trim();
if (!cleaned) {

View File

@@ -1,19 +1,20 @@
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Deletes an entity by its id within a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes an entity.
*
* @example
* const fn = del(pool);
* await fn(42, 5);
*/
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Deletes an entity by its id within the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the entity to delete.
* @returns {Promise<void>}
* @throws {Error} If the entity is not found.
*
* @example
* await store.entity.delete(42, 5);
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');

View File

@@ -2,20 +2,21 @@ import { GET_BY_COMPANY_AND_ID } from '../../sql/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shape from './shape.js';
/**
* Returns a single entity by its id within a company.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches an entity by id.
*
* @example
* const fn = getById(pool);
* const entity = await fn(42, 5);
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
export default function getById(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Returns a single entity by its id within the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the entity.
* @returns {Promise<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>} The entity.
* @throws {Error} If the entity is not found.
*
* @example
* const entity = await store.entity.getById(42, 5);
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');

View File

@@ -1,23 +1,26 @@
import { NUMBER_TAKEN_IN_COMPANY } from '../../sql/entity.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
/**
* Returns whether a customer number is free within the company.
* Pass `excludeEntityId` when editing so the entity's own number counts as free.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that checks number availability.
*
* @example
* const fn = isNumberFree(pool);
* const free = await fn(42, 'C-100');
* // => true
* const freeOnEdit = await fn(42, 'C-100', { excludeEntityId: 5 });
* // => true
*/
export default function isNumberFree(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Returns whether a customer number is free within the user's selected company.
* Pass `excludeEntityId` when editing so the entity's own number counts as free.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} number - The customer number to check.
* @param {object} [options] - Optional options.
* @param {number} [options.excludeEntityId] - An entity id to exclude from the check.
* @returns {Promise<boolean>} Whether the number is free.
* @throws {Error} If the number is empty.
*
* @example
* const free = await store.entity.isNumberFree(42, 'C-100');
* // => true
* const freeOnEdit = await store.entity.isNumberFree(42, 'C-100', { excludeEntityId: 5 });
* // => true
*/
return async (googleAuthId, number, { excludeEntityId } = {}) => {
const cleaned =
number == null || number === '' ? null : String(number).trim() || null;

View File

@@ -9,22 +9,25 @@ import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyA
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
import shape from './shape.js';
/**
* Lists the entities (customers) of a company, optionally filtered by customer
* group or restricted to the 'customer' role.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists entities.
*
* @example
* const fn = list(pool);
* const entities = await fn(42, { customergroupId: 5 });
* // => [{ id, customergroupId, firstName, name, number, createdAt, updatedAt }]
*/
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
const groupAccess = resolveCustomerGroupAccess(pool);
/**
* Lists the entities (customers) of the user's selected company, optionally
* filtered by customer group or restricted to the 'customer' role.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} [options] - Optional options.
* @param {number} [options.customergroupId] - Filter by customer group id.
* @param {string} [options.role] - Restrict to `'customer'` role if set.
* @returns {Promise<Array<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>>} The entities.
* @throws {Error} If the role is invalid.
*
* @example
* const entities = await store.entity.list(42, { customergroupId: 5 });
* // => [{ id, customergroupId, firstName, name, number, createdAt, updatedAt }]
*/
return async (googleAuthId, { customergroupId, role } = {}) => {
const { companyId } = await access(googleAuthId);

View File

@@ -1,22 +1,21 @@
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import peekNumber from '../numbers/peekNumber.js';
/**
* Returns the next customer number that would be allocated for the company
* (`numbers` type `entity`) without consuming it.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that peeks the next entity number.
*
* @example
* const fn = peekNextNumber(pool);
* const number = await fn(42);
* // => '1'
*/
export default function peekNextNumber(pool) {
const access = resolveSelectedCompanyAccess(pool);
const peek = peekNumber(pool);
/**
* Returns the next customer number that would be allocated for the user's
* selected company (`numbers` type `entity`) without consuming it.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @returns {Promise<string>} The next entity number.
*
* @example
* const number = await store.entity.peekNextNumber(42);
* // => '1'
*/
return async (googleAuthId) => {
const { companyId } = await access(googleAuthId);
return peek(companyId, 'entity');

View File

@@ -4,21 +4,26 @@ import request from '../request.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import shape from './shape.js';
/**
* Updates name, first name, and/or customer number of an entity, recording history.
* Optional `number` sets the customer number explicitly (does not auto-allocate).
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates an entity.
*
* @example
* const fn = update(pool);
* const entity = await fn(42, 5, { name: 'Doe', firstName: 'Jane', number: 'C-42' });
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
export default function update(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Updates name, first name, and/or customer number of an entity, recording history.
* Optional `number` sets the customer number explicitly (does not auto-allocate).
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the entity to update.
* @param {object} patch - The update patch.
* @param {string} [patch.name] - The new entity name.
* @param {string} [patch.firstName] - The new first name.
* @param {string} [patch.number] - The new customer number.
* @returns {Promise<{id: number, customergroupId: number, firstName: string|null, name: string, number: string, createdAt: string, updatedAt: string}>} The updated entity.
* @throws {Error} If no valid field is provided or the entity is not found.
*
* @example
* const entity = await store.entity.update(42, 5, { name: 'Doe', firstName: 'Jane', number: 'C-42' });
* // => { id, customergroupId, firstName, name, number, createdAt, updatedAt }
*/
return async (googleAuthId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Entity not found');

View File

@@ -24,21 +24,32 @@ function optionalString(value) {
return s || null;
}
/**
* Creates a new address for an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates an entity address.
*
* @example
* const fn = create(pool);
* const address = await fn(42, 5, { addressType: 'billing', street: 'Main St', city: 'Berlin' });
* // => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
*/
export default function create(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Creates a new address for an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @param {object} input - The address input.
* @param {string} input.addressType - The address type (e.g. `'billing'`).
* @param {string} [input.firstName] - The first name.
* @param {string} [input.name] - The name.
* @param {string} [input.street] - The street.
* @param {string} [input.streetNumber] - The street number.
* @param {string} [input.zip] - The zip code.
* @param {string} [input.city] - The city.
* @param {string} [input.county] - The county.
* @param {number} [input.sort] - Optional sort order.
* @returns {Promise<object>} The created address.
* @throws {Error} If the address type is missing.
*
* @example
* const address = await store.entityaddress.create(42, 5, { addressType: 'billing', street: 'Main St', city: 'Berlin' });
* // => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
*/
return async (googleAuthId, entityId, input = {}) => {
const addressType = input.addressType?.trim?.()
? input.addressType.trim()

View File

@@ -2,20 +2,22 @@ import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entityaddress.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
/**
* Deletes an address of an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes an entity address.
*
* @example
* const fn = del(pool);
* await fn(42, 5, 9);
*/
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Deletes an address of an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @param {number} id - The id of the address to delete.
* @returns {Promise<void>}
* @throws {Error} If the address is not found.
*
* @example
* await store.entityaddress.delete(42, 5, 9);
*/
return async (googleAuthId, entityId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Address not found');

View File

@@ -18,21 +18,21 @@ function shape(row) {
};
}
/**
* Lists the addresses of an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists entity addresses.
*
* @example
* const fn = list(pool);
* const addresses = await fn(42, 5);
* // => [{ id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }]
*/
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Lists the addresses of an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @returns {Promise<Array<object>>} The addresses.
*
* @example
* const addresses = await store.entityaddress.list(42, 5);
* // => [{ id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }]
*/
return async (googleAuthId, entityId) => {
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);

View File

@@ -24,21 +24,24 @@ function optionalString(value) {
return s || null;
}
/**
* Updates an address of an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates an entity address.
*
* @example
* const fn = update(pool);
* const address = await fn(42, 5, 9, { city: 'Hamburg' });
* // => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
*/
export default function update(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Updates an address of an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @param {number} id - The id of the address to update.
* @param {object} patch - The update patch (any subset of address fields).
* @returns {Promise<object>} The updated address.
* @throws {Error} If the address is not found.
*
* @example
* const address = await store.entityaddress.update(42, 5, 9, { city: 'Hamburg' });
* // => { id, entityId, firstName, name, street, streetNumber, zip, city, county, addressType, sort }
*/
return async (googleAuthId, entityId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Address not found');

View File

@@ -12,21 +12,26 @@ function shape(row) {
};
}
/**
* Creates a new contact for an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that creates an entity contact.
*
* @example
* const fn = create(pool);
* const contact = await fn(42, 5, { contactType: 'email', contact: 'a@b.c' });
* // => { id, entityId, contactType, contact, sort }
*/
export default function create(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Creates a new contact for an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @param {object} input - The contact input.
* @param {string} input.contactType - The contact type (e.g. `'email'`).
* @param {string} input.contact - The contact value.
* @param {number} [input.sort] - Optional sort order.
* @returns {Promise<{id: number, entityId: number, contactType: string, contact: string, sort: number}>} The created contact.
* @throws {Error} If the contact type or value is missing.
*
* @example
* const contact = await store.entitycontact.create(42, 5, { contactType: 'email', contact: 'a@b.c' });
* // => { id, entityId, contactType, contact, sort }
*/
return async (googleAuthId, entityId, input = {}) => {
const contactType = input.contactType?.trim?.()
? input.contactType.trim()

View File

@@ -2,20 +2,22 @@ import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entitycontact.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
/**
* Deletes a contact of an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that deletes an entity contact.
*
* @example
* const fn = del(pool);
* await fn(42, 5, 9);
*/
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Deletes a contact of an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @param {number} id - The id of the contact to delete.
* @returns {Promise<void>}
* @throws {Error} If the contact is not found.
*
* @example
* await store.entitycontact.delete(42, 5, 9);
*/
return async (googleAuthId, entityId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Contact not found');

View File

@@ -12,21 +12,21 @@ function shape(row) {
};
}
/**
* Lists the contacts of an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists entity contacts.
*
* @example
* const fn = list(pool);
* const contacts = await fn(42, 5);
* // => [{ id, entityId, contactType, contact, sort }]
*/
export default function list(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Lists the contacts of an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @returns {Promise<Array<{id: number, entityId: number, contactType: string, contact: string, sort: number}>>} The contacts.
*
* @example
* const contacts = await store.entitycontact.list(42, 5);
* // => [{ id, entityId, contactType, contact, sort }]
*/
return async (googleAuthId, entityId) => {
const { companyId } = await access(googleAuthId);
await entityAccess(companyId, entityId);

View File

@@ -12,21 +12,24 @@ function shape(row) {
};
}
/**
* Updates a contact of an entity.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that updates an entity contact.
*
* @example
* const fn = update(pool);
* const contact = await fn(42, 5, 9, { contact: 'b@c.d' });
* // => { id, entityId, contactType, contact, sort }
*/
export default function update(pool) {
const access = resolveSelectedCompanyAccess(pool);
const entityAccess = resolveEntityAccess(pool);
/**
* Updates a contact of an entity.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} entityId - The id of the entity.
* @param {number} id - The id of the contact to update.
* @param {object} patch - The update patch (any subset of contact fields).
* @returns {Promise<{id: number, entityId: number, contactType: string, contact: string, sort: number}>} The updated contact.
* @throws {Error} If the contact is not found.
*
* @example
* const contact = await store.entitycontact.update(42, 5, 9, { contact: 'b@c.d' });
* // => { id, entityId, contactType, contact, sort }
*/
return async (googleAuthId, entityId, id, patch = {}) => {
if (!Number.isFinite(id)) {
throw new Error('Contact not found');

View File

@@ -1,17 +1,16 @@
import { ADD } from '../../sql/google_auth.js';
/**
* Adds a new allowed Google auth email.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that adds an allowed email.
*
* @example
* const fn = add(pool);
* const id = await fn('user@example.com');
* // => 1
*/
export default function add(pool) {
/**
* Adds a new allowed Google auth email.
*
* @param {string} email - The email address to allow.
* @returns {Promise<number>} The id of the created Google auth row.
*
* @example
* const id = await store.google_auth.add('user@example.com');
* // => 1
*/
return async (email) => {
const result = await pool
.request()

View File

@@ -1,17 +1,16 @@
import { GET_BY_EMAIL } from '../../sql/google_auth.js';
/**
* Returns a Google auth row by email.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches a Google auth row by email.
*
* @example
* const fn = getByEmail(pool);
* const auth = await fn('user@example.com');
* // => { id, email, ... }
*/
export default function getByEmail(pool) {
/**
* Returns a Google auth row by email.
*
* @param {string} email - The email address.
* @returns {Promise<object|undefined>} The Google auth row, or `undefined` if not found.
*
* @example
* const auth = await store.google_auth.getByEmail('user@example.com');
* // => { id, email, ... }
*/
return async (email) => {
const result = await pool
.request()

View File

@@ -1,17 +1,16 @@
import { GET_BY_ID } from '../../sql/google_auth.js';
/**
* Returns a Google auth row by its id.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that fetches a Google auth row by id.
*
* @example
* const fn = getById(pool);
* const auth = await fn(42);
* // => { id, email, ... }
*/
export default function getById(pool) {
/**
* Returns a Google auth row by its id.
*
* @param {number} googleAuthId - The id of the Google auth row.
* @returns {Promise<object|undefined>} The Google auth row, or `undefined` if not found.
*
* @example
* const auth = await store.google_auth.getById(42);
* // => { id, email, ... }
*/
return async (googleAuthId) => {
const result = await pool
.request()

View File

@@ -1,17 +1,15 @@
import { LIST_ALLOWED_EMAILS } from '../../sql/google_auth.js';
/**
* Lists all allowed Google auth emails.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that lists allowed emails.
*
* @example
* const fn = listAllowedEmails(pool);
* const emails = await fn();
* // => [{ id, email }]
*/
export default function listAllowedEmails(pool) {
/**
* Lists all allowed Google auth emails.
*
* @returns {Promise<Array<{id: number, email: string}>>} The allowed emails.
*
* @example
* const emails = await store.google_auth.listAllowedEmails();
* // => [{ id, email }]
*/
return async () => {
const result = await pool.request().query(LIST_ALLOWED_EMAILS);
return result.recordset;

View File

@@ -1,16 +1,15 @@
import { REMOVE_ALLOWED_EMAIL } from '../../sql/google_auth.js';
/**
* Removes an allowed Google auth email.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that removes an allowed email.
*
* @example
* const fn = removeAllowedEmail(pool);
* await fn('user@example.com');
*/
export default function removeAllowedEmail(pool) {
/**
* Removes an allowed Google auth email.
*
* @param {string} email - The email address to remove.
* @returns {Promise<void>}
*
* @example
* await store.google_auth.removeAllowedEmail('user@example.com');
*/
return async (email) => {
await pool
.request()

View File

@@ -1,16 +1,20 @@
import { SET_PROFILE } from '../../sql/google_auth.js';
/**
* Updates the profile (picture, picture binary, sub) of a Google auth user.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the user profile.
*
* @example
* const fn = setProfile(pool);
* await fn(42, { picture: 'url', pictureBinary: buffer, sub: 'sub' });
*/
export default function setProfile(pool) {
/**
* Updates the profile (picture, picture binary, sub) of a Google auth user.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} input - The profile input.
* @param {string} [input.picture] - The picture URL.
* @param {Buffer} [input.pictureBinary] - The picture binary.
* @param {string} [input.sub] - The Google sub identifier.
* @returns {Promise<void>}
* @throws {Error} If the user is not found or the picture binary is invalid.
*
* @example
* await store.google_auth.setProfile(42, { picture: 'url', pictureBinary: buffer, sub: 'sub' });
*/
return async (
googleAuthId,
{ picture = null, pictureBinary = null, sub = null } = {},

View File

@@ -1,16 +1,16 @@
import { SET_SELECTED_COMPANY } from '../../sql/google_auth.js';
/**
* Sets the currently selected company for a Google auth user.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that sets the selected company.
*
* @example
* const fn = setSelectedCompany(pool);
* await fn(42, 1);
*/
export default function setSelectedCompany(pool) {
/**
* Sets the currently selected company for a Google auth user.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} companyId - The id of the company to select.
* @returns {Promise<void>}
*
* @example
* await store.google_auth.setSelectedCompany(42, 1);
*/
return async (googleAuthId, companyId) => {
await pool
.request()

View File

@@ -1,16 +1,14 @@
import { SCHEMA_SCRIPTS } from '../schema.js';
/**
* Initializes the database schema by running all schema scripts.
*
* @param {object} pool - The mssql connection pool.
* @returns {Function} An async function that initializes the schema.
*
* @example
* const fn = initialize(pool);
* await fn();
*/
export default function initialize(pool) {
/**
* Initializes the database schema by running all schema scripts.
*
* @returns {Promise<void>}
*
* @example
* await store.initialize();
*/
return async () => {
for (const script of SCHEMA_SCRIPTS) {
await pool.request().query(script);

View File

@@ -1,424 +0,0 @@
import {
CREATE_LINE,
CREATE_MANIFEST,
DELETE_LINES_BY_MANIFEST,
DELETE_MANIFEST,
GET_MANIFEST_HEADER,
GET_MANIFEST_LINES,
LIST_MANIFESTS,
RESOLVE_ARTICLE_ID_BY_SKU,
UPDATE_MANIFEST,
} from '../../sql/manifestDoc.js';
import {
GET_CUSTOMER_BY_ID,
POS_GROUP_NAME,
SUPPLIER_GROUP_NAME,
} from '../../sql/entity.js';
import request from '../request.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import taxEnsure from '../tax/ensure.js';
function shapeSummary(type, row) {
return {
id: row.id,
number: row.number,
type,
entityId: row.entityId ?? null,
date: row.date,
};
}
function shapeLine(row) {
return {
id: row.id,
articleId: row.articleId ?? null,
sku: row.sku ?? null,
amount: row.amount,
net: row.net,
rebate: row.rebate,
tax: row.tax,
comment: row.comment ?? null,
};
}
function parseDate(value) {
if (value instanceof Date && !Number.isNaN(value.getTime())) {
return value;
}
if (typeof value === 'string' && value.trim()) {
const d = new Date(value);
if (!Number.isNaN(d.getTime())) {
return d;
}
}
return null;
}
function normalizeLines(lines) {
if (!Array.isArray(lines)) {
throw new Error('Invalid lines');
}
return lines.map((entry) => {
const sku = entry?.sku?.trim?.() ? entry.sku.trim() : null;
if (!sku) {
throw new Error('Invalid lines');
}
const amount = entry?.amount;
const net = entry?.net;
const rebate =
entry?.rebate === undefined || entry?.rebate === null ? 0 : entry.rebate;
const tax = entry?.tax;
const comment =
entry?.comment === undefined || entry?.comment === null
? null
: String(entry.comment);
if (
!Number.isFinite(amount) ||
!Number.isFinite(net) ||
!Number.isFinite(rebate) ||
!Number.isFinite(tax)
) {
throw new Error('Invalid lines');
}
return { sku, amount, net, rebate, tax, comment };
});
}
async function assertCustomerEntity(pool, tx, companyId, entityId) {
if (entityId == null) {
return null;
}
if (!Number.isFinite(entityId)) {
throw new Error('Entity not found');
}
const result = await request(pool, tx)
.input('id', entityId)
.input('companyId', companyId)
.input('supplierGroupName', SUPPLIER_GROUP_NAME)
.input('posGroupName', POS_GROUP_NAME)
.query(GET_CUSTOMER_BY_ID);
if (!result.recordset[0]) {
throw new Error('Entity not found');
}
return entityId;
}
async function resolveArticleId(pool, tx, rootCategoryId, sku) {
const result = await request(pool, tx)
.input('rootCategoryId', rootCategoryId)
.input('sku', sku)
.query(RESOLVE_ARTICLE_ID_BY_SKU);
const articleId = result.recordset[0]?.id;
if (!articleId) {
throw new Error('Article not found');
}
return articleId;
}
async function insertLines(
pool,
tx,
{ manifestId, rootCategoryId, lines, ensureTax },
) {
for (const line of lines) {
const articleId = await resolveArticleId(
pool,
tx,
rootCategoryId,
line.sku,
);
const taxId = await ensureTax(line.tax, tx);
await request(pool, tx)
.input('manifestId', manifestId)
.input('articleId', articleId)
.input('amount', line.amount)
.input('net', line.net)
.input('comment', line.comment)
.input('rebate', line.rebate)
.input('taxId', taxId)
.query(CREATE_LINE);
}
}
async function loadDoc(pool, tx, { type, companyId, id }) {
const header = await request(pool, tx)
.input('id', id)
.input('companyId', companyId)
.input('type', type)
.query(GET_MANIFEST_HEADER);
const row = header.recordset[0];
if (!row) {
return null;
}
const lines = await request(pool, tx)
.input('manifestId', id)
.query(GET_MANIFEST_LINES);
return {
...shapeSummary(type, row),
lines: lines.recordset.map(shapeLine),
};
}
/**
* Factory for quote / order (and similar) stores on manifest / manifestarticle.
*
* @param {object} pool
* @param {{ type: string, label: string }} options
* `type` → manifest.type (`quote` / `order`)
* `label` → error prefix (`Quote` / `Order` → "Quote not found")
*/
export default function createManifestDocStore(pool, { type, label }) {
const access = resolveSelectedCompanyAccess(pool);
const ensureTax = taxEnsure(pool);
const notFound = `${label} not found`;
const invalid = `Invalid ${label.toLowerCase()}`;
/**
* Lists the quote/order documents of a company.
*
* @param {number} googleAuthId - The Google auth user id.
* @returns {Promise<Array<object>>} The document summaries.
*/
async function list(googleAuthId) {
const { companyId } = await access(googleAuthId);
const result = await pool
.request()
.input('companyId', companyId)
.input('type', type)
.query(LIST_MANIFESTS);
return result.recordset.map((row) => shapeSummary(type, row));
}
/**
* Returns a single quote/order document by its id within a company.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} id - The document id.
* @returns {Promise<object|null>} The document with its lines, or null if not found.
*/
async function getById(googleAuthId, id) {
if (!Number.isFinite(id)) {
throw new Error(notFound);
}
const { companyId } = await access(googleAuthId);
const doc = await loadDoc(pool, null, { type, companyId, id });
if (!doc) {
throw new Error(notFound);
}
return doc;
}
/**
* Creates a new quote/order document with its lines.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {object} body - The document payload (`number`, `date`, `entityId`, `lines`).
* @returns {Promise<object>} The created document with its lines.
*/
async function create(googleAuthId, body = {}) {
const number = body.number?.trim?.() ? body.number.trim() : null;
const date = parseDate(body.date);
if (!number || !date) {
throw new Error(invalid);
}
const lines = normalizeLines(body.lines ?? []);
const { companyId, rootCategoryId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();
try {
const entityId = await assertCustomerEntity(
pool,
tx,
companyId,
body.entityId ?? null,
);
const created = await request(pool, tx)
.input('companyId', companyId)
.input('number', number)
.input('type', type)
.input('entityId', entityId)
.input('date', date)
.query(CREATE_MANIFEST);
const manifestId = created.recordset[0].id;
await insertLines(pool, tx, {
manifestId,
rootCategoryId,
lines,
ensureTax,
});
const doc = await loadDoc(pool, tx, { type, companyId, id: manifestId });
await tx.commit();
return doc;
} catch (err) {
try {
await tx.rollback();
} catch {
// ignore
}
throw err;
}
}
/**
* Updates a quote/order document's header and/or lines.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} id - The document id.
* @param {object} patch - The fields to update (`number`, `date`, `entityId`, `lines`).
* @returns {Promise<object>} The updated document with its lines.
*/
async function update(googleAuthId, id, patch = {}) {
if (!Number.isFinite(id)) {
throw new Error(notFound);
}
const hasNumber = Object.prototype.hasOwnProperty.call(patch, 'number');
const hasDate = Object.prototype.hasOwnProperty.call(patch, 'date');
const hasEntityId = Object.prototype.hasOwnProperty.call(patch, 'entityId');
const hasLines = Object.prototype.hasOwnProperty.call(patch, 'lines');
if (!hasNumber && !hasDate && !hasEntityId && !hasLines) {
throw new Error(invalid);
}
let normalizedLines;
if (hasLines) {
normalizedLines = normalizeLines(patch.lines);
}
const { companyId, rootCategoryId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();
try {
const existing = await request(pool, tx)
.input('id', id)
.input('companyId', companyId)
.input('type', type)
.query(GET_MANIFEST_HEADER);
const current = existing.recordset[0];
if (!current) {
throw new Error(notFound);
}
const summary = shapeSummary(type, current);
let number = summary.number;
if (hasNumber) {
number = patch.number?.trim?.() ? patch.number.trim() : null;
if (!number) {
throw new Error(invalid);
}
}
let date = summary.date;
if (hasDate) {
date = parseDate(patch.date);
if (!date) {
throw new Error(invalid);
}
}
let entityId = summary.entityId;
if (hasEntityId) {
entityId = await assertCustomerEntity(
pool,
tx,
companyId,
patch.entityId ?? null,
);
}
await request(pool, tx)
.input('id', id)
.input('companyId', companyId)
.input('type', type)
.input('number', number)
.input('entityId', entityId)
.input('date', date)
.query(UPDATE_MANIFEST);
if (hasLines) {
await request(pool, tx)
.input('manifestId', id)
.query(DELETE_LINES_BY_MANIFEST);
await insertLines(pool, tx, {
manifestId: id,
rootCategoryId,
lines: normalizedLines,
ensureTax,
});
}
const doc = await loadDoc(pool, tx, { type, companyId, id });
await tx.commit();
return doc;
} catch (err) {
try {
await tx.rollback();
} catch {
// ignore
}
throw err;
}
}
/**
* Deletes a quote/order document by its id within a company.
*
* @param {number} googleAuthId - The Google auth user id.
* @param {number} id - The document id.
* @returns {Promise<void>}
*/
async function del(googleAuthId, id) {
if (!Number.isFinite(id)) {
throw new Error(notFound);
}
const { companyId } = await access(googleAuthId);
const result = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.input('type', type)
.query(DELETE_MANIFEST);
if (!result.rowsAffected[0]) {
throw new Error(notFound);
}
}
return {
list,
getById,
create,
update,
delete: del,
};
}

View File

@@ -0,0 +1,64 @@
import {
GET_MANIFEST_HEADER,
GET_MANIFEST_LINES,
} from '../../sql/manifestDoc.js';
import {
GET_CUSTOMER_BY_ID,
POS_GROUP_NAME,
SUPPLIER_GROUP_NAME,
} from '../../sql/entity.js';
import request from '../request.js';
import { shapeLine } from '../manifestLines.js';
export function shapeSummary(type, row) {
return {
id: row.id,
number: row.number,
type,
entityId: row.entityId ?? null,
date: row.date,
};
}
export async function assertCustomerEntity(pool, tx, companyId, entityId) {
if (entityId == null) {
return null;
}
if (!Number.isFinite(entityId)) {
throw new Error('Entity not found');
}
const result = await request(pool, tx)
.input('id', entityId)
.input('companyId', companyId)
.input('supplierGroupName', SUPPLIER_GROUP_NAME)
.input('posGroupName', POS_GROUP_NAME)
.query(GET_CUSTOMER_BY_ID);
if (!result.recordset[0]) {
throw new Error('Entity not found');
}
return entityId;
}
export async function loadDoc(pool, tx, { type, companyId, id }) {
const header = await request(pool, tx)
.input('id', id)
.input('companyId', companyId)
.input('type', type)
.query(GET_MANIFEST_HEADER);
const row = header.recordset[0];
if (!row) {
return null;
}
const lines = await request(pool, tx)
.input('manifestId', id)
.query(GET_MANIFEST_LINES);
return {
...shapeSummary(type, row),
lines: lines.recordset.map(shapeLine),
};
}

133
src/store/manifestLines.js Normal file
View File

@@ -0,0 +1,133 @@
import { CREATE_LINE, RESOLVE_ARTICLE_ID_BY_SKU } from '../sql/manifestDoc.js';
import request from './request.js';
/**
* Normalizes and validates an array of manifest line inputs.
*
* @param {Array<object>} lines - The raw line inputs.
* @returns {Array<{sku: string, amount: number, net: number, rebate: number, tax: number, comment: string|null}>} The normalized lines.
* @throws {Error} If the lines are invalid.
*/
export function normalizeLines(lines) {
if (!Array.isArray(lines)) {
throw new Error('Invalid lines');
}
return lines.map((entry) => {
const sku = entry?.sku?.trim?.() ? entry.sku.trim() : null;
if (!sku) {
throw new Error('Invalid lines');
}
const amount = entry?.amount;
const net = entry?.net;
const rebate =
entry?.rebate === undefined || entry?.rebate === null ? 0 : entry.rebate;
const tax = entry?.tax;
const comment =
entry?.comment === undefined || entry?.comment === null
? null
: String(entry.comment);
if (
!Number.isFinite(amount) ||
!Number.isFinite(net) ||
!Number.isFinite(rebate) ||
!Number.isFinite(tax)
) {
throw new Error('Invalid lines');
}
return { sku, amount, net, rebate, tax, comment };
});
}
/**
* Parses a date value (Date instance or ISO string) into a Date.
*
* @param {Date|string} value - The date value.
* @returns {Date|null} The parsed date, or null if invalid.
*/
export function parseDate(value) {
if (value instanceof Date && !Number.isNaN(value.getTime())) {
return value;
}
if (typeof value === 'string' && value.trim()) {
const d = new Date(value);
if (!Number.isNaN(d.getTime())) {
return d;
}
}
return null;
}
/**
* Shapes a manifestarticle row into a line object.
*
* @param {object} row - The manifestarticle row.
* @returns {object} The shaped line.
*/
export function shapeLine(row) {
return {
id: row.id,
articleId: row.articleId ?? null,
sku: row.sku ?? null,
amount: row.amount,
net: row.net,
rebate: row.rebate,
tax: row.tax,
comment: row.comment ?? null,
};
}
async function resolveArticleId(pool, tx, rootCategoryId, sku) {
const result = await request(pool, tx)
.input('rootCategoryId', rootCategoryId)
.input('sku', sku)
.query(RESOLVE_ARTICLE_ID_BY_SKU);
const articleId = result.recordset[0]?.id;
if (!articleId) {
throw new Error('Article not found');
}
return articleId;
}
/**
* Inserts manifest lines for a document, resolving each SKU to an article id
* and ensuring the tax rate.
*
* @param {object} pool - The mssql connection pool.
* @param {object} tx - The transaction.
* @param {object} params - The insert parameters.
* @param {number} params.manifestId - The manifest id.
* @param {number} params.rootCategoryId - The company root category id.
* @param {Array<object>} params.lines - The normalized lines.
* @param {Function} params.ensureTax - The tax ensure function.
* @returns {Promise<void>}
*/
export async function insertLines(
pool,
tx,
{ manifestId, rootCategoryId, lines, ensureTax },
) {
for (const line of lines) {
const articleId = await resolveArticleId(
pool,
tx,
rootCategoryId,
line.sku,
);
const taxId = await ensureTax(line.tax, tx);
await request(pool, tx)
.input('manifestId', manifestId)
.input('articleId', articleId)
.input('amount', line.amount)
.input('net', line.net)
.input('comment', line.comment)
.input('rebate', line.rebate)
.input('taxId', taxId)
.query(CREATE_LINE);
}
}

View File

@@ -2,6 +2,18 @@ import { ENSURE_TYPE } from '../../sql/numbers.js';
import request from '../request.js';
export default function nextNumber(pool) {
/**
* Allocates and returns the next number for a company and type, consuming it.
*
* @param {number} companyId - The id of the company.
* @param {string} type - The number type (e.g. `'entity'`, `'sku'`).
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<string>} The allocated number.
*
* @example
* const number = await store.numbers.next(1, 'entity');
* // => '1'
*/
return async (companyId, type, tx) => {
await request(pool, tx)
.input('companyId', companyId)

View File

@@ -2,5 +2,16 @@ import nextNumber from './nextNumber.js';
export default function nextSku(pool) {
const allocate = nextNumber(pool);
/**
* Allocates and returns the next SKU for a company, consuming it.
*
* @param {number} companyId - The id of the company.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<string>} The allocated SKU.
*
* @example
* const sku = await store.numbers.nextSku(1);
* // => '1'
*/
return (companyId, tx) => allocate(companyId, 'sku', tx);
}

View File

@@ -2,6 +2,18 @@ import { ENSURE_TYPE } from '../../sql/numbers.js';
import request from '../request.js';
export default function peekNumber(pool) {
/**
* Returns the next number for a company and type without consuming it.
*
* @param {number} companyId - The id of the company.
* @param {string} type - The number type (e.g. `'entity'`, `'sku'`).
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<string>} The next number.
*
* @example
* const number = await store.numbers.peek(1, 'entity');
* // => '1'
*/
return async (companyId, type, tx) => {
await request(pool, tx)
.input('companyId', companyId)

82
src/store/order/create.js Normal file
View File

@@ -0,0 +1,82 @@
import { CREATE_MANIFEST, ORDER_TYPE } from '../../sql/manifestDoc.js';
import request from '../request.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import taxEnsure from '../tax/ensure.js';
import { insertLines, normalizeLines, parseDate } from '../manifestLines.js';
import { assertCustomerEntity, loadDoc } from '../manifestDoc/helpers.js';
export default function create(pool) {
const access = resolveSelectedCompanyAccess(pool);
const ensureTax = taxEnsure(pool);
/**
* Creates a new order for the user's selected company with its lines.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {object} body - The order input.
* @param {string} body.number - The order number.
* @param {string} body.date - The order date.
* @param {number} [body.entityId] - The id of the customer entity.
* @param {Array<object>} [body.lines] - The order lines.
* @returns {Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>} The created order.
* @throws {Error} If the order is invalid.
*
* @example
* const order = await store.order.create(42, { number: 'B-2026-001', date: '2026-09-15', entityId: 12, lines: [...] });
* // => { id, number, type, entityId, date, lines: [...] }
*/
return async (googleAuthId, body = {}) => {
const number = body.number?.trim?.() ? body.number.trim() : null;
const date = parseDate(body.date);
if (!number || !date) {
throw new Error('Invalid order');
}
const lines = normalizeLines(body.lines ?? []);
const { companyId, rootCategoryId } = await access(googleAuthId);
const tx = pool.transaction();
await tx.begin();
try {
const entityId = await assertCustomerEntity(
pool,
tx,
companyId,
body.entityId ?? null,
);
const created = await request(pool, tx)
.input('companyId', companyId)
.input('number', number)
.input('type', ORDER_TYPE)
.input('entityId', entityId)
.input('date', date)
.query(CREATE_MANIFEST);
const manifestId = created.recordset[0].id;
await insertLines(pool, tx, {
manifestId,
rootCategoryId,
lines,
ensureTax,
});
const doc = await loadDoc(pool, tx, {
type: ORDER_TYPE,
companyId,
id: manifestId,
});
await tx.commit();
return doc;
} catch (err) {
try {
await tx.rollback();
} catch {
// ignore
}
throw err;
}
};
}

36
src/store/order/delete.js Normal file
View File

@@ -0,0 +1,36 @@
import { DELETE_MANIFEST, ORDER_TYPE } from '../../sql/manifestDoc.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
export default function del(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Deletes an order by its id within the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the order to delete.
* @returns {Promise<void>}
* @throws {Error} If the order is not found.
*
* @example
* await store.order.delete(42, 5);
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Order not found');
}
const { companyId } = await access(googleAuthId);
const result = await pool
.request()
.input('id', id)
.input('companyId', companyId)
.input('type', ORDER_TYPE)
.query(DELETE_MANIFEST);
if (!result.rowsAffected[0]) {
throw new Error('Order not found');
}
};
}

View File

@@ -0,0 +1,33 @@
import { ORDER_TYPE } from '../../sql/manifestDoc.js';
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
import { loadDoc } from '../manifestDoc/helpers.js';
export default function getById(pool) {
const access = resolveSelectedCompanyAccess(pool);
/**
* Returns a single order by its id within the user's selected company.
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {number} id - The id of the order.
* @returns {Promise<{id: number, number: string, type: string, entityId: number|null, date: string, lines: Array<object>}>} The order with its lines.
* @throws {Error} If the order is not found.
*
* @example
* const order = await store.order.getById(42, 5);
* // => { id, number, type, entityId, date, lines: [...] }
*/
return async (googleAuthId, id) => {
if (!Number.isFinite(id)) {
throw new Error('Order not found');
}
const { companyId } = await access(googleAuthId);
const doc = await loadDoc(pool, null, { type: ORDER_TYPE, companyId, id });
if (!doc) {
throw new Error('Order not found');
}
return doc;
};
}

Some files were not shown because too many files have changed in this diff Show More