diff --git a/docs/api/README.md b/docs/api/README.md index c749af0..c70af5d 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -45,6 +45,7 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route | [`routes/article/set-categories.md`](routes/article/set-categories.md) | `PUT /article/{sku}/categories` | | [`routes/article/_sku/children.get.md`](routes/article/_sku/children.get.md) | `GET /article/{sku}/children` | | [`routes/article/_sku/children.post.md`](routes/article/_sku/children.post.md) | `POST /article/{sku}/children` | +| [`routes/article/_sku/children.put.md`](routes/article/_sku/children.put.md) | `PUT /article/{sku}/children` | | [`routes/article/_sku/children/_childSku.md`](routes/article/_sku/children/_childSku.md) | `DELETE /article/{sku}/children/{childSku}` | | [`routes/article/set-eans.md`](routes/article/set-eans.md) | `PUT /article/{sku}/eans` | | [`routes/article/_sku/father.md`](routes/article/_sku/father.md) | `POST /article/{sku}/father` | diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 5d61a62..2913b12 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -14615,8 +14615,8 @@ } }, "post": { - "summary": "Add a child article to a father article", - "description": "Automatically generates SKU as fatherSku-nextFreeIndex (or accepts custom sku) and assigns the variant attribute.", + "summary": "Create and attach a new child article to a father article", + "description": "Automatically generates SKU as fatherSku-nextFreeIndex and name from father name + value. Rejects a caller-supplied sku.", "tags": [ "Article" ], @@ -14654,10 +14654,6 @@ "name": { "type": "string", "description": "Optional custom variant name" - }, - "sku": { - "type": "string", - "description": "Optional custom SKU for the child article" } } } @@ -14681,7 +14677,93 @@ } }, "400": { - "description": "Father not configured as father, missing value, duplicate variant, or child SKU already exists" + "description": "Father not configured as father, missing value, duplicate variant, or sku was provided in body" + } + } + }, + "put": { + "summary": "Attach an existing article as a child to a father article", + "description": "Attaches an existing article to this father article. Idempotent if already a child. Returns 409 if the article already belongs to another father.", + "tags": [ + "Article" + ], + "security": [ + { + "cookieAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sku", + "required": true, + "schema": { + "type": "string" + }, + "description": "Father article SKU" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "sku" + ], + "properties": { + "sku": { + "type": "string", + "description": "SKU of the existing article to attach as child" + }, + "attributeValueId": { + "type": "integer", + "description": "ID of existing attribute value for the father's attribute" + }, + "value": { + "type": "string", + "description": "String value to find or create under the father's attribute" + }, + "reparent": { + "type": "boolean", + "description": "Whether to allow re-parenting if child already belongs to another father" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Attached child article and list of children", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "article": { + "$ref": "#/components/schemas/Article" + }, + "children": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "400": { + "description": "Missing sku or attribute value, or invalid input" + }, + "404": { + "description": "Father or child article not found" + }, + "409": { + "description": "Article already belongs to another father, or variant value already exists" } } } diff --git a/docs/api/routes-all.md b/docs/api/routes-all.md index 14b5e3f..188488b 100644 --- a/docs/api/routes-all.md +++ b/docs/api/routes-all.md @@ -1,6 +1,6 @@ # Dash API Routes Overview (All) -Overview of all 180 API endpoints across all modules. +Overview of all 181 API endpoints across all modules. ### Parameter Notation @@ -24,7 +24,8 @@ Overview of all 180 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: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | 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: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | 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 | -| `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 | +| `POST` | `/article/{sku}/children` | `sku:string, body: { attributeValueId?, value?, name? }` | `201 { article: { id, categoryId, name, sku, track_stock } }` | Create and attach a new child article to a father article | +| `PUT` | `/article/{sku}/children` | `sku:string, body: { sku: string, attributeValueId?, value?, reparent? }` | `200 { article, children: object[] }` | Attach an existing article as a child 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 | | `PUT` | `/article/{sku}/eans` | `sku:string, body: { eans: string[] }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | 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 | diff --git a/docs/api/routes.md b/docs/api/routes.md index 76776d8..c47fb9c 100644 --- a/docs/api/routes.md +++ b/docs/api/routes.md @@ -1,6 +1,6 @@ # Dash API Routes Overview -Overview of all 144 API endpoints across all modules. +Overview of all 145 API endpoints across all modules. ### Parameter Notation @@ -23,7 +23,8 @@ Overview of all 144 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: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | 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: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | 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 | -| `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 | +| `POST` | `/article/{sku}/children` | `sku:string, body: { attributeValueId?, value?, name? }` | `201 { article: { id, categoryId, name, sku, track_stock } }` | Create and attach a new child article to a father article | +| `PUT` | `/article/{sku}/children` | `sku:string, body: { sku: string, attributeValueId?, value?, reparent? }` | `200 { article, children: object[] }` | Attach an existing article as a child 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 | | `PUT` | `/article/{sku}/eans` | `sku:string, body: { eans: string[] }` | `200 { article: { id, sku, name, slug, track_stock, unitamount, weight, unit, brand, categoryIds: integer[], photos: { sort, fullHash, fullW, fullH, fullSize, thumbW, thumbH, thumbHash }[], eans: string[], prices: { minmultiplier, net, taxRate, customergroupId }[], suppliers: { id, articleId, entityId, supplierName, sku, pu, stock, sort, priceId, price }[] } }` | 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 | diff --git a/docs/api/routes/article/_sku/children.post.md b/docs/api/routes/article/_sku/children.post.md index d060018..5e9b688 100644 --- a/docs/api/routes/article/_sku/children.post.md +++ b/docs/api/routes/article/_sku/children.post.md @@ -2,9 +2,9 @@ Protected. Requires a valid session cookie (`credentials: 'include'`). -Add a child article to a father article +Create and attach a new child article to a father article -Automatically generates SKU as fatherSku-nextFreeIndex (or accepts custom sku) and assigns the variant attribute. +Automatically generates SKU as fatherSku-nextFreeIndex and name from father name + value. Rejects a caller-supplied sku. ## Path parameters @@ -19,14 +19,12 @@ Automatically generates SKU as fatherSku-nextFreeIndex (or accepts custom sku) a | `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 | | `name` | no | string | Optional custom variant name | -| `sku` | no | string | Optional custom SKU for the child article | ```json { "attributeValueId": 1, "value": "…", - "name": "…", - "sku": "…" + "name": "…" } ``` @@ -50,7 +48,7 @@ Created child article | Status | Description | |--------|-------------| -| 400 | Father not configured as father, missing value, duplicate variant, or child SKU already exists | +| 400 | Father not configured as father, missing value, duplicate variant, or sku was provided in body | --- diff --git a/docs/api/routes/article/_sku/children.put.md b/docs/api/routes/article/_sku/children.put.md new file mode 100644 index 0000000..229b0bc --- /dev/null +++ b/docs/api/routes/article/_sku/children.put.md @@ -0,0 +1,62 @@ +# `PUT /article/{sku}/children` + +Protected. Requires a valid session cookie (`credentials: 'include'`). + +Attach an existing article as a child to a father article + +Attaches an existing article to this father article. Idempotent if already a child. Returns 409 if the article already belongs to another father. + +## Path parameters + +| Param | Required | Type | Description | +|-------|----------|------|-------------| +| `sku` | yes | string | Father article SKU | + +## Body (JSON) + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `sku` | yes | string | SKU of the existing article to attach as child | +| `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 | +| `reparent` | no | boolean | Whether to allow re-parenting if child already belongs to another father | + +```json +{ + "sku": "…", + "attributeValueId": 1, + "value": "…", + "reparent": true +} +``` + +## Response `200` + +Attached child article and list of children + +```json +{ + "article": { + "id": 1, + "categoryId": 1, + "name": "…", + "sku": "…", + "track_stock": true + }, + "children": [ + {} + ] +} +``` + +## Errors + +| Status | Description | +|--------|-------------| +| 400 | Missing sku or attribute value, or invalid input | +| 404 | Father or child article not found | +| 409 | Article already belongs to another father, or variant value already exists | + +--- + +_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._ diff --git a/src/api/routes/attributename.js b/src/api/routes/attributename.js index 0b47abd..109eb11 100644 --- a/src/api/routes/attributename.js +++ b/src/api/routes/attributename.js @@ -17,6 +17,7 @@ function attributeErrorStatus(error) { if (error.message === 'Name required') return 400; if (error.message === 'Invalid attribute name order') return 400; if (error.message === 'Invalid id') return 400; + if (error.message === 'Attribute name already exists') return 409; if (error.message === 'Attribute name in use' || error.message?.includes('FOREIGN KEY constraint')) return 409; return 500; } diff --git a/src/api/routes/attributevalue.js b/src/api/routes/attributevalue.js index 277cab9..38b003d 100644 --- a/src/api/routes/attributevalue.js +++ b/src/api/routes/attributevalue.js @@ -16,6 +16,7 @@ function attributeValueErrorStatus(error) { if (error.message === 'Value required') return 400; if (error.message === 'Invalid attribute value order') return 400; if (error.message === 'Invalid id' || error.message === 'Invalid attributeId') return 400; + if (error.message === 'Attribute value already exists') return 409; if (error.message === 'Attribute value in use' || error.message?.includes('FOREIGN KEY constraint')) return 409; return 500; } diff --git a/src/api/routes/propertyname.js b/src/api/routes/propertyname.js index 425342a..f4159a2 100644 --- a/src/api/routes/propertyname.js +++ b/src/api/routes/propertyname.js @@ -17,6 +17,7 @@ function propertyErrorStatus(error) { if (error.message === 'Name required') return 400; if (error.message === 'Invalid property name order') return 400; if (error.message === 'Invalid id') return 400; + if (error.message === 'Property name already exists') return 409; if (error.message === 'Property name in use' || error.message?.startsWith('Cannot set property exclusive') || error.message?.includes('FOREIGN KEY constraint')) return 409; return 500; }