402 lines
12 KiB
JavaScript
402 lines
12 KiB
JavaScript
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?offset=0&limit=50&search=...&seekMonths=-1
|
||
* @param {{ offset?: number, limit?: number, search?: string, seekMonths?: number, sortModel?: Array, filterModel?: object }} [params]
|
||
* → { history: ArticleHistoryEntry[], total: number, unfilteredTotal?: number }
|
||
*/
|
||
// #region GET /api/article/history — getHistory
|
||
getHistory({ offset = 0, limit = 50, search, seekMonths, sortModel, filterModel } = {}) {
|
||
const params = new URLSearchParams();
|
||
params.set('offset', String(Math.max(0, Math.floor(Number(offset) || 0))));
|
||
params.set('limit', String(Math.max(1, Math.floor(Number(limit) || 50))));
|
||
if (search && String(search).trim()) params.set('search', String(search).trim());
|
||
if (seekMonths != null && Number(seekMonths) < 0) {
|
||
params.set('seekMonths', String(Math.floor(Number(seekMonths))));
|
||
}
|
||
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}` : ''}`);
|
||
},
|
||
// #endregion
|
||
|
||
/**
|
||
* 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 { sku } */
|
||
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
|
||
* → 204 (no body; refetch via getBySku)
|
||
*/
|
||
update(sku, body) {
|
||
return http(`/api/article/${encodeURIComponent(sku)}`, {
|
||
method: 'PATCH',
|
||
body,
|
||
});
|
||
},
|
||
|
||
/**
|
||
* PUT /api/article/:sku/track_stock
|
||
* Body: { track_stock: boolean }
|
||
* → 204 (no body)
|
||
*/
|
||
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.
|
||
* → 204 (no body)
|
||
*/
|
||
setPrices(sku, prices) {
|
||
return http(`/api/article/${encodeURIComponent(sku)}/prices`, {
|
||
method: 'PUT',
|
||
body: { prices },
|
||
});
|
||
},
|
||
|
||
/**
|
||
* PUT /api/article/:sku/weight
|
||
* Body: { weight } → 204 (no body)
|
||
*/
|
||
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.
|
||
* → 204 (no body)
|
||
*/
|
||
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.
|
||
* → 204 (no body)
|
||
*/
|
||
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.
|
||
* → 204 (no body)
|
||
*/
|
||
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 } → 204 (no body)
|
||
*/
|
||
toggleParent(sku, body) {
|
||
return http(`/api/article/${encodeURIComponent(sku)}/parent`, {
|
||
method: 'POST',
|
||
body,
|
||
});
|
||
},
|
||
|
||
|
||
/**
|
||
* POST /api/article/:sku/children
|
||
* Body: { attributeValueId?: number, value?: string, name?: string }
|
||
* → 201 { sku }
|
||
*/
|
||
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' }
|
||
);
|
||
},
|
||
|
||
|
||
/**
|
||
* 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,
|
||
});
|
||
},
|
||
|
||
|
||
/**
|
||
* 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,
|
||
});
|
||
},
|
||
};
|