Add entity customer numbers and scope APIs via selected company.
Entity gets an optional unique-per-company number with allocate/peek/isNumberFree helpers. Company-scoped store methods no longer take companyId and resolve google_auth.selected_company instead.
This commit is contained in:
293
README.md
293
README.md
@@ -16,6 +16,8 @@ SQL lives in `src/sql/<table>.js`. Schema DDL: `src/schema.js` / `import 'libdas
|
||||
|
||||
ID fields in the JS API use camelCase `…Id` (DB columns stay snake_case). `googleAuthId` is always `google_auth.id` (the logged-in user), not `companyuser.id`.
|
||||
|
||||
Company-scoped methods take **no** `companyId` argument: they use `google_auth.selected_company` (set via `google_auth.setSelectedCompany`). Missing selection → `No company selected`. Exceptions that still take `companyId`: `setSelectedCompany`, `companyuser.add`, `company.delete`, and internal helpers.
|
||||
|
||||
---
|
||||
|
||||
## Schema
|
||||
@@ -124,9 +126,9 @@ Backend mapping:
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `{ si, basesi, basesifactor, symbol, name }[]` |
|
||||
| `upsert` | `(googleAuthId, companyId, { si, basesi, basesifactor, symbol, name })` | `{ si, basesi, basesifactor, symbol, name }` |
|
||||
| `delete` | `(googleAuthId, companyId, si)` | `void` |
|
||||
| `list` | `(googleAuthId)` | `{ si, basesi, basesifactor, symbol, name }[]` |
|
||||
| `upsert` | `(googleAuthId, { si, basesi, basesifactor, symbol, name })` | `{ si, basesi, basesifactor, symbol, name }` |
|
||||
| `delete` | `(googleAuthId, si)` | `void` |
|
||||
|
||||
Shape (überall):
|
||||
|
||||
@@ -138,82 +140,84 @@ Shape (überall):
|
||||
|
||||
### `list`
|
||||
|
||||
Auth via `resolveCompanyAccess` (like `getTreeForCompany`). Returns only units of that company. Errors: `Company not found`, `Not authorized`.
|
||||
Auth via selected company (like `getTreeForCompany`). Returns only units of that company. Errors: `No company selected`, `Company not found`, `Not authorized`.
|
||||
|
||||
### `upsert`
|
||||
|
||||
Match key `(companyId, si)`. Inserts `unit` + `unittext`, or updates `basesi` / `basesifactor` and upserts `unittext` for the company language. `basesifactor` must be finite and `> 0`. Errors: `Company not found`, `Not authorized`, `Invalid unit`.
|
||||
Match key `(companyId, si)`. Inserts `unit` + `unittext`, or updates `basesi` / `basesifactor` and upserts `unittext` for the company language. `basesifactor` must be finite and `> 0`. Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid unit`.
|
||||
|
||||
Default article unit `pc` is ensured **per company** on article create (with `unittext` symbol/name `pc` / `Piece` for the company language).
|
||||
|
||||
### `delete`
|
||||
|
||||
Deletes the unit for `(companyId, si)`. Blocked by FK if any article still references it (`Unit in use`). Missing → `Unit not found`. Errors: `Company not found`, `Not authorized`, `Unit not found`, `Unit in use`.
|
||||
Deletes the unit for `(companyId, si)`. Blocked by FK if any article still references it (`Unit in use`). Missing → `Unit not found`. Errors: `No company selected`, `Company not found`, `Not authorized`, `Unit not found`, `Unit in use`.
|
||||
|
||||
---
|
||||
|
||||
## `companysetting`
|
||||
|
||||
Company-scoped key/value settings. Auth via `resolveCompanyAccess`.
|
||||
Company-scoped key/value settings. Auth via selected company (`google_auth.selected_company` + membership).
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId, { frontendOnly? })` | `{ key, value, frontend }[]` |
|
||||
| `get` | `(googleAuthId, companyId, key, { frontendOnly? })` | `{ key, value, frontend } \| undefined` |
|
||||
| `set` | `(googleAuthId, companyId, { key, value, frontend? })` | `{ key, value, frontend }` |
|
||||
| `delete` | `(googleAuthId, companyId, key)` | `void` |
|
||||
| `list` | `(googleAuthId, { frontendOnly? })` | `{ key, value, frontend }[]` |
|
||||
| `get` | `(googleAuthId, key, { frontendOnly? })` | `{ key, value, frontend } \| undefined` |
|
||||
| `set` | `(googleAuthId, { key, value, frontend? })` | `{ key, value, frontend }` |
|
||||
| `delete` | `(googleAuthId, key)` | `void` |
|
||||
|
||||
- `value` is always a string (`NVARCHAR(MAX)`).
|
||||
- `frontend: true` → readable by frontend routes (`frontendOnly: true`). Default on `set` is `false` (backend-only).
|
||||
- With `frontendOnly: true`, backend-only keys are omitted / `get` returns `undefined` (no leak via error type beyond missing).
|
||||
|
||||
Errors: `Company not found`, `Not authorized`, `Invalid setting`, `Setting not found`.
|
||||
Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid setting`, `Setting not found`.
|
||||
|
||||
---
|
||||
|
||||
## `brand`
|
||||
|
||||
Company-scoped brands. Auth via `resolveCompanyAccess`.
|
||||
Company-scoped brands. Auth via selected company (`google_auth.selected_company` + membership).
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `{ id, name, gsprpinfo }[]` |
|
||||
| `upsert` | `(googleAuthId, companyId, { name, gsprpinfo? })` | `{ id, name, gsprpinfo }` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `list` | `(googleAuthId)` | `{ id, name, gsprpinfo }[]` |
|
||||
| `upsert` | `(googleAuthId, { name, gsprpinfo? })` | `{ id, name, gsprpinfo }` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
|
||||
`brand.company_id` **NOT NULL**, unique `(company_id, name)`. `upsert` matches by `(companyId, name)`. `delete` blocked by FK if any article references it (`Brand in use`). Errors: `Company not found`, `Not authorized`, `Invalid brand`, `Brand not found`, `Brand in use`.
|
||||
`brand.company_id` **NOT NULL**, unique `(company_id, name)`. `upsert` matches by `(companyId, name)`. `delete` blocked by FK if any article references it (`Brand in use`). Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid brand`, `Brand not found`, `Brand in use`.
|
||||
|
||||
---
|
||||
|
||||
## `customergroup`
|
||||
|
||||
Company-scoped customer groups. Auth via `resolveCompanyAccess`.
|
||||
Company-scoped customer groups. Auth via selected company (`google_auth.selected_company` + membership).
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `{ id, name, sort }[]` |
|
||||
| `create` | `(googleAuthId, companyId, { name })` | `{ id, name, sort }` |
|
||||
| `update` | `(googleAuthId, companyId, id, { name })` | `{ id, name, sort }` |
|
||||
| `order` | `(googleAuthId, companyId, { customergroupIds })` | `{ customergroupIds }` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `list` | `(googleAuthId)` | `{ id, name, sort }[]` |
|
||||
| `create` | `(googleAuthId, { name })` | `{ id, name, sort }` |
|
||||
| `update` | `(googleAuthId, id, { name })` | `{ id, name, sort }` |
|
||||
| `order` | `(googleAuthId, { customergroupIds })` | `{ customergroupIds }` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
|
||||
List ordered by `sort`, `name`, `id`. `create` appends with next `sort` (`0…n`). `order` sets `sort` to each id’s index `0…n-1`; `customergroupIds` must be exactly the company’s groups (same set, no dups) else `Invalid customer group order`. Implicit groups `POS` (company create) and `Lieferanten` (first supplier) cannot be renamed (`Name cannot be changed`). Creating a group with those reserved names → `Name not allowed`.
|
||||
|
||||
Empty name → `Name required`. Deleting the POS group (or any group still referenced with `NO ACTION`) → `Customer group in use`. Cascades entities when the group is deleted. Errors: `Company not found`, `Not authorized`, `Name required`, `Name not allowed`, `Name cannot be changed`, `Invalid customer group order`, `Customer group not found`, `Customer group in use`.
|
||||
Empty name → `Name required`. Deleting the POS group (or any group still referenced with `NO ACTION`) → `Customer group in use`. Cascades entities when the group is deleted. Errors: `No company selected`, `Company not found`, `Not authorized`, `Name required`, `Name not allowed`, `Name cannot be changed`, `Invalid customer group order`, `Customer group not found`, `Customer group in use`.
|
||||
|
||||
---
|
||||
|
||||
## `entity`
|
||||
|
||||
Entities belong to a `customergroup` of the company. Auth via `resolveCompanyAccess`.
|
||||
Entities belong to a `customergroup` of the selected company. Company scope comes from `google_auth.selected_company` (not a `companyId` argument).
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId, { customergroupId?, role? })` | `Entity[]` |
|
||||
| `getById` | `(googleAuthId, companyId, id)` | `Entity` |
|
||||
| `create` | `(googleAuthId, companyId, { customergroupId, name, firstName? })` | `Entity` |
|
||||
| `update` | `(googleAuthId, companyId, id, { name?, firstName? })` | `Entity` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `list` | `(googleAuthId, { customergroupId?, role? })` | `Entity[]` |
|
||||
| `getById` | `(googleAuthId, id)` | `Entity` |
|
||||
| `create` | `(googleAuthId, { customergroupId, name, firstName?, number? })` | `Entity` |
|
||||
| `update` | `(googleAuthId, id, { name?, firstName?, number? })` | `Entity` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
| `peekNextNumber` | `(googleAuthId)` | `string` |
|
||||
| `isNumberFree` | `(googleAuthId, number, { excludeEntityId? })` | `boolean` |
|
||||
|
||||
```ts
|
||||
type Entity = {
|
||||
@@ -221,6 +225,7 @@ type Entity = {
|
||||
customergroupId: number,
|
||||
firstName: string | null,
|
||||
name: string,
|
||||
number: string | null,
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
}
|
||||
@@ -228,66 +233,68 @@ type Entity = {
|
||||
|
||||
`list` optional filters: `customergroupId` (must belong to the company), or `role: 'customer'` (entities whose customergroup name is not `Lieferanten` and not `POS` — for quote/order pickers). `role` wins over `customergroupId` when both are set. Unknown `role` → `Invalid role`.
|
||||
|
||||
`create` / `update` write `entityhistory` (`Erstellt`, or name/firstName change lines). `customergroupId` must belong to the company. Errors: `Company not found`, `Not authorized`, `Name required`, `Customer group not found`, `Entity not found`, `Invalid role`.
|
||||
`number` is optional on the row (e.g. suppliers leave it null). Non-null numbers are unique per company (via `customergroup`). `create` allocates via `getnextnumberwithprefixandpadding` (`type` `entity`) when `number` is omitted. `update` only changes `number` when the key is present (no auto-allocate). `peekNextNumber` returns what the next allocate would yield without consuming it (`peeknextnumberwithprefixandpadding`; ensures the `numbers` row exists). `isNumberFree` checks occupancy company-wide; use `excludeEntityId` on edit so the current entity’s number stays free. Empty `number` → `Number required`.
|
||||
|
||||
`create` / `update` write `entityhistory` (`Erstellt`, or name/firstName/number change lines). `customergroupId` must belong to the company. Errors: `No company selected`, `Company not found`, `Not authorized`, `Name required`, `Customer group not found`, `Entity not found`, `Invalid role`, `Number already in use`, `Number required`.
|
||||
|
||||
---
|
||||
|
||||
## `entityaddress`
|
||||
|
||||
Addresses for an entity. Auth via company + entity ownership.
|
||||
Addresses for an entity. Auth via selected company + entity ownership.
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId, entityId)` | `EntityAddress[]` |
|
||||
| `create` | `(googleAuthId, companyId, entityId, { addressType, firstName?, name?, street?, streetNumber?, zip?, city?, county?, sort? })` | `EntityAddress` |
|
||||
| `update` | `(googleAuthId, companyId, entityId, id, patch)` | `EntityAddress` |
|
||||
| `delete` | `(googleAuthId, companyId, entityId, id)` | `void` |
|
||||
| `list` | `(googleAuthId, entityId)` | `EntityAddress[]` |
|
||||
| `create` | `(googleAuthId, entityId, { addressType, firstName?, name?, street?, streetNumber?, zip?, city?, county?, sort? })` | `EntityAddress` |
|
||||
| `update` | `(googleAuthId, entityId, id, patch)` | `EntityAddress` |
|
||||
| `delete` | `(googleAuthId, entityId, id)` | `void` |
|
||||
|
||||
`addressType` required. `sort` defaults to next index for the entity (`0…n`). List ordered by `sort`, `id`. Errors: `Company not found`, `Not authorized`, `Entity not found`, `Address type required`, `Address not found`, `Invalid sort`.
|
||||
`addressType` required. `sort` defaults to next index for the entity (`0…n`). List ordered by `sort`, `id`. Errors: `No company selected`, `Company not found`, `Not authorized`, `Entity not found`, `Address type required`, `Address not found`, `Invalid sort`.
|
||||
|
||||
---
|
||||
|
||||
## `entitycontact`
|
||||
|
||||
Contacts for an entity (`contactType` e.g. `email`, `phone`). Auth via company + entity ownership.
|
||||
Contacts for an entity (`contactType` e.g. `email`, `phone`). Auth via selected company + entity ownership.
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId, entityId)` | `{ id, entityId, contactType, contact, sort }[]` |
|
||||
| `create` | `(googleAuthId, companyId, entityId, { contactType, contact, sort? })` | row |
|
||||
| `update` | `(googleAuthId, companyId, entityId, id, { contactType?, contact?, sort? })` | row |
|
||||
| `delete` | `(googleAuthId, companyId, entityId, id)` | `void` |
|
||||
| `list` | `(googleAuthId, entityId)` | `{ id, entityId, contactType, contact, sort }[]` |
|
||||
| `create` | `(googleAuthId, entityId, { contactType, contact, sort? })` | row |
|
||||
| `update` | `(googleAuthId, entityId, id, { contactType?, contact?, sort? })` | row |
|
||||
| `delete` | `(googleAuthId, entityId, id)` | `void` |
|
||||
|
||||
`sort` defaults to next index for the entity. List ordered by `sort`, `id`. Errors: `Company not found`, `Not authorized`, `Entity not found`, `Contact required`, `Contact not found`, `Invalid sort`.
|
||||
`sort` defaults to next index for the entity. List ordered by `sort`, `id`. Errors: `No company selected`, `Company not found`, `Not authorized`, `Entity not found`, `Contact required`, `Contact not found`, `Invalid sort`.
|
||||
|
||||
---
|
||||
|
||||
## `supplier`
|
||||
|
||||
Suppliers are `entity` rows in the company `customergroup` named `Lieferanten` (created implicitly on first supplier). Auth via `resolveCompanyAccess`.
|
||||
Suppliers are `entity` rows in the company `customergroup` named `Lieferanten` (created implicitly on first supplier). Auth via selected company (`google_auth.selected_company` + membership).
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `{ id, name, createdAt, updatedAt }[]` |
|
||||
| `create` | `(googleAuthId, companyId, { name })` | `{ id, name, createdAt, updatedAt }` |
|
||||
| `update` | `(googleAuthId, companyId, id, { name })` | `{ id, name, createdAt, updatedAt }` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `list` | `(googleAuthId)` | `{ id, name, createdAt, updatedAt }[]` |
|
||||
| `create` | `(googleAuthId, { name })` | `{ id, name, createdAt, updatedAt }` |
|
||||
| `update` | `(googleAuthId, id, { name })` | `{ id, name, createdAt, updatedAt }` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
|
||||
`create` ensures `customergroup` `Lieferanten` for the company, inserts `entity` (`name` only; `first_name` null), writes `entityhistory` (`Erstellt`) with the auth `companyuser_id`. `update` renames and writes `Name geändert von "…" auf "…"` when the name actually changes; bumps `updated_at`. Empty name → `Name required`. `delete` removes the entity (`entityhistory` cascades; `manifest.entity_id` set null). Errors: `Company not found`, `Not authorized`, `Name required`, `Supplier not found`.
|
||||
`create` ensures `customergroup` `Lieferanten` for the company, inserts `entity` (`name` only; `first_name` null), writes `entityhistory` (`Erstellt`) with the auth `companyuser_id`. `update` renames and writes `Name geändert von "…" auf "…"` when the name actually changes; bumps `updated_at`. Empty name → `Name required`. `delete` removes the entity (`entityhistory` cascades; `manifest.entity_id` set null). Errors: `No company selected`, `Company not found`, `Not authorized`, `Name required`, `Supplier not found`.
|
||||
|
||||
---
|
||||
|
||||
## `delivery`
|
||||
|
||||
Company-scoped goods receipts on `manifest` / `manifestarticle` with fixed `type = 'delivery'`. Auth via `resolveCompanyAccess`. `manifest.company_id` scopes the list (`ON DELETE NO ACTION` — avoid SQL Server multiple cascade paths with `entity_id`).
|
||||
Company-scoped goods receipts on `manifest` / `manifestarticle` with fixed `type = 'delivery'`. Auth via selected company (`google_auth.selected_company` + membership). `manifest.company_id` scopes the list (`ON DELETE NO ACTION` — avoid SQL Server multiple cascade paths with `entity_id`).
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `DeliverySummary[]` |
|
||||
| `getById` | `(googleAuthId, companyId, id)` | `Delivery` |
|
||||
| `create` | `(googleAuthId, companyId, { number, date, supplierId?, lines })` | `Delivery` |
|
||||
| `update` | `(googleAuthId, companyId, id, { number?, date?, supplierId?, lines? })` | `Delivery` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `list` | `(googleAuthId)` | `DeliverySummary[]` |
|
||||
| `getById` | `(googleAuthId, id)` | `Delivery` |
|
||||
| `create` | `(googleAuthId, { number, date, supplierId?, lines })` | `Delivery` |
|
||||
| `update` | `(googleAuthId, id, { number?, date?, supplierId?, lines? })` | `Delivery` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
|
||||
```ts
|
||||
type DeliverySummary = {
|
||||
@@ -316,21 +323,21 @@ type Delivery = DeliverySummary & { lines: DeliveryLine[] }
|
||||
|
||||
`update` patches header fields that are present. If `lines` is present, replace-all lines; if omitted, leave lines unchanged. Returns full `Delivery`.
|
||||
|
||||
`delete` removes the manifest (`manifestarticle` cascades). Errors: `Company not found`, `Not authorized`, `Delivery not found`, `Supplier not found`, `Article not found`, `Invalid delivery`, `Invalid lines`.
|
||||
`delete` removes the manifest (`manifestarticle` cascades). Errors: `No company selected`, `Company not found`, `Not authorized`, `Delivery not found`, `Supplier not found`, `Article not found`, `Invalid delivery`, `Invalid lines`.
|
||||
|
||||
---
|
||||
|
||||
## `quote`
|
||||
|
||||
Company-scoped quotes on `manifest` / `manifestarticle` with fixed `type = 'quote'`. Auth via `resolveCompanyAccess`. Same line shape as `delivery` (no `transactedAmount` — ledger owns movements). `manifest.date` is the document date; HTTP/OpenAPI may describe it as valid-until.
|
||||
Company-scoped quotes on `manifest` / `manifestarticle` with fixed `type = 'quote'`. Auth via selected company (`google_auth.selected_company` + membership). Same line shape as `delivery` (no `transactedAmount` — ledger owns movements). `manifest.date` is the document date; HTTP/OpenAPI may describe it as valid-until.
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `QuoteSummary[]` |
|
||||
| `getById` | `(googleAuthId, companyId, id)` | `Quote` |
|
||||
| `create` | `(googleAuthId, companyId, { number, date, entityId?, lines })` | `Quote` |
|
||||
| `update` | `(googleAuthId, companyId, id, { number?, date?, entityId?, lines? })` | `Quote` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `list` | `(googleAuthId)` | `QuoteSummary[]` |
|
||||
| `getById` | `(googleAuthId, id)` | `Quote` |
|
||||
| `create` | `(googleAuthId, { number, date, entityId?, lines })` | `Quote` |
|
||||
| `update` | `(googleAuthId, id, { number?, date?, entityId?, lines? })` | `Quote` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
|
||||
```ts
|
||||
type QuoteSummary = {
|
||||
@@ -357,7 +364,7 @@ type Quote = QuoteSummary & { lines: ManifestLine[] }
|
||||
|
||||
`entityId` null or a company entity whose customergroup is not named `Lieferanten` or `POS` (else `Entity not found`). Lines: `sku` (category tree → `Article not found`), `amount`, `net`, `tax` (`tax.ensure`); `rebate` defaults `0`. Empty/invalid `number` or `date` → `Invalid quote`; bad lines → `Invalid lines`.
|
||||
|
||||
`update`: present fields only; `lines` present → replace-all. `delete` cascades lines. Errors: `Company not found`, `Not authorized`, `Quote not found`, `Entity not found`, `Article not found`, `Invalid quote`, `Invalid lines`.
|
||||
`update`: present fields only; `lines` present → replace-all. `delete` cascades lines. Errors: `No company selected`, `Company not found`, `Not authorized`, `Quote not found`, `Entity not found`, `Article not found`, `Invalid quote`, `Invalid lines`.
|
||||
|
||||
---
|
||||
|
||||
@@ -367,11 +374,11 @@ Company-scoped orders on `manifest` / `manifestarticle` with fixed `type = 'orde
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `OrderSummary[]` |
|
||||
| `getById` | `(googleAuthId, companyId, id)` | `Order` |
|
||||
| `create` | `(googleAuthId, companyId, { number, date, entityId?, lines })` | `Order` |
|
||||
| `update` | `(googleAuthId, companyId, id, { number?, date?, entityId?, lines? })` | `Order` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `list` | `(googleAuthId)` | `OrderSummary[]` |
|
||||
| `getById` | `(googleAuthId, id)` | `Order` |
|
||||
| `create` | `(googleAuthId, { number, date, entityId?, lines })` | `Order` |
|
||||
| `update` | `(googleAuthId, id, { number?, date?, entityId?, lines? })` | `Order` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
|
||||
```ts
|
||||
type OrderSummary = {
|
||||
@@ -385,7 +392,7 @@ type OrderSummary = {
|
||||
type Order = OrderSummary & { lines: ManifestLine[] } // ManifestLine as in quote
|
||||
```
|
||||
|
||||
Errors: `Company not found`, `Not authorized`, `Order not found`, `Entity not found`, `Article not found`, `Invalid order`, `Invalid lines`.
|
||||
Errors: `No company selected`, `Company not found`, `Not authorized`, `Order not found`, `Entity not found`, `Article not found`, `Invalid order`, `Invalid lines`.
|
||||
|
||||
---
|
||||
|
||||
@@ -409,28 +416,28 @@ Engine also maintains `ledger_start_transaction_id` / `ledger_start_sequence_num
|
||||
|
||||
## `posterminal`
|
||||
|
||||
Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
||||
Company-scoped POS terminals. Auth via selected company (`google_auth.selected_company` + membership).
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `{ id, name, token, pairingpin }[]` |
|
||||
| `upsert` | `(googleAuthId, companyId, { id?, name, token?, pairingpin })` | `{ id, name, token, pairingpin }` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `setOn` | `(googleAuthId, companyId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
|
||||
| `setOff` | `(googleAuthId, companyId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
|
||||
| `setOnAll` | `(googleAuthId, companyId, posterminalId)` | `{ id, categoryIds[] }` |
|
||||
| `setOffAll` | `(googleAuthId, companyId, posterminalId)` | `{ id, categoryIds[] }` |
|
||||
| `list` | `(googleAuthId)` | `{ id, name, token, pairingpin }[]` |
|
||||
| `upsert` | `(googleAuthId, { id?, name, token?, pairingpin })` | `{ id, name, token, pairingpin }` |
|
||||
| `delete` | `(googleAuthId, id)` | `void` |
|
||||
| `setOn` | `(googleAuthId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
|
||||
| `setOff` | `(googleAuthId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
|
||||
| `setOnAll` | `(googleAuthId, posterminalId)` | `{ id, categoryIds[] }` |
|
||||
| `setOffAll` | `(googleAuthId, posterminalId)` | `{ id, categoryIds[] }` |
|
||||
| `getCategoryPosterminalIds` | `(companyId)` | `{ [categoryId]: posterminalId[] }` (internal, used by category tree) |
|
||||
|
||||
`posterminal.company_id` **NOT NULL**. `name` is required. `token` is nullable and unique (pairing code assigned during setup). `pairingpin` is required.
|
||||
|
||||
`upsert`: if `id` is provided, updates the existing posterminal (must belong to company); otherwise creates a new one. Errors: `Company not found`, `Not authorized`, `Invalid posterminal`, `Posterminal not found`.
|
||||
`upsert`: if `id` is provided, updates the existing posterminal (must belong to company); otherwise creates a new one. Errors: `No company selected`, `Company not found`, `Not authorized`, `Invalid posterminal`, `Posterminal not found`.
|
||||
|
||||
`setOn` / `setOff`: toggle a single category's visibility on a single terminal. `setOn` inserts the row (idempotent — no error if already present); `setOff` deletes it (idempotent). Both return the updated `categoryIds` for that terminal. Errors: `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal or category`.
|
||||
`setOn` / `setOff`: toggle a single category's visibility on a single terminal. `setOn` inserts the row (idempotent — no error if already present); `setOff` deletes it (idempotent). Both return the updated `categoryIds` for that terminal. Errors: `No company selected`, `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal or category`.
|
||||
|
||||
`setOnAll` / `setOffAll`: toggle **all** company categories on a single terminal. `setOnAll` walks the company's category tree and inserts any missing links; `setOffAll` removes all category links for the terminal. Both return the updated `categoryIds` for that terminal. Errors: `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal`.
|
||||
`setOnAll` / `setOffAll`: toggle **all** company categories on a single terminal. `setOnAll` walks the company's category tree and inserts any missing links; `setOffAll` removes all category links for the terminal. Both return the updated `categoryIds` for that terminal. Errors: `No company selected`, `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal`.
|
||||
|
||||
`delete` cascades to `posterminalcategory`. Errors: `Company not found`, `Not authorized`, `Posterminal not found`.
|
||||
`delete` cascades to `posterminalcategory`. Errors: `No company selected`, `Company not found`, `Not authorized`, `Posterminal not found`.
|
||||
|
||||
### Category tree integration
|
||||
|
||||
@@ -447,7 +454,7 @@ Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
||||
| `move` | `({ googleAuthId, id, parentId })` | `{ id, parentId, sort }` |
|
||||
| `order` | `({ googleAuthId, parentId, categoryIds })` | `{ categoryIds }` |
|
||||
| `delete` | `({ googleAuthId, id })` | `void` — `Category has children` if child categories remain; FK `categoryarticle` also blocks deletion |
|
||||
| `getTreeForCompany` | `(googleAuthId, companyId)` | `{ languageId, tree, articles }` — tree rows carry `name` + `slug` |
|
||||
| `getTreeForCompany` | `(googleAuthId)` | `{ languageId, tree, articles }` — tree rows carry `name` + `slug` |
|
||||
| `getCategoryArticles` | `(googleAuthId, categoryId)` | `{ id, sku, name, slug, net, taxRate, optionsCount, articleListCount, childCount, fatherArticleId, fatherSku, fullHash, thumbW, thumbH, thumbHash }[]` |
|
||||
| `addImage` | `(googleAuthId, categoryId, imageBuffer)` | `{ id, thumbW, thumbH, thumbHash }` |
|
||||
| `deleteImage` | `(googleAuthId, categoryId)` | `{ id, thumbW: null, thumbH: null, thumbHash: null }` |
|
||||
@@ -456,7 +463,7 @@ Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
||||
|
||||
1. `parentId` exists, else `Parent not found`
|
||||
2. Walk ancestors to root; find `company` with `root_category_id` = root
|
||||
3. `companyuser` for `(googleAuthId, companyId)`, else `Not authorized`
|
||||
3. `companyuser` for selected company via `googleAuthId`, else `Not authorized`
|
||||
4. Insert `category` (next sibling `sort`) + `categorytext` (`name`, slugified `slug`, company default language); returns `{ id, parentId, sort, name, slug }`
|
||||
|
||||
`getTreeForCompany` throws `Not authorized` if `googleAuthId` is not a `companyuser` of `companyId`, and `Company not found` if the company is missing, has no `root_category_id`, or no `default_language_id`.
|
||||
@@ -519,7 +526,7 @@ Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
||||
}
|
||||
```
|
||||
|
||||
`listSearchIndex` returns every article in the company (reachable via the category tree). Designed for client-side search / autocomplete preload. Auth via `resolveCompanyAccess`. Errors: `Company not found`, `Not authorized`.
|
||||
`listSearchIndex` returns every article in the company (reachable via the category tree). Designed for client-side search / autocomplete preload. Auth via selected company (`google_auth.selected_company` + membership). Errors: `No company selected`, `Company not found`, `Not authorized`.
|
||||
|
||||
```ts
|
||||
type SearchIndexEntry = {
|
||||
@@ -531,7 +538,7 @@ type SearchIndexEntry = {
|
||||
}
|
||||
```
|
||||
|
||||
`listExport` returns articles in a category subtree (root = company when `categoryId` omitted). `categoryId` must be reachable via the selected company's root category (else `Not authorized`). Auth via `resolveCompanyAccess` + `resolveCategoryAccess`. Errors: `Company not found`, `Parent not found`, `Not authorized`.
|
||||
`listExport` returns articles in a category subtree (root = company when `categoryId` omitted). `categoryId` must be reachable via the selected company's root category (else `Not authorized`). Auth via selected company + `resolveCategoryAccess`. Errors: `No company selected`, `Company not found`, `Parent not found`, `Not authorized`.
|
||||
|
||||
```ts
|
||||
type ArticleExportEntry = {
|
||||
@@ -662,8 +669,8 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `PATCH /api/company/:id` | `company.update(id, { iso639, name, taxrate, reducedtaxrate })` |
|
||||
| `DELETE /api/company/:id` | `company.delete(id)` |
|
||||
| `PUT /api/auth/selected-company` | `google_auth.setSelectedCompany(googleAuthId, companyId)` |
|
||||
| `GET /api/category/tree` | `category.getTreeForCompany(googleAuthId, companyId)` |
|
||||
| `GET /api/category/export.zip` | `category.getTreeForCompany(googleAuthId, companyId)` + `pic.getImageByHash` |
|
||||
| `GET /api/category/tree` | `category.getTreeForCompany(googleAuthId)` |
|
||||
| `GET /api/category/export.zip` | `category.getTreeForCompany(googleAuthId)` + `pic.getImageByHash` |
|
||||
| `POST /api/category` | `category.createUnderParent({ googleAuthId, parentId, name })` |
|
||||
| `PATCH /api/category/:id` | `category.rename({ googleAuthId, id, name })` |
|
||||
| `PUT /api/category/order` | `category.order({ googleAuthId, parentId, categoryIds })` |
|
||||
@@ -671,64 +678,66 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `DELETE /api/category/:id` | `category.delete({ googleAuthId, id })` |
|
||||
| `POST /api/category/:id/image` | `category.addImage(googleAuthId, categoryId, imageBuffer)` |
|
||||
| `DELETE /api/category/:id/image` | `category.deleteImage(googleAuthId, categoryId)` |
|
||||
| `GET /api/unit` | `unit.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/unit` | `unit.upsert(googleAuthId, companyId, { si, basesi, basesifactor, symbol, name })` |
|
||||
| `DELETE /api/unit` | `unit.delete(googleAuthId, companyId, si)` |
|
||||
| `GET /api/company/:id/settings` | `companysetting.list(googleAuthId, companyId, { frontendOnly: true })` |
|
||||
| `PUT /api/company/:id/settings` | `companysetting.set(googleAuthId, companyId, { key, value, frontend? })` |
|
||||
| `DELETE /api/company/:id/settings/:key` | `companysetting.delete(googleAuthId, companyId, key)` |
|
||||
| `GET /api/brand` | `brand.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/brand` | `brand.upsert(googleAuthId, companyId, { name, gsprpinfo? })` |
|
||||
| `DELETE /api/brand/:id` | `brand.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/customergroup` | `customergroup.list(googleAuthId, companyId)` |
|
||||
| `POST /api/customergroup` | `customergroup.create(googleAuthId, companyId, { name })` |
|
||||
| `PATCH /api/customergroup/:id` | `customergroup.update(googleAuthId, companyId, id, { name })` |
|
||||
| `PUT /api/customergroup/order` | `customergroup.order(googleAuthId, companyId, { customergroupIds })` |
|
||||
| `DELETE /api/customergroup/:id` | `customergroup.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/entity` | `entity.list(googleAuthId, companyId, { customergroupId?, role? })` |
|
||||
| `GET /api/entity/:id` | `entity.getById(googleAuthId, companyId, id)` |
|
||||
| `POST /api/entity` | `entity.create(googleAuthId, companyId, { customergroupId, name, firstName? })` |
|
||||
| `PATCH /api/entity/:id` | `entity.update(googleAuthId, companyId, id, { name?, firstName? })` |
|
||||
| `DELETE /api/entity/:id` | `entity.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/entity/:entityId/address` | `entityaddress.list(googleAuthId, companyId, entityId)` |
|
||||
| `POST /api/entity/:entityId/address` | `entityaddress.create(googleAuthId, companyId, entityId, { addressType, … })` |
|
||||
| `PATCH /api/entity/:entityId/address/:id` | `entityaddress.update(googleAuthId, companyId, entityId, id, patch)` |
|
||||
| `DELETE /api/entity/:entityId/address/:id` | `entityaddress.delete(googleAuthId, companyId, entityId, id)` |
|
||||
| `GET /api/entity/:entityId/contact` | `entitycontact.list(googleAuthId, companyId, entityId)` |
|
||||
| `POST /api/entity/:entityId/contact` | `entitycontact.create(googleAuthId, companyId, entityId, { contactType, contact })` |
|
||||
| `PATCH /api/entity/:entityId/contact/:id` | `entitycontact.update(googleAuthId, companyId, entityId, id, patch)` |
|
||||
| `DELETE /api/entity/:entityId/contact/:id` | `entitycontact.delete(googleAuthId, companyId, entityId, id)` |
|
||||
| `GET /api/supplier` | `supplier.list(googleAuthId, companyId)` |
|
||||
| `POST /api/supplier` | `supplier.create(googleAuthId, companyId, { name })` |
|
||||
| `PATCH /api/supplier/:id` | `supplier.update(googleAuthId, companyId, id, { name })` |
|
||||
| `DELETE /api/supplier/:id` | `supplier.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/delivery` | `delivery.list(googleAuthId, companyId)` |
|
||||
| `GET /api/delivery/:id` | `delivery.getById(googleAuthId, companyId, id)` |
|
||||
| `POST /api/delivery` | `delivery.create(googleAuthId, companyId, { number, date, supplierId?, lines })` |
|
||||
| `PATCH /api/delivery/:id` | `delivery.update(googleAuthId, companyId, id, { number?, date?, supplierId?, lines? })` |
|
||||
| `DELETE /api/delivery/:id` | `delivery.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/quote` | `quote.list(googleAuthId, companyId)` |
|
||||
| `GET /api/quote/:id` | `quote.getById(googleAuthId, companyId, id)` |
|
||||
| `POST /api/quote` | `quote.create(googleAuthId, companyId, { number, date, entityId?, lines })` |
|
||||
| `PATCH /api/quote/:id` | `quote.update(googleAuthId, companyId, id, { number?, date?, entityId?, lines? })` |
|
||||
| `DELETE /api/quote/:id` | `quote.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/order` | `order.list(googleAuthId, companyId)` |
|
||||
| `GET /api/order/:id` | `order.getById(googleAuthId, companyId, id)` |
|
||||
| `POST /api/order` | `order.create(googleAuthId, companyId, { number, date, entityId?, lines })` |
|
||||
| `PATCH /api/order/:id` | `order.update(googleAuthId, companyId, id, { number?, date?, entityId?, lines? })` |
|
||||
| `DELETE /api/order/:id` | `order.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/posterminal` | `posterminal.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/posterminal` | `posterminal.upsert(googleAuthId, companyId, { id?, name, token?, pairingpin })` |
|
||||
| `DELETE /api/posterminal/:id` | `posterminal.delete(googleAuthId, companyId, id)` |
|
||||
| `PUT /api/posterminal/:id/category/:categoryId` | `posterminal.setOn(googleAuthId, companyId, id, categoryId)` |
|
||||
| `DELETE /api/posterminal/:id/category/:categoryId` | `posterminal.setOff(googleAuthId, companyId, id, categoryId)` |
|
||||
| `PUT /api/posterminal/:id/categories` | `posterminal.setOnAll(googleAuthId, companyId, id)` |
|
||||
| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, companyId, id)` |
|
||||
| `GET /api/unit` | `unit.list(googleAuthId)` |
|
||||
| `PUT /api/unit` | `unit.upsert(googleAuthId, { si, basesi, basesifactor, symbol, name })` |
|
||||
| `DELETE /api/unit` | `unit.delete(googleAuthId, si)` |
|
||||
| `GET /api/company/:id/settings` | `companysetting.list(googleAuthId, { frontendOnly: true })` |
|
||||
| `PUT /api/company/:id/settings` | `companysetting.set(googleAuthId, { key, value, frontend? })` |
|
||||
| `DELETE /api/company/:id/settings/:key` | `companysetting.delete(googleAuthId, key)` |
|
||||
| `GET /api/brand` | `brand.list(googleAuthId)` |
|
||||
| `PUT /api/brand` | `brand.upsert(googleAuthId, { name, gsprpinfo? })` |
|
||||
| `DELETE /api/brand/:id` | `brand.delete(googleAuthId, id)` |
|
||||
| `GET /api/customergroup` | `customergroup.list(googleAuthId)` |
|
||||
| `POST /api/customergroup` | `customergroup.create(googleAuthId, { name })` |
|
||||
| `PATCH /api/customergroup/:id` | `customergroup.update(googleAuthId, id, { name })` |
|
||||
| `PUT /api/customergroup/order` | `customergroup.order(googleAuthId, { customergroupIds })` |
|
||||
| `DELETE /api/customergroup/:id` | `customergroup.delete(googleAuthId, id)` |
|
||||
| `GET /api/entity` | `entity.list(googleAuthId, { customergroupId?, role? })` |
|
||||
| `GET /api/entity/next-number` | `entity.peekNextNumber(googleAuthId)` |
|
||||
| `GET /api/entity/number-free` | `entity.isNumberFree(googleAuthId, number, { excludeEntityId? })` |
|
||||
| `GET /api/entity/:id` | `entity.getById(googleAuthId, id)` |
|
||||
| `POST /api/entity` | `entity.create(googleAuthId, { customergroupId, name, firstName?, number? })` |
|
||||
| `PATCH /api/entity/:id` | `entity.update(googleAuthId, id, { name?, firstName?, number? })` |
|
||||
| `DELETE /api/entity/:id` | `entity.delete(googleAuthId, id)` |
|
||||
| `GET /api/entity/:entityId/address` | `entityaddress.list(googleAuthId, entityId)` |
|
||||
| `POST /api/entity/:entityId/address` | `entityaddress.create(googleAuthId, entityId, { addressType, … })` |
|
||||
| `PATCH /api/entity/:entityId/address/:id` | `entityaddress.update(googleAuthId, entityId, id, patch)` |
|
||||
| `DELETE /api/entity/:entityId/address/:id` | `entityaddress.delete(googleAuthId, entityId, id)` |
|
||||
| `GET /api/entity/:entityId/contact` | `entitycontact.list(googleAuthId, entityId)` |
|
||||
| `POST /api/entity/:entityId/contact` | `entitycontact.create(googleAuthId, entityId, { contactType, contact })` |
|
||||
| `PATCH /api/entity/:entityId/contact/:id` | `entitycontact.update(googleAuthId, entityId, id, patch)` |
|
||||
| `DELETE /api/entity/:entityId/contact/:id` | `entitycontact.delete(googleAuthId, entityId, id)` |
|
||||
| `GET /api/supplier` | `supplier.list(googleAuthId)` |
|
||||
| `POST /api/supplier` | `supplier.create(googleAuthId, { name })` |
|
||||
| `PATCH /api/supplier/:id` | `supplier.update(googleAuthId, id, { name })` |
|
||||
| `DELETE /api/supplier/:id` | `supplier.delete(googleAuthId, id)` |
|
||||
| `GET /api/delivery` | `delivery.list(googleAuthId)` |
|
||||
| `GET /api/delivery/:id` | `delivery.getById(googleAuthId, id)` |
|
||||
| `POST /api/delivery` | `delivery.create(googleAuthId, { number, date, supplierId?, lines })` |
|
||||
| `PATCH /api/delivery/:id` | `delivery.update(googleAuthId, id, { number?, date?, supplierId?, lines? })` |
|
||||
| `DELETE /api/delivery/:id` | `delivery.delete(googleAuthId, id)` |
|
||||
| `GET /api/quote` | `quote.list(googleAuthId)` |
|
||||
| `GET /api/quote/:id` | `quote.getById(googleAuthId, id)` |
|
||||
| `POST /api/quote` | `quote.create(googleAuthId, { number, date, entityId?, lines })` |
|
||||
| `PATCH /api/quote/:id` | `quote.update(googleAuthId, id, { number?, date?, entityId?, lines? })` |
|
||||
| `DELETE /api/quote/:id` | `quote.delete(googleAuthId, id)` |
|
||||
| `GET /api/order` | `order.list(googleAuthId)` |
|
||||
| `GET /api/order/:id` | `order.getById(googleAuthId, id)` |
|
||||
| `POST /api/order` | `order.create(googleAuthId, { number, date, entityId?, lines })` |
|
||||
| `PATCH /api/order/:id` | `order.update(googleAuthId, id, { number?, date?, entityId?, lines? })` |
|
||||
| `DELETE /api/order/:id` | `order.delete(googleAuthId, id)` |
|
||||
| `GET /api/posterminal` | `posterminal.list(googleAuthId)` |
|
||||
| `PUT /api/posterminal` | `posterminal.upsert(googleAuthId, { id?, name, token?, pairingpin })` |
|
||||
| `DELETE /api/posterminal/:id` | `posterminal.delete(googleAuthId, id)` |
|
||||
| `PUT /api/posterminal/:id/category/:categoryId` | `posterminal.setOn(googleAuthId, id, categoryId)` |
|
||||
| `DELETE /api/posterminal/:id/category/:categoryId` | `posterminal.setOff(googleAuthId, id, categoryId)` |
|
||||
| `PUT /api/posterminal/:id/categories` | `posterminal.setOnAll(googleAuthId, id)` |
|
||||
| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, id)` |
|
||||
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
||||
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
||||
| `GET /api/article/:sku/history` | `article.getHistoryBySku(googleAuthId, sku)` |
|
||||
| `GET /api/articleIndex` | `article.listSearchIndex(googleAuthId, companyId)` |
|
||||
| `GET /api/article/export.zip` | `article.listExport(googleAuthId, companyId, categoryId?)` |
|
||||
| `GET /api/articleIndex` | `article.listSearchIndex(googleAuthId)` |
|
||||
| `GET /api/article/export.zip` | `article.listExport(googleAuthId, categoryId?)` |
|
||||
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
|
||||
| `PUT /api/article/:sku/prices` | `article.setPricesBySku(googleAuthId, sku, [{ price, tax, minmultiplier? }])` |
|
||||
| `PUT /api/article/:sku/eans` | `article.setEansBySku(googleAuthId, sku, string[])` |
|
||||
|
||||
Reference in New Issue
Block a user