This commit is contained in:
seb
2026-09-29 18:26:40 +02:00
parent 9beab57ab4
commit 63af77eab3
8 changed files with 732 additions and 0 deletions

View File

@@ -69,6 +69,15 @@ function articleErrorStatus(error) {
if (error.message === 'Invalid weight') return 400;
if (error.message === 'Invalid track_stock') return 400;
if (error.message === 'Invalid categoryIds') return 400;
if (error.message === 'Attribute name not found') return 404;
if (error.message === 'attributeNameId is required to become a father article') return 400;
if (error.message === 'A child article cannot become a father') return 400;
if (error.message === 'Cannot disable father mode on an article that still has children') return 400;
if (error.message === 'Article is not a father article') 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 === 'Child article does not belong to this father') return 400;
if (error.message === 'Father article not found') return 404;
if (error.message?.includes('already exists') || error.code === 'SQLITE_CONSTRAINT_UNIQUE') return 409;
return 500;
}
@@ -1659,4 +1668,267 @@ export function register(app, basePath = '') {
res.status(500).json({ error: error.message });
}
});
/**
* @openapi
* /article/{sku}/father:
* post:
* summary: Toggle father mode for an article
* description: Enables or disables father article status. Enabling requires specifying an attributeNameId; disabling requires the article to have zero children.
* tags:
* - Article
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: sku
* required: true
* schema:
* type: string
* description: Article SKU
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [isFather]
* properties:
* isFather:
* type: boolean
* description: True to make article a father, false to revert to regular article
* attributeNameId:
* type: integer
* description: Required when isFather is true. The attributename id for variants.
* responses:
* 200:
* description: Updated article
* content:
* application/json:
* schema:
* type: object
* properties:
* article:
* $ref: '#/components/schemas/Article'
* 400:
* description: Invalid input or article still has children
* 404:
* description: Article or attribute name not found
*/
app.post(`${basePath}/article/:sku/father`, async (req, res) => {
const parsed = parseSku(req.params.sku);
if (parsed.error) return res.status(400).json({ error: parsed.error });
const isFather = req.body?.isFather;
if (typeof isFather !== 'boolean') {
return res.status(400).json({ error: 'isFather boolean is required' });
}
const attributeNameId = req.body?.attributeNameId ?? req.body?.fatherAttributeNameId;
try {
const result = await db.toggleFather(req.user.id, parsed.sku, {
isFather,
attributeNameId,
});
emitArticleSignal(req, parsed.sku);
emitArticlesSignal(req);
res.status(200).json(result);
} catch (error) {
const status = articleErrorStatus(error);
if (status === 500) console.error('Toggle father failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /article/{sku}/children:
* get:
* summary: List child articles of a father article
* description: Returns variants/children linked to this father article.
* tags:
* - Article
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: sku
* required: true
* schema:
* type: string
* description: Father article SKU
* responses:
* 200:
* description: List of children
* content:
* application/json:
* schema:
* type: object
* properties:
* children:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* sku:
* type: string
* name:
* type: string
* fathersort:
* type: integer
* attributeValueId:
* type: integer
* attributeValue:
* type: string
* 404:
* description: Article not found
*/
app.get(`${basePath}/article/:sku/children`, async (req, res) => {
const parsed = parseSku(req.params.sku);
if (parsed.error) return res.status(400).json({ error: parsed.error });
try {
const children = await db.listChildren(req.user.id, parsed.sku);
res.status(200).json({ children });
} catch (error) {
const status = articleErrorStatus(error);
if (status === 500) console.error('List children failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /article/{sku}/children:
* post:
* summary: Add a child article to a father article
* description: Automatically generates SKU as fatherSku-attributeValueId and assigns the variant attribute.
* 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
* properties:
* 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
* name:
* type: string
* description: Optional custom variant name
* responses:
* 201:
* description: Created child article
* content:
* application/json:
* schema:
* type: object
* properties:
* article:
* $ref: '#/components/schemas/Article'
* 400:
* description: Father not configured as father or missing value
* 409:
* description: Child SKU already exists
*/
app.post(`${basePath}/article/:sku/children`, async (req, res) => {
const parsed = parseSku(req.params.sku);
if (parsed.error) return res.status(400).json({ error: parsed.error });
try {
const result = await db.addChild(req.user.id, parsed.sku, req.body || {});
emitArticleSignal(req, parsed.sku);
if (result?.article?.sku) {
emitArticleSignal(req, result.article.sku);
}
emitArticlesSignal(req);
res.status(201).json(result);
} catch (error) {
const status = articleErrorStatus(error);
if (status === 500) console.error('Add child failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /article/{sku}/children/{childSku}:
* delete:
* summary: Remove a child article from a father article
* description: Unlinks the child from the father (or deletes if deleteArticle query/body is true).
* tags:
* - Article
* security:
* - cookieAuth: []
* parameters:
* - in: path
* name: sku
* required: true
* schema:
* type: string
* description: Father article SKU
* - in: path
* name: childSku
* required: true
* schema:
* type: string
* description: Child article SKU
* - in: query
* name: deleteArticle
* schema:
* type: boolean
* description: If true, permanently deletes the child article instead of just unlinking
* responses:
* 200:
* description: Child removed
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* deleted:
* type: boolean
*/
app.delete(`${basePath}/article/:sku/children/:childSku`, async (req, res) => {
const parsed = parseSku(req.params.sku);
if (parsed.error) return res.status(400).json({ error: parsed.error });
const parsedChild = parseSku(req.params.childSku);
if (parsedChild.error) return res.status(400).json({ error: 'childSku is required' });
const deleteArticle = req.query.deleteArticle === 'true' || req.body?.deleteArticle === true;
try {
const result = await db.removeChild(req.user.id, parsed.sku, parsedChild.sku, {
deleteArticle,
});
emitArticleSignal(req, parsed.sku);
emitArticleSignal(req, parsedChild.sku);
emitArticlesSignal(req);
res.status(200).json(result);
} catch (error) {
const status = articleErrorStatus(error);
if (status === 500) console.error('Remove child failed:', error);
res.status(status).json({ error: error.message });
}
});
}

View File

@@ -79,3 +79,24 @@ export async function listExport(googleAuthId, categoryId) {
const store = await getStore();
return store.article.listExport(googleAuthId, categoryId);
}
export async function toggleFather(googleAuthId, sku, options) {
const store = await getStore();
return store.article.toggleFather(googleAuthId, sku, options);
}
export async function addChild(googleAuthId, sku, options) {
const store = await getStore();
return store.article.addChild(googleAuthId, sku, options);
}
export async function removeChild(googleAuthId, sku, childSku, options) {
const store = await getStore();
return store.article.removeChild(googleAuthId, sku, childSku, options);
}
export async function listChildren(googleAuthId, sku) {
const store = await getStore();
return store.article.listChildren(googleAuthId, sku);
}