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 `.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, }); }, };