feat(api): implement articlesupplier endpoints, sort reordering, and openapi docs

This commit is contained in:
seb
2026-09-17 12:38:16 +02:00
parent 49efd5f8a1
commit fe8fa55fbb
9 changed files with 336 additions and 4 deletions

View File

@@ -40,9 +40,12 @@ function articleSupplierErrorStatus(error) {
if (error.message === 'Supplier required' || error.message === 'entityId is required') return 400;
if (error.message === 'Invalid pu' || error.message === 'pu must be greater than 0') return 400;
if (error.message === 'Invalid stock') return 400;
if (error.message === 'Invalid sort') return 400;
if (error.message === 'Invalid supplier order') return 400;
if (error.message === 'Invalid price') return 400;
if (error.message === 'sku is required') return 400;
if (error.message === 'id is required') return 400;
if (error.message === 'ids is required') return 400;
return 500;
}
@@ -144,6 +147,10 @@ export function register(app, basePath = '') {
* type: number
* nullable: true
* description: Supplier stock
* sort:
* type: integer
* nullable: true
* description: Display sort order (defaults to next sort)
* netPrice:
* type: number
* nullable: true
@@ -161,6 +168,7 @@ export function register(app, basePath = '') {
* sku: MET-44910
* pu: 1
* stock: 140
* sort: 0
* netPrice: 4.20
* taxId: 1
* responses:
@@ -236,7 +244,7 @@ export function register(app, basePath = '') {
return res.status(400).json({ error: skuParsed.error });
}
const { entityId, sku: supplierSku, pu, stock, netPrice, taxId, taxRate } = req.body ?? {};
const { entityId, sku: supplierSku, pu, stock, sort, netPrice, taxId, taxRate } = req.body ?? {};
if (entityId == null || !Number.isFinite(entityId)) {
return res.status(400).json({ error: 'entityId is required' });
}
@@ -246,6 +254,9 @@ export function register(app, basePath = '') {
if (stock !== undefined && stock !== null && !Number.isFinite(stock)) {
return res.status(400).json({ error: 'Invalid stock' });
}
if (sort !== undefined && sort !== null && (!Number.isInteger(sort) || sort < 0)) {
return res.status(400).json({ error: 'Invalid sort' });
}
if (netPrice !== undefined && netPrice !== null && !Number.isFinite(netPrice)) {
return res.status(400).json({ error: 'Invalid price' });
}
@@ -256,6 +267,7 @@ export function register(app, basePath = '') {
sku: supplierSku,
pu,
stock,
sort,
netPrice,
taxId,
taxRate,
@@ -309,6 +321,9 @@ export function register(app, basePath = '') {
* stock:
* type: number
* nullable: true
* sort:
* type: integer
* description: Display sort order
* netPrice:
* type: number
* nullable: true
@@ -438,6 +453,12 @@ export function register(app, basePath = '') {
}
patch.stock = body.stock;
}
if (body.sort !== undefined) {
if (body.sort === null || !Number.isInteger(body.sort) || body.sort < 0) {
return res.status(400).json({ error: 'Invalid sort' });
}
patch.sort = body.sort;
}
if (body.netPrice !== undefined) {
if (body.netPrice !== null && !Number.isFinite(body.netPrice)) {
return res.status(400).json({ error: 'Invalid price' });
@@ -484,4 +505,112 @@ export function register(app, basePath = '') {
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /article/{sku}/supplier/order:
* put:
* tags: [Article]
* summary: Reorder article suppliers
* description: |
* Sets the display sort order of supplier assignments for an article.
* Expects an array of all assigned `articlesupplier` IDs in the desired order.
* security:
* - cookieAuth: []
* x-signals: [article-<sku>]
* x-markdown: article/supplier/order.md
* parameters:
* - in: path
* name: sku
* required: true
* schema:
* type: string
* description: Article SKU
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [ids]
* properties:
* ids:
* type: array
* items:
* type: integer
* description: Full list of articlesupplier ids in their new order
* example:
* ids: [12, 10, 15]
* responses:
* 200:
* description: New order applied
* content:
* application/json:
* schema:
* type: object
* properties:
* ids:
* type: array
* items:
* type: integer
* 400:
* description: Invalid supplier order, ids is required, 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'
* 404:
* description: Article or company not found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
app.put(`${basePath}/article/:sku/supplier/order`, async (req, res) => {
const selected = requireSelectedCompany(req);
if (selected.error) {
return res.status(400).json({ error: selected.error });
}
const skuParsed = parseSku(req.params.sku);
if (skuParsed.error) {
return res.status(400).json({ error: skuParsed.error });
}
const rawIds = req.body?.ids;
if (!Array.isArray(rawIds)) {
return res.status(400).json({ error: 'ids is required' });
}
const ids = [];
for (const value of rawIds) {
const id = Number(value);
if (!Number.isInteger(id) || id <= 0) {
return res.status(400).json({ error: 'Invalid supplier order' });
}
ids.push(id);
}
try {
const result = await db.order(req.user.id, skuParsed.sku, { ids });
emitArticleSignal(req, skuParsed.sku);
res.status(200).json(result);
} catch (error) {
const status = articleSupplierErrorStatus(error);
if (status === 500) console.error('Order article suppliers failed:', error);
res.status(status).json({ error: error.message });
}
});
}