Add quote and order stores plus ledger hash_version.
Mirror delivery on manifest for quote/order with customer entityId (excluding Lieferanten/POS), and support entity.list role=customer for pickers.
This commit is contained in:
88
README.md
88
README.md
@@ -208,7 +208,7 @@ Entities belong to a `customergroup` of the company. Auth via `resolveCompanyAcc
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId, { customergroupId? })` | `Entity[]` |
|
||||
| `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` |
|
||||
@@ -225,7 +225,9 @@ type Entity = {
|
||||
}
|
||||
```
|
||||
|
||||
`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`.
|
||||
`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`.
|
||||
|
||||
---
|
||||
|
||||
@@ -317,6 +319,75 @@ type Delivery = DeliverySummary & { lines: DeliveryLine[] }
|
||||
|
||||
---
|
||||
|
||||
## `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.
|
||||
|
||||
| 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` |
|
||||
|
||||
```ts
|
||||
type QuoteSummary = {
|
||||
id: number,
|
||||
number: string,
|
||||
type: 'quote',
|
||||
entityId: number | null, // manifest.entity_id — customer
|
||||
date: Date,
|
||||
}
|
||||
|
||||
type ManifestLine = {
|
||||
id: number,
|
||||
articleId: number | null,
|
||||
sku: string | null,
|
||||
amount: number,
|
||||
net: number,
|
||||
rebate: number,
|
||||
tax: number, // tax.rate
|
||||
comment: string | null,
|
||||
}
|
||||
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
## `order`
|
||||
|
||||
Company-scoped orders on `manifest` / `manifestarticle` with fixed `type = 'order'`. Same API shape as `quote` (`entityId` customer, not Lieferanten/POS). `manifest.date` may be documented as expected delivery in HTTP docs.
|
||||
|
||||
| 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` |
|
||||
|
||||
```ts
|
||||
type OrderSummary = {
|
||||
id: number,
|
||||
number: string,
|
||||
type: 'order',
|
||||
entityId: number | null,
|
||||
date: Date,
|
||||
}
|
||||
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
## `ledger`
|
||||
|
||||
Append-only SQL Server ledger table (`WITH (LEDGER = ON (APPEND_ONLY = ON))`). Rows cannot be updated or deleted. References `manifestarticle` with `ON DELETE NO ACTION` (a line with ledger entries cannot be removed). No store API yet — INSERT only via future store methods / direct SQL.
|
||||
@@ -328,6 +399,7 @@ Append-only SQL Server ledger table (`WITH (LEDGER = ON (APPEND_ONLY = ON))`). R
|
||||
| `transaction_type` | `NVARCHAR(100)` | |
|
||||
| `amount` | `FLOAT` | |
|
||||
| `transaction_text` | `NVARCHAR(MAX)` | |
|
||||
| `hash_version` | `INT` | hash algorithm / payload version |
|
||||
| `prev_hash` | `NVARCHAR(128)` | nullable (genesis row) |
|
||||
|
||||
Engine also maintains `ledger_start_transaction_id` / `ledger_start_sequence_number`. `purge`/`reset-db` soft-drops the table (`MSSQL_DroppedLedgerTable_*` remains; a new `ledger` can still be created).
|
||||
@@ -611,7 +683,7 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `POST /api/customergroup` | `customergroup.create(googleAuthId, companyId, { name })` |
|
||||
| `PATCH /api/customergroup/:id` | `customergroup.update(googleAuthId, companyId, id, { name })` |
|
||||
| `DELETE /api/customergroup/:id` | `customergroup.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/entity` | `entity.list(googleAuthId, companyId, { customergroupId? })` |
|
||||
| `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? })` |
|
||||
@@ -633,6 +705,16 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `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)` |
|
||||
|
||||
Reference in New Issue
Block a user