This commit is contained in:
seb
2026-10-10 00:39:23 +02:00
parent 2c36a705c4
commit ff54fe3a18
30 changed files with 2250 additions and 2256 deletions

413
src/api/article.js Normal file
View File

@@ -0,0 +1,413 @@
import { http, httpBinary, httpGetBinary } from './http.js';
/**
* NOTE: there is no `articleIndex` namespace anymore.
*
* The whole-company search index (`GET /api/articleIndex`) was abandoned. Article
* lists are fetched per category via `category.getArticles(categoryId)`
* (`GET /api/category/:id/articles`); `categoryAutomat.loadTree()` warms every
* category view right after the tree loads, and `articleIndexAutomat` derives the
* client-side search index from those views.
*/
export const article = {
search({ target, filter, offset, maxCount } = {}) {
const params = new URLSearchParams({ target });
if (filter) params.set('filter', filter);
if (offset) params.set('offset', String(offset));
if (maxCount != null) params.set('maxCount', String(maxCount));
return http(`/api/article/search?${params}`);
},
/**
* GET /api/article/:sku → { article }
* Detail: prices[], photos[], eans[], brand, unit, categoryIds, …
* (Flat net/taxRate/fullHash/thumb* only on category tree / getCategoryArticles.)
*/
getBySku(sku) {
return http(`/api/article/${encodeURIComponent(sku)}`);
},
/**
* GET /api/article/history
* History of all articles with paging/sorting/filtering
*/
getHistory({ startRow, endRow, offset, limit, sortModel, filterModel } = {}) {
const params = new URLSearchParams();
if (startRow != null) params.set('startRow', String(startRow));
if (endRow != null) params.set('endRow', String(endRow));
if (offset != null) params.set('offset', String(offset));
if (limit != null) params.set('limit', String(limit));
if (sortModel && sortModel.length) params.set('sort', JSON.stringify(sortModel));
if (filterModel && Object.keys(filterModel).length) params.set('filter', JSON.stringify(filterModel));
const qs = params.toString();
return http(`/api/article/history${qs ? `?${qs}` : ''}`);
},
/**
* GET /api/article/:sku/history
*/
getBySkuHistory(sku, { limit } = {}) {
const params = new URLSearchParams();
if (limit != null) params.set('limit', String(limit));
const qs = params.toString();
return http(`/api/article/${encodeURIComponent(sku)}/history${qs ? `?${qs}` : ''}`);
},
/** POST /api/article → 201 { article } */
create({ categoryId, name, track_stock }) {
return http('/api/article', {
method: 'POST',
body: {
categoryId: Number(categoryId),
name,
...(track_stock !== undefined ? { track_stock: Boolean(track_stock) } : {}),
},
});
},
/**
* PATCH /api/article/:sku
* Body: at least one of { name, unitamount, unitSi, track_stock, desc, seo }
* unitSi must exist for selected company → 404 Unit not found
* → 200 { article } (same detail shape as GET: includes desc, seo)
*/
update(sku, body) {
return http(`/api/article/${encodeURIComponent(sku)}`, {
method: 'PATCH',
body,
});
},
/**
* PUT /api/article/:sku/track_stock
* Body: { track_stock: boolean }
* → 200 { article }
*/
setTrackStock(sku, body) {
const payload = typeof body === 'boolean' ? { track_stock: body } : body;
return http(`/api/article/${encodeURIComponent(sku)}/track_stock`, {
method: 'PUT',
body: payload,
});
},
/**
* DELETE /api/article/:sku → 204
* Errors: 400 Article has children, 401, 403, 404 Article not found
*/
delete(sku, { cascade = false } = {}) {
const qs = cascade ? '?cascade=true' : '';
return http(`/api/article/${encodeURIComponent(sku)}${qs}`, {
method: 'DELETE',
});
},
/**
* POST /api/article/:sku/image
* Body: raw image bytes (Content-Type: image/webp, max 1000×1000)
* → 201 { image: { picId, sort, full, thumb, fullHash, thumbHash } }
*/
uploadImage(sku, file) {
return httpBinary(`/api/article/${encodeURIComponent(sku)}/image`, {
method: 'POST',
contentType: file?.type || 'image/webp',
body: file,
});
},
/**
* DELETE /api/article/:sku/image/:fullHash → 204
*/
deleteImage(sku, fullHash) {
return http(
`/api/article/${encodeURIComponent(sku)}/image/${encodeURIComponent(fullHash)}`,
{ method: 'DELETE' },
);
},
/**
* PUT /api/article/:sku/images/order
* Body: { fullHashes: string[] } (every image exactly once)
* → 200 { fullHashes: string[] }
*/
orderImages(sku, fullHashes) {
return http(`/api/article/${encodeURIComponent(sku)}/images/order`, {
method: 'PUT',
body: { fullHashes },
});
},
/**
* PUT /api/article/:sku/prices
* Body: { prices: [{ price (net), tax (rate), minmultiplier? }] }
* Replace-all POS customer group prices. Empty prices clears all POS prices.
* → 200 { article } (same shape as GET by SKU)
*/
setPrices(sku, prices) {
return http(`/api/article/${encodeURIComponent(sku)}/prices`, {
method: 'PUT',
body: { prices },
});
},
/**
* PUT /api/article/:sku/weight
* Body: { weight } → 200 { article }
*/
setWeight(sku, body) {
return http(`/api/article/${encodeURIComponent(sku)}/weight`, {
method: 'PUT',
body,
});
},
/**
* PUT /api/article/:sku/brand
* Body: { brandId: number | null } — null clears the brand.
* brandId must belong to the article's company → 404 Article or brand not found.
* → 200 { article } (same shape as GET by SKU)
*/
setBrandIdBySku(sku, brandId) {
return http(`/api/article/${encodeURIComponent(sku)}/brand`, {
method: 'PUT',
body: { brandId: brandId != null ? Number(brandId) : null },
});
},
/**
* PUT /api/article/:sku/eans
* Body: { eans: string[] } — replace-all. Empty array clears all.
* EANs must be non-empty unique strings → 400 Invalid eans.
* → 200 { article } (same shape as GET by SKU)
*/
setEansBySku(sku, eans) {
return http(`/api/article/${encodeURIComponent(sku)}/eans`, {
method: 'PUT',
body: { eans },
});
},
/**
* PUT /api/article/:sku/categories
* Body: { categoryIds: number[] } — replace-all. Empty array clears all.
* Categories must belong to the article's company; ids must be unique.
* → 200 { article } (same shape as GET by SKU)
*/
setCategories(sku, categoryIds) {
return http(`/api/article/${encodeURIComponent(sku)}/categories`, {
method: 'PUT',
body: { categoryIds },
});
},
/**
* GET /api/article/:sku/supplier → { suppliers: ArticleSupplier[] }
*/
getSuppliers(sku) {
return http(`/api/article/${encodeURIComponent(sku)}/supplier`);
},
/**
* POST /api/article/:sku/supplier
* Body: { entityId, sku?, pu?, stock?, sort?, netPrice?, taxId?, taxRate? }
* → 201 { supplier: ArticleSupplier }
*/
addSupplier(sku, body) {
const payload = {
...body,
entityId: Number(body?.entityId),
};
return http(`/api/article/${encodeURIComponent(sku)}/supplier`, {
method: 'POST',
body: payload,
});
},
/**
* PATCH /api/article/:sku/supplier/:id
* Body: { sku?, pu?, stock?, sort?, netPrice?, taxId?, taxRate? }
* → 200 { supplier: ArticleSupplier }
*/
updateSupplier(sku, id, body) {
return http(
`/api/article/${encodeURIComponent(sku)}/supplier/${encodeURIComponent(id)}`,
{
method: 'PATCH',
body,
},
);
},
/**
* PUT /api/article/:sku/supplier/order
* Body: { ids: number[] }
* → 200 { ids: number[] }
*/
orderSuppliers(sku, ids) {
return http(`/api/article/${encodeURIComponent(sku)}/supplier/order`, {
method: 'PUT',
body: { ids },
});
},
/**
* DELETE /api/article/:sku/supplier/:id → 204
*/
deleteSupplier(sku, id) {
return http(
`/api/article/${encodeURIComponent(sku)}/supplier/${encodeURIComponent(id)}`,
{
method: 'DELETE',
},
);
},
/**
* GET /api/article/export.zip → application/zip (binary download)
* `artikel.zip` = artikel.csv (semicolon-separated, Windows-1252) +
* one `<fullHash>.avif` per article image (content-addressed, deduplicated).
* Optional `categoryId` restricts the export to that category and below.
* Errors: 400 No company selected, 401, 403, 404 (JSON { error }).
*/
exportZip(categoryId) {
const params = new URLSearchParams();
if (categoryId != null) params.set('categoryId', String(categoryId));
const qs = params.toString();
return httpGetBinary(`/api/article/export.zip${qs ? `?${qs}` : ''}`);
},
/**
* POST /api/article/:sku/print
* Body: { printerId, templateId, quantity }
* Prints the given template to the specified printer for this article.
*/
printLabel(sku, body) {
return http(`/api/article/${encodeURIComponent(sku)}/print`, {
method: 'POST',
body,
});
},
/**
* GET /api/article/:sku/attributevalue → { attributevalues: ArticleAttributeValue[] }
*/
getAttributeValues(sku) {
return http(`/api/article/${encodeURIComponent(sku)}/attributevalue`);
},
/**
* PUT /api/article/:sku/attributevalue
* Body: { attributevalues: number[] | { attributeValueId: number, sort?: number }[] }
* → 200 { attributevalues: ArticleAttributeValue[] }
*/
setAttributeValues(sku, attributevalues) {
return http(`/api/article/${encodeURIComponent(sku)}/attributevalue`, {
method: 'PUT',
body: { attributevalues },
});
},
/**
* GET /api/article/:sku/property → { properties: ArticleProperty[] }
*/
getProperties(sku) {
return http(`/api/article/${encodeURIComponent(sku)}/property`);
},
/**
* PUT /api/article/:sku/property
* Body: { properties: { propertyId: number, value: string, sort?: number }[] }
* → 200 { properties: ArticleProperty[] }
*/
setProperties(sku, properties) {
return http(`/api/article/${encodeURIComponent(sku)}/property`, {
method: 'PUT',
body: { properties },
});
},
/**
* DELETE /api/article/:sku/property/:propertyId → 200 { ok: true }
*/
deleteProperty(sku, propertyId) {
return http(
`/api/article/${encodeURIComponent(sku)}/property/${encodeURIComponent(propertyId)}`,
{ method: 'DELETE' }
);
},
/**
* POST /api/article/:sku/parent
* Body: { isParent: boolean, attributeNameId?: number }
*/
toggleParent(sku, body) {
return http(`/api/article/${encodeURIComponent(sku)}/parent`, {
method: 'POST',
body,
});
},
/**
* GET /api/article/:sku/children → { children: [...] }
*/
getChildren(sku) {
return http(`/api/article/${encodeURIComponent(sku)}/children`);
},
/**
* POST /api/article/:sku/children
* Body: { attributeValueId?: number, value?: string, name?: string }
*/
addChild(sku, body) {
return http(`/api/article/${encodeURIComponent(sku)}/children`, {
method: 'POST',
body,
});
},
/**
* DELETE /api/article/:sku/children/:childSku
*/
removeChild(sku, childSku, options = {}) {
const qs = options?.deleteArticle ? '?deleteArticle=true' : '';
return http(
`/api/article/${encodeURIComponent(sku)}/children/${encodeURIComponent(childSku)}${qs}`,
{ method: 'DELETE' }
);
},
/**
* GET /api/article/:sku/articlelist → { items: [...] }
*/
getArticlelist(sku) {
return http(`/api/article/${encodeURIComponent(sku)}/articlelist`);
},
/**
* PUT /api/article/:sku/articlelist
* Body: { items: [{ articleId, sku, count, sort }] }
*/
setArticlelist(sku, items) {
return http(`/api/article/${encodeURIComponent(sku)}/articlelist`, {
method: 'PUT',
body: Array.isArray(items) ? { items } : items,
});
},
/**
* GET /api/article/:sku/optionslist → { options: [...] }
*/
getOptionslist(sku) {
return http(`/api/article/${encodeURIComponent(sku)}/optionslist`);
},
/**
* PUT /api/article/:sku/optionslist
* Body: { options: [{ articleId, sku, count, sort, mandated }] }
*/
setOptionslist(sku, options) {
return http(`/api/article/${encodeURIComponent(sku)}/optionslist`, {
method: 'PUT',
body: Array.isArray(options) ? { options } : options,
});
},
};