feat(api): implement articlesupplier endpoints, sort reordering, and openapi docs
This commit is contained in:
@@ -124,7 +124,7 @@
|
||||
* description: Tax rate in percent
|
||||
* ArticleSupplier:
|
||||
* type: object
|
||||
* required: [id, articleId, entityId, pu]
|
||||
* required: [id, articleId, entityId, pu, sort]
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
@@ -146,6 +146,9 @@
|
||||
* type: number
|
||||
* nullable: true
|
||||
* description: Supplier stock
|
||||
* sort:
|
||||
* type: integer
|
||||
* description: Display sort order
|
||||
* priceId:
|
||||
* type: integer
|
||||
* nullable: true
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,3 +19,9 @@ export async function remove(googleAuthId, sku, id) {
|
||||
const store = await getStore();
|
||||
return store.articlesupplier.delete(googleAuthId, sku, id);
|
||||
}
|
||||
|
||||
export async function order(googleAuthId, sku, data) {
|
||||
const store = await getStore();
|
||||
return store.articlesupplier.order(googleAuthId, sku, data);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user