u
This commit is contained in:
File diff suppressed because one or more lines are too long
1780
api-graph.svg
1780
api-graph.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 322 KiB After Width: | Height: | Size: 330 KiB |
@@ -39,6 +39,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
|||||||
| [`routes/account/_id.md`](routes/account/_id.md) | `DELETE /account/{id}` |
|
| [`routes/account/_id.md`](routes/account/_id.md) | `DELETE /account/{id}` |
|
||||||
| [`routes/article/create.md`](routes/article/create.md) | `POST /article` |
|
| [`routes/article/create.md`](routes/article/create.md) | `POST /article` |
|
||||||
| [`routes/article/export.zip.md`](routes/article/export.zip.md) | `GET /article/export.zip` |
|
| [`routes/article/export.zip.md`](routes/article/export.zip.md) | `GET /article/export.zip` |
|
||||||
|
| [`routes/article/history-list.md`](routes/article/history-list.md) | `GET /article/history` |
|
||||||
| [`routes/article/delete.md`](routes/article/delete.md) | `DELETE /article/{sku}` |
|
| [`routes/article/delete.md`](routes/article/delete.md) | `DELETE /article/{sku}` |
|
||||||
| [`routes/article/get.md`](routes/article/get.md) | `GET /article/{sku}` |
|
| [`routes/article/get.md`](routes/article/get.md) | `GET /article/{sku}` |
|
||||||
| [`routes/article/update.md`](routes/article/update.md) | `PATCH /article/{sku}` |
|
| [`routes/article/update.md`](routes/article/update.md) | `PATCH /article/{sku}` |
|
||||||
@@ -166,6 +167,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
|||||||
| [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PATCH /labeltemplate/{id}` |
|
| [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PATCH /labeltemplate/{id}` |
|
||||||
| [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PUT /labeltemplate/{id}` |
|
| [`routes/labeltemplate/update.md`](routes/labeltemplate/update.md) | `PUT /labeltemplate/{id}` |
|
||||||
| [`routes/ledger.md`](routes/ledger.md) | `GET /ledger` |
|
| [`routes/ledger.md`](routes/ledger.md) | `GET /ledger` |
|
||||||
|
| [`routes/ledger/history.md`](routes/ledger/history.md) | `GET /ledger/history` |
|
||||||
| [`routes/ledger/manifestarticle/_manifestarticleId.md`](routes/ledger/manifestarticle/_manifestarticleId.md) | `GET /ledger/manifestarticle/{manifestarticleId}` |
|
| [`routes/ledger/manifestarticle/_manifestarticleId.md`](routes/ledger/manifestarticle/_manifestarticleId.md) | `GET /ledger/manifestarticle/{manifestarticleId}` |
|
||||||
| [`routes/ledger/sku/_sku.md`](routes/ledger/sku/_sku.md) | `GET /ledger/sku/{sku}` |
|
| [`routes/ledger/sku/_sku.md`](routes/ledger/sku/_sku.md) | `GET /ledger/sku/{sku}` |
|
||||||
| [`routes/mcp.md`](routes/mcp.md) | `POST /mcp` |
|
| [`routes/mcp.md`](routes/mcp.md) | `POST /mcp` |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Dash API Routes Overview (All)
|
# Dash API Routes Overview (All)
|
||||||
|
|
||||||
Overview of all 261 API endpoints across all modules.
|
Overview of all 263 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -83,6 +83,9 @@ _none_
|
|||||||
#### Export articles as ZIP — `GET /article/export.zip`
|
#### Export articles as ZIP — `GET /article/export.zip`
|
||||||
- Params: `query: categoryId?: int`
|
- Params: `query: categoryId?: int`
|
||||||
- Returns: `200 zip`
|
- Returns: `200 zip`
|
||||||
|
#### Query company-wide article history — `GET /article/history`
|
||||||
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
|
- Returns: `200 { history: { id: int, date: str, sku: str, historytext: str, name?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Delete article by SKU — `DELETE /article/{sku}`
|
#### Delete article by SKU — `DELETE /article/{sku}`
|
||||||
- Params: `sku: str, query: cascade?: bool`
|
- Params: `sku: str, query: cascade?: bool`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
@@ -528,6 +531,9 @@ _none_
|
|||||||
#### Query ledger by manifest article ID or SKU — `GET /ledger`
|
#### Query ledger by manifest article ID or SKU — `GET /ledger`
|
||||||
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
|
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
|
||||||
- Returns: `200 { ledger: T15[] }`
|
- Returns: `200 { ledger: T15[] }`
|
||||||
|
#### Query company-wide ledger history — `GET /ledger/history`
|
||||||
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
|
- Returns: `200 { ledger: { id: int, date: str, transaction_type: str, transaction_text: str, sku?: str|null, name?: str|null, amount?: num }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}`
|
#### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}`
|
||||||
- Params: `manifestarticleId: int, query: transaction_type?: str`
|
- Params: `manifestarticleId: int, query: transaction_type?: str`
|
||||||
- Returns: `200 { ledger: T15[] }`
|
- Returns: `200 { ledger: T15[] }`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Dash API Routes Overview
|
# Dash API Routes Overview
|
||||||
|
|
||||||
Overview of all 186 API endpoints across all modules.
|
Overview of all 188 API endpoints across all modules.
|
||||||
|
|
||||||
### Notation
|
### Notation
|
||||||
|
|
||||||
@@ -50,6 +50,9 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
#### Create article in category — `POST /article`
|
#### Create article in category — `POST /article`
|
||||||
- Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }`
|
- Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }`
|
||||||
- Returns: `201 { article: T26 }`
|
- Returns: `201 { article: T26 }`
|
||||||
|
#### Query company-wide article history — `GET /article/history`
|
||||||
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
|
- Returns: `200 { history: { id: int, date: str, sku: str, historytext: str, name?: str|null }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Delete article by SKU — `DELETE /article/{sku}`
|
#### Delete article by SKU — `DELETE /article/{sku}`
|
||||||
- Params: `sku: str, query: cascade?: bool`
|
- Params: `sku: str, query: cascade?: bool`
|
||||||
- Returns: `204 void`
|
- Returns: `204 void`
|
||||||
@@ -410,6 +413,9 @@ _none_
|
|||||||
#### Query ledger by manifest article ID or SKU — `GET /ledger`
|
#### Query ledger by manifest article ID or SKU — `GET /ledger`
|
||||||
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
|
- Params: `query: manifestarticle_id?: int, sku?: str, transaction_type?: str`
|
||||||
- Returns: `200 { ledger: T11[] }`
|
- Returns: `200 { ledger: T11[] }`
|
||||||
|
#### Query company-wide ledger history — `GET /ledger/history`
|
||||||
|
- Params: `query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||||
|
- Returns: `200 { ledger: { id: int, date: str, transaction_type: str, transaction_text: str, sku?: str|null, name?: str|null, amount?: num }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||||
#### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}`
|
#### Query ledger by manifest article ID — `GET /ledger/manifestarticle/{manifestarticleId}`
|
||||||
- Params: `manifestarticleId: int, query: transaction_type?: str`
|
- Params: `manifestarticleId: int, query: transaction_type?: str`
|
||||||
- Returns: `200 { ledger: T11[] }`
|
- Returns: `200 { ledger: T11[] }`
|
||||||
|
|||||||
52
docs/api/routes/article/history-list.md
Normal file
52
docs/api/routes/article/history-list.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# `GET /article/history`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Query company-wide article history
|
||||||
|
|
||||||
|
Queries article change history across all articles for the authenticated user's selected company.
|
||||||
|
Supports pagination, search filter, and date cutoff via `seekMonths`.
|
||||||
|
|
||||||
|
|
||||||
|
## Query parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `offset` | no | integer | Paging offset |
|
||||||
|
| `limit` | no | integer | Page size |
|
||||||
|
| `search` | no | string | Optional search filter across SKU, article name, and history text |
|
||||||
|
| `seekMonths` | no | integer | Negative integer cutoff months ago (e.g. -13) |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Paged article history list
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"date": "2026-08-10T00:00:00.000Z",
|
||||||
|
"sku": "…",
|
||||||
|
"name": "…",
|
||||||
|
"historytext": "…"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"unfilteredTotal": 1,
|
||||||
|
"offset": 1,
|
||||||
|
"limit": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | Validation error or no company selected — `{"error":"…"}` |
|
||||||
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 403 | Not authorized — `{"error":"…"}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
54
docs/api/routes/ledger/history.md
Normal file
54
docs/api/routes/ledger/history.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# `GET /ledger/history`
|
||||||
|
|
||||||
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
|
Query company-wide ledger history
|
||||||
|
|
||||||
|
Queries ledger entries across all articles for the authenticated user's selected company.
|
||||||
|
Supports pagination, search filter, and date cutoff via `seekMonths`.
|
||||||
|
|
||||||
|
|
||||||
|
## Query parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `offset` | no | integer | Paging offset |
|
||||||
|
| `limit` | no | integer | Page size |
|
||||||
|
| `search` | no | string | Optional search filter across SKU, article name, manifest number, external number, transaction type, and transaction text |
|
||||||
|
| `seekMonths` | no | integer | Negative integer cutoff months ago (e.g. -1) |
|
||||||
|
|
||||||
|
## Response `200`
|
||||||
|
|
||||||
|
Paged ledger history list
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ledger": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"date": "2026-08-10T00:00:00.000Z",
|
||||||
|
"sku": "…",
|
||||||
|
"name": "…",
|
||||||
|
"transaction_type": "…",
|
||||||
|
"transaction_text": "…",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"unfilteredTotal": 1,
|
||||||
|
"offset": 1,
|
||||||
|
"limit": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| 400 | Validation error or no company selected — `{"error":"…"}` |
|
||||||
|
| 401 | Unauthorized — `{"error":"…"}` |
|
||||||
|
| 403 | Not authorized — `{"error":"…"}` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||||
@@ -257,7 +257,155 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @openapi
|
* @openapi
|
||||||
* /article/{sku}:
|
* /article/history:
|
||||||
|
* get:
|
||||||
|
* tags: [Article]
|
||||||
|
* summary: Query company-wide article history
|
||||||
|
* description: |
|
||||||
|
* Queries article change history across all articles for the authenticated user's selected company.
|
||||||
|
* Supports pagination, search filter, and date cutoff via `seekMonths`.
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* x-markdown: article/history-list.md
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: offset
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* minimum: 0
|
||||||
|
* default: 0
|
||||||
|
* description: Paging offset
|
||||||
|
* - in: query
|
||||||
|
* name: limit
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* minimum: 1
|
||||||
|
* maximum: 1000
|
||||||
|
* default: 50
|
||||||
|
* description: Page size
|
||||||
|
* - in: query
|
||||||
|
* name: search
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Optional search filter across SKU, article name, and history text
|
||||||
|
* - in: query
|
||||||
|
* name: seekMonths
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* description: Negative integer cutoff months ago (e.g. -13)
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Paged article history list
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required: [history, total, unfilteredTotal, offset, limit]
|
||||||
|
* properties:
|
||||||
|
* history:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: object
|
||||||
|
* required: [id, date, sku, historytext]
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* date:
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* sku:
|
||||||
|
* type: string
|
||||||
|
* name:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* historytext:
|
||||||
|
* type: string
|
||||||
|
* total:
|
||||||
|
* type: integer
|
||||||
|
* unfilteredTotal:
|
||||||
|
* type: integer
|
||||||
|
* offset:
|
||||||
|
* type: integer
|
||||||
|
* limit:
|
||||||
|
* type: integer
|
||||||
|
* 400:
|
||||||
|
* description: Validation error or no company selected
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 403:
|
||||||
|
* description: Not authorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
*/
|
||||||
|
// #region GET /article/history
|
||||||
|
|
||||||
|
app.get(`${basePath}/article/history`, async (req, res) => {
|
||||||
|
const companyId = req.user.selectedCompanyId;
|
||||||
|
if (companyId == null) {
|
||||||
|
return res.status(400).json({ error: 'No company selected' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawOffset = req.query.offset;
|
||||||
|
const rawLimit = req.query.limit;
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
let limit = 50;
|
||||||
|
|
||||||
|
if (rawOffset !== undefined) {
|
||||||
|
offset = parseInt(rawOffset, 10);
|
||||||
|
if (!Number.isInteger(offset) || offset < 0) {
|
||||||
|
return res.status(400).json({ error: 'offset must be an integer >= 0' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawLimit !== undefined) {
|
||||||
|
limit = parseInt(rawLimit, 10);
|
||||||
|
if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {
|
||||||
|
return res.status(400).json({ error: 'limit must be an integer between 1 and 1000' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = typeof req.query.search === 'string' && req.query.search.trim() !== ''
|
||||||
|
? req.query.search.trim()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
let seekMonths = undefined;
|
||||||
|
if (req.query.seekMonths !== undefined && req.query.seekMonths !== '') {
|
||||||
|
const parsedSeek = parseInt(req.query.seekMonths, 10);
|
||||||
|
if (!Number.isInteger(parsedSeek)) {
|
||||||
|
return res.status(400).json({ error: 'seekMonths must be an integer' });
|
||||||
|
}
|
||||||
|
seekMonths = parsedSeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await db.listHistory(req.user.id, {
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
search,
|
||||||
|
seekMonths,
|
||||||
|
});
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
const status = articleErrorStatus(error);
|
||||||
|
if (status === 500) console.error('List article history failed:', error);
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
/**
|
||||||
* @openapi
|
* @openapi
|
||||||
* /article/{sku}:
|
* /article/{sku}:
|
||||||
* get:
|
* get:
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export function parseFullHash(value) {
|
|||||||
|
|
||||||
export function articleErrorStatus(error) {
|
export function articleErrorStatus(error) {
|
||||||
if (error.message === 'No company selected') return 400;
|
if (error.message === 'No company selected') return 400;
|
||||||
|
if (error.message === 'Invalid offset') return 400;
|
||||||
|
if (error.message === 'Invalid limit') return 400;
|
||||||
|
if (error.message === 'Invalid seekMonths') return 400;
|
||||||
if (error.message === 'Unsupported image format' || error.message?.includes('unsupported image format')) return 400;
|
if (error.message === 'Unsupported image format' || error.message?.includes('unsupported image format')) return 400;
|
||||||
if (error.message === 'Not authorized') return 403;
|
if (error.message === 'Not authorized') return 403;
|
||||||
if (error.message === 'Parent not found') return 404;
|
if (error.message === 'Parent not found') return 404;
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ function requireSelectedCompany(req) {
|
|||||||
|
|
||||||
function ledgerErrorStatus(error) {
|
function ledgerErrorStatus(error) {
|
||||||
if (error.message === 'No company selected') return 400;
|
if (error.message === 'No company selected') return 400;
|
||||||
|
if (error.message === 'Invalid offset') return 400;
|
||||||
|
if (error.message === 'Invalid limit') return 400;
|
||||||
|
if (error.message === 'Invalid seekMonths') return 400;
|
||||||
if (error.message === 'Not authorized') return 403;
|
if (error.message === 'Not authorized') return 403;
|
||||||
if (error.message === 'Company not found') return 404;
|
if (error.message === 'Company not found') return 404;
|
||||||
if (error.message === 'Manifest article not found') return 404;
|
if (error.message === 'Manifest article not found') return 404;
|
||||||
@@ -281,4 +284,160 @@ export function register(app, basePath = '') {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /ledger/history:
|
||||||
|
* get:
|
||||||
|
* tags: [Ledger]
|
||||||
|
* summary: Query company-wide ledger history
|
||||||
|
* description: |
|
||||||
|
* Queries ledger entries across all articles for the authenticated user's selected company.
|
||||||
|
* Supports pagination, search filter, and date cutoff via `seekMonths`.
|
||||||
|
* security:
|
||||||
|
* - cookieAuth: []
|
||||||
|
* x-markdown: ledger/history.md
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: offset
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* minimum: 0
|
||||||
|
* default: 0
|
||||||
|
* description: Paging offset
|
||||||
|
* - in: query
|
||||||
|
* name: limit
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* minimum: 1
|
||||||
|
* maximum: 1000
|
||||||
|
* default: 50
|
||||||
|
* description: Page size
|
||||||
|
* - in: query
|
||||||
|
* name: search
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Optional search filter across SKU, article name, manifest number, external number, transaction type, and transaction text
|
||||||
|
* - in: query
|
||||||
|
* name: seekMonths
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* description: Negative integer cutoff months ago (e.g. -1)
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Paged ledger history list
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required: [ledger, total, unfilteredTotal, offset, limit]
|
||||||
|
* properties:
|
||||||
|
* ledger:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: object
|
||||||
|
* required: [id, date, transaction_type, transaction_text]
|
||||||
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* date:
|
||||||
|
* type: string
|
||||||
|
* format: date-time
|
||||||
|
* sku:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* name:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* transaction_type:
|
||||||
|
* type: string
|
||||||
|
* transaction_text:
|
||||||
|
* type: string
|
||||||
|
* amount:
|
||||||
|
* type: number
|
||||||
|
* total:
|
||||||
|
* type: integer
|
||||||
|
* unfilteredTotal:
|
||||||
|
* type: integer
|
||||||
|
* offset:
|
||||||
|
* type: integer
|
||||||
|
* limit:
|
||||||
|
* type: integer
|
||||||
|
* 400:
|
||||||
|
* description: Validation error or no company selected
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 401:
|
||||||
|
* description: Unauthorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
* 403:
|
||||||
|
* description: Not authorized
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Error'
|
||||||
|
*/
|
||||||
|
// #region GET /ledger/history
|
||||||
|
|
||||||
|
app.get(`${basePath}/ledger/history`, async (req, res) => {
|
||||||
|
const selected = requireSelectedCompany(req);
|
||||||
|
if (selected.error) {
|
||||||
|
return res.status(400).json({ error: selected.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rawOffset = req.query.offset;
|
||||||
|
const rawLimit = req.query.limit;
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
let limit = 50;
|
||||||
|
|
||||||
|
if (rawOffset !== undefined) {
|
||||||
|
offset = parseInt(rawOffset, 10);
|
||||||
|
if (!Number.isInteger(offset) || offset < 0) {
|
||||||
|
return res.status(400).json({ error: 'offset must be an integer >= 0' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawLimit !== undefined) {
|
||||||
|
limit = parseInt(rawLimit, 10);
|
||||||
|
if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {
|
||||||
|
return res.status(400).json({ error: 'limit must be an integer between 1 and 1000' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = typeof req.query.search === 'string' && req.query.search.trim() !== ''
|
||||||
|
? req.query.search.trim()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
let seekMonths = undefined;
|
||||||
|
if (req.query.seekMonths !== undefined && req.query.seekMonths !== '') {
|
||||||
|
const parsedSeek = parseInt(req.query.seekMonths, 10);
|
||||||
|
if (!Number.isInteger(parsedSeek)) {
|
||||||
|
return res.status(400).json({ error: 'seekMonths must be an integer' });
|
||||||
|
}
|
||||||
|
seekMonths = parsedSeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await db.listHistory(req.user.id, {
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
search,
|
||||||
|
seekMonths,
|
||||||
|
});
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
const status = ledgerErrorStatus(error);
|
||||||
|
if (status === 500) console.error('List ledger history failed:', error);
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ export async function getHistoryBySku(googleAuthId, sku, opts) {
|
|||||||
return store.article.getHistoryBySku(googleAuthId, sku, opts);
|
return store.article.getHistoryBySku(googleAuthId, sku, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listHistory(googleAuthId, opts) {
|
||||||
|
const store = await getStore();
|
||||||
|
return store.article.listHistory(googleAuthId, opts);
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateBySku(googleAuthId, sku, data) {
|
export async function updateBySku(googleAuthId, sku, data) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.article.updateBySku(googleAuthId, sku, data);
|
return store.article.updateBySku(googleAuthId, sku, data);
|
||||||
|
|||||||
@@ -26,3 +26,16 @@ export async function listBySku(googleAuthId, sku, transactionType) {
|
|||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.ledger.listBySku(googleAuthId, sku, transactionType);
|
return store.ledger.listBySku(googleAuthId, sku, transactionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists ledger entries across all articles with pagination, seekMonths, and search.
|
||||||
|
*
|
||||||
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
|
* @param {object} [opts] - Query options.
|
||||||
|
* @returns {Promise<object>} Paged ledger history.
|
||||||
|
*/
|
||||||
|
export async function listHistory(googleAuthId, opts) {
|
||||||
|
const store = await getStore();
|
||||||
|
return store.ledger.listHistory(googleAuthId, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user