u
This commit is contained in:
@@ -23,11 +23,11 @@ Overview of all 136 API endpoints across all modules.
|
|||||||
| `PUT` | `/article/{sku}/brand` | `sku:string, body: { brandId: integer }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: object[], eans: string[], prices: object[], suppliers: object[] } }` | Set or clear article brand by SKU |
|
| `PUT` | `/article/{sku}/brand` | `sku:string, body: { brandId: integer }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: object[], eans: string[], prices: object[], suppliers: object[] } }` | Set or clear article brand by SKU |
|
||||||
| `PUT` | `/article/{sku}/categories` | `sku:string, body: { categoryIds: integer[] }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: object[], eans: string[], prices: object[], suppliers: object[] } }` | Replace article categories by SKU |
|
| `PUT` | `/article/{sku}/categories` | `sku:string, body: { categoryIds: integer[] }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: object[], eans: string[], prices: object[], suppliers: object[] } }` | Replace article categories by SKU |
|
||||||
| `GET` | `/article/{sku}/children` | `sku:string` | `200 { children: { id, sku, name, fathersort, attributeValueId, attributeValue }[] }` | List child articles of a father article |
|
| `GET` | `/article/{sku}/children` | `sku:string` | `200 { children: { id, sku, name, fathersort, attributeValueId, attributeValue }[] }` | List child articles of a father article |
|
||||||
| `POST` | `/article/{sku}/children` | `sku:string, body: { attributeValueId?, value?, name? }` | `201 { article: { id, categoryId, name, sku, track_stock } }` | Add a child article to a father article |
|
| `POST` | `/article/{sku}/children` | `sku:string, body: { attributeValueId?, value?, name?, sku?: string }` | `201 { article: { id, categoryId, name, sku, track_stock } }` | Add a child article to a father article |
|
||||||
| `DELETE` | `/article/{sku}/children/{childSku}` | `sku:string, childSku:string, query: deleteArticle?:boolean` | `200 { success, deleted }` | Remove a child article from a father article |
|
| `DELETE` | `/article/{sku}/children/{childSku}` | `sku:string, childSku:string, query: deleteArticle?:boolean` | `200 { success, deleted }` | Remove a child article from a father article |
|
||||||
| `PUT` | `/article/{sku}/eans` | `sku:string, body: { eans: string[] }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: object[], eans: string[], prices: object[], suppliers: object[] } }` | Replace article EANs by SKU |
|
| `PUT` | `/article/{sku}/eans` | `sku:string, body: { eans: string[] }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: object[], eans: string[], prices: object[], suppliers: object[] } }` | Replace article EANs by SKU |
|
||||||
| `POST` | `/article/{sku}/father` | `sku:string, body: { isFather, attributeNameId? }` | `200 { article: { id, categoryId, name, sku, track_stock } }` | Toggle father mode for an article |
|
| `POST` | `/article/{sku}/father` | `sku:string, body: { isFather, attributeNameId? }` | `200 { article: { id, categoryId, name, sku, track_stock } }` | Toggle father mode for an article |
|
||||||
| `GET` | `/article/{sku}/history` | `sku:string` | `200 { history: { id, date, text }[] }` | Get article history by SKU |
|
| `GET` | `/article/{sku}/history` | `sku:string, query: limit?:integer` | `200 { history: { id, date, text }[], truncated }` | Get article history by SKU |
|
||||||
| `DELETE` | `/article/{sku}/image/{fullHash}` | `sku:string, fullHash:string` | `204 void` | Delete article image |
|
| `DELETE` | `/article/{sku}/image/{fullHash}` | `sku:string, fullHash:string` | `204 void` | Delete article image |
|
||||||
| `PUT` | `/article/{sku}/images/order` | `sku:string, body: { fullHashes: string[] }` | `200 { fullHashes: string[] }` | Reorder article images |
|
| `PUT` | `/article/{sku}/images/order` | `sku:string, body: { fullHashes: string[] }` | `200 { fullHashes: string[] }` | Reorder article images |
|
||||||
| `GET` | `/article/{sku}/optionslist` | `sku:string` | `200 { options: { id, articleId, sku, name, count, sort, mandated }[] }` | Get options list for an article |
|
| `GET` | `/article/{sku}/optionslist` | `sku:string` | `200 { options: { id, articleId, sku, name, count, sort, mandated }[] }` | Get options list for an article |
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Replaces all attribute values for an article. Enforces exclusive constraint (max
|
|||||||
|
|
||||||
| Field | Required | Type | Description |
|
| Field | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `attributevalues` | yes | any[] | |
|
| `attributevalues` | yes | any[] | Array of attribute value IDs (numbers) or objects with id or attributeValueId |
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`).
|
|||||||
|
|
||||||
Add a child article to a father article
|
Add a child article to a father article
|
||||||
|
|
||||||
Automatically generates SKU as fatherSku-attributeValueId and assigns the variant attribute.
|
Automatically generates SKU as fatherSku-nextFreeIndex (or accepts custom sku) and assigns the variant attribute.
|
||||||
|
|
||||||
## Path parameters
|
## Path parameters
|
||||||
|
|
||||||
@@ -19,12 +19,14 @@ Automatically generates SKU as fatherSku-attributeValueId and assigns the varian
|
|||||||
| `attributeValueId` | no | integer | ID of existing attribute value for the father's attribute |
|
| `attributeValueId` | no | integer | ID of existing attribute value for the father's attribute |
|
||||||
| `value` | no | string | String value to find or create under the father's attribute |
|
| `value` | no | string | String value to find or create under the father's attribute |
|
||||||
| `name` | no | string | Optional custom variant name |
|
| `name` | no | string | Optional custom variant name |
|
||||||
|
| `sku` | no | string | Optional custom SKU for the child article |
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"attributeValueId": 1,
|
"attributeValueId": 1,
|
||||||
"value": "…",
|
"value": "…",
|
||||||
"name": "…"
|
"name": "…",
|
||||||
|
"sku": "…"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -48,8 +50,7 @@ Created child article
|
|||||||
|
|
||||||
| Status | Description |
|
| Status | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| 400 | Father not configured as father or missing value |
|
| 400 | Father not configured as father, missing value, duplicate variant, or child SKU already exists |
|
||||||
| 409 | Child SKU already exists |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ Auth like GET by SKU.
|
|||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `sku` | yes | string | Article SKU |
|
| `sku` | yes | string | Article SKU |
|
||||||
|
|
||||||
|
## Query parameters
|
||||||
|
|
||||||
|
| Param | Required | Type | Description |
|
||||||
|
|-------|----------|------|-------------|
|
||||||
|
| `limit` | no | integer | Maximum number of entries to return (newest first). Omit for all entries. |
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
|
|
||||||
Article history (newest first)
|
Article history (newest first)
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ function articleErrorStatus(error) {
|
|||||||
if (error.message === 'Attribute value is required for child article' || error.message === 'Attribute value is required') return 400;
|
if (error.message === 'Attribute value is required for child article' || error.message === 'Attribute value is required') return 400;
|
||||||
if (error.message === 'Attribute value not found or does not match father attribute') return 400;
|
if (error.message === 'Attribute value not found or does not match father attribute') return 400;
|
||||||
if (error.message === 'Child article does not belong to this father') return 400;
|
if (error.message === 'Child article does not belong to this father') return 400;
|
||||||
|
if (error.message === 'Variant with this attribute value already exists') return 400;
|
||||||
|
if (error.message?.startsWith('Child article') && error.message?.includes('already exists')) return 400;
|
||||||
|
if (error.message?.toLowerCase().includes('duplicate variant')) return 400;
|
||||||
if (error.message === 'Father article not found') return 404;
|
if (error.message === 'Father article not found') return 404;
|
||||||
if (
|
if (
|
||||||
error.message?.includes('Stückliste') ||
|
error.message?.includes('Stückliste') ||
|
||||||
@@ -553,6 +556,14 @@ export function register(app, basePath = '') {
|
|||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Article SKU
|
* description: Article SKU
|
||||||
|
* - in: query
|
||||||
|
* name: limit
|
||||||
|
* required: false
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* minimum: 1
|
||||||
|
* maximum: 1000
|
||||||
|
* description: Maximum number of entries to return (newest first). Omit for all entries.
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: Article history (newest first)
|
* description: Article history (newest first)
|
||||||
@@ -560,12 +571,15 @@ export function register(app, basePath = '') {
|
|||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [history]
|
* required: [history, truncated]
|
||||||
* properties:
|
* properties:
|
||||||
* history:
|
* history:
|
||||||
* type: array
|
* type: array
|
||||||
* items:
|
* items:
|
||||||
* $ref: '#/components/schemas/ArticleHistoryEntry'
|
* $ref: '#/components/schemas/ArticleHistoryEntry'
|
||||||
|
* truncated:
|
||||||
|
* type: boolean
|
||||||
|
* description: True when a limit was applied and the result set was exactly that size (more entries may exist).
|
||||||
* example:
|
* example:
|
||||||
* history:
|
* history:
|
||||||
* - id: 42
|
* - id: 42
|
||||||
@@ -605,9 +619,19 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: 'sku is required' });
|
return res.status(400).json({ error: 'sku is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rawLimit = req.query.limit;
|
||||||
|
let limit = null;
|
||||||
|
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' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const history = await db.getHistoryBySku(req.user.id, decodeURIComponent(sku));
|
const history = await db.getHistoryBySku(req.user.id, decodeURIComponent(sku), limit != null ? { limit } : undefined);
|
||||||
res.status(200).json({ history });
|
const truncated = limit != null && history.length === limit;
|
||||||
|
res.status(200).json({ history, truncated });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const status = articleErrorStatus(error);
|
const status = articleErrorStatus(error);
|
||||||
if (status === 500) console.error('Get article history by sku failed:', error);
|
if (status === 500) console.error('Get article history by sku failed:', error);
|
||||||
@@ -1868,7 +1892,7 @@ export function register(app, basePath = '') {
|
|||||||
* /article/{sku}/children:
|
* /article/{sku}/children:
|
||||||
* post:
|
* post:
|
||||||
* summary: Add a child article to a father article
|
* summary: Add a child article to a father article
|
||||||
* description: Automatically generates SKU as fatherSku-attributeValueId and assigns the variant attribute.
|
* description: Automatically generates SKU as fatherSku-nextFreeIndex (or accepts custom sku) and assigns the variant attribute.
|
||||||
* tags:
|
* tags:
|
||||||
* - Article
|
* - Article
|
||||||
* security:
|
* security:
|
||||||
@@ -1896,6 +1920,9 @@ export function register(app, basePath = '') {
|
|||||||
* name:
|
* name:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Optional custom variant name
|
* description: Optional custom variant name
|
||||||
|
* sku:
|
||||||
|
* type: string
|
||||||
|
* description: Optional custom SKU for the child article
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: Created child article
|
* description: Created child article
|
||||||
@@ -1907,9 +1934,7 @@ export function register(app, basePath = '') {
|
|||||||
* article:
|
* article:
|
||||||
* $ref: '#/components/schemas/Article'
|
* $ref: '#/components/schemas/Article'
|
||||||
* 400:
|
* 400:
|
||||||
* description: Father not configured as father or missing value
|
* description: Father not configured as father, missing value, duplicate variant, or child SKU already exists
|
||||||
* 409:
|
|
||||||
* description: Child SKU already exists
|
|
||||||
*/
|
*/
|
||||||
app.post(`${basePath}/article/:sku/children`, async (req, res) => {
|
app.post(`${basePath}/article/:sku/children`, async (req, res) => {
|
||||||
const parsed = parseSku(req.params.sku);
|
const parsed = parseSku(req.params.sku);
|
||||||
|
|||||||
@@ -89,14 +89,19 @@ export function register(app, basePath = '') {
|
|||||||
* properties:
|
* properties:
|
||||||
* attributevalues:
|
* attributevalues:
|
||||||
* type: array
|
* type: array
|
||||||
|
* description: Array of attribute value IDs (numbers) or objects with id or attributeValueId
|
||||||
* items:
|
* items:
|
||||||
* oneOf:
|
* oneOf:
|
||||||
* - type: integer
|
* - type: integer
|
||||||
|
* description: Attribute value ID
|
||||||
* - type: object
|
* - type: object
|
||||||
* required: [attributeValueId]
|
|
||||||
* properties:
|
* properties:
|
||||||
|
* id:
|
||||||
|
* type: integer
|
||||||
|
* description: Attribute value ID
|
||||||
* attributeValueId:
|
* attributeValueId:
|
||||||
* type: integer
|
* type: integer
|
||||||
|
* description: Attribute value ID
|
||||||
* sort:
|
* sort:
|
||||||
* type: integer
|
* type: integer
|
||||||
* responses:
|
* responses:
|
||||||
@@ -142,7 +147,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: skuParsed.error });
|
return res.status(400).json({ error: skuParsed.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const valuesInput = req.body?.attributevalues ?? req.body;
|
const valuesInput = req.body?.attributevalues ?? req.body?.attributeValues ?? req.body?.attribute_values ?? req.body?.values ?? req.body;
|
||||||
if (!Array.isArray(valuesInput)) {
|
if (!Array.isArray(valuesInput)) {
|
||||||
return res.status(400).json({ error: 'attributevalues array required' });
|
return res.status(400).json({ error: 'attributevalues array required' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ function deliveryErrorStatus(error) {
|
|||||||
if (error.message === 'Invalid file') return 400;
|
if (error.message === 'Invalid file') return 400;
|
||||||
if (error.message === 'Invalid JSON in "data" field') return 400;
|
if (error.message === 'Invalid JSON in "data" field') return 400;
|
||||||
if (error.message === 'Only PDF or text files are accepted as attachments') return 400;
|
if (error.message === 'Only PDF or text files are accepted as attachments') return 400;
|
||||||
|
if (error.message?.includes('father article') || error.message?.includes('A father article')) return 400;
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function orderErrorStatus(error) {
|
|||||||
if (error.message === 'Invalid entityId') return 400;
|
if (error.message === 'Invalid entityId') return 400;
|
||||||
if (error.message === 'Invalid order') return 400;
|
if (error.message === 'Invalid order') return 400;
|
||||||
if (error.message === 'Invalid lines') return 400;
|
if (error.message === 'Invalid lines') return 400;
|
||||||
|
if (error.message?.includes('father article') || error.message?.includes('A father article')) return 400;
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function quoteErrorStatus(error) {
|
|||||||
if (error.message === 'Invalid entityId') return 400;
|
if (error.message === 'Invalid entityId') return 400;
|
||||||
if (error.message === 'Invalid quote') return 400;
|
if (error.message === 'Invalid quote') return 400;
|
||||||
if (error.message === 'Invalid lines') return 400;
|
if (error.message === 'Invalid lines') return 400;
|
||||||
|
if (error.message?.includes('father article') || error.message?.includes('A father article')) return 400;
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function stockErrorStatus(error) {
|
|||||||
if (error.message?.includes('Amount must be')) return 400;
|
if (error.message?.includes('Amount must be')) return 400;
|
||||||
if (error.message?.includes('Invalid amount')) return 400;
|
if (error.message?.includes('Invalid amount')) return 400;
|
||||||
if (error.message === 'No items to adjust') return 400;
|
if (error.message === 'No items to adjust') return 400;
|
||||||
if (error.message?.includes('cannot have stock') || error.message?.includes('A father article cannot have') || error.message?.includes('father article')) return 400;
|
if (error.message?.includes('cannot have stock') || error.message?.includes('A father article cannot have') || error.message?.includes('father article') || error.message?.includes('Stückliste')) return 400;
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ export async function getBySku(googleAuthId, sku) {
|
|||||||
return store.article.getBySku(googleAuthId, sku);
|
return store.article.getBySku(googleAuthId, sku);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHistoryBySku(googleAuthId, sku) {
|
export async function getHistoryBySku(googleAuthId, sku, opts) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.article.getHistoryBySku(googleAuthId, sku);
|
return store.article.getHistoryBySku(googleAuthId, sku, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateBySku(googleAuthId, sku, data) {
|
export async function updateBySku(googleAuthId, sku, data) {
|
||||||
|
|||||||
Reference in New Issue
Block a user