u
This commit is contained in:
20
README.md
20
README.md
@@ -187,6 +187,7 @@ Company-scoped brands. Auth via `resolveCompanyAccess`.
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `createUnderParent` | `({ googleAuthId, parentId, name })` | `{ id, parentId, sort, name }` |
|
||||
| `rename` | `({ googleAuthId, id, name })` | `{ id, name }` |
|
||||
| `delete` | `({ googleAuthId, id })` | `void` — blocked by FK if `categoryarticle` or child categories remain |
|
||||
| `getTreeForCompany` | `(googleAuthId, companyId)` | `{ languageId, tree, articles }` |
|
||||
| `getCategoryArticles` | `(googleAuthId, categoryId)` | `{ id, sku, name, slug, net, taxRate, optionsCount, articleListCount, childCount, fatherArticleId, fatherSku, fullHash, thumbW, thumbH, thumbHash }[]` |
|
||||
@@ -200,6 +201,8 @@ Company-scoped brands. Auth via `resolveCompanyAccess`.
|
||||
|
||||
`getTreeForCompany` throws `Not authorized` if `googleAuthId` is not a `companyuser` of `companyId`, and `Company not found` if the company is missing, has no `root_category_id`, or no `default_language_id`.
|
||||
|
||||
`rename` updates the `categorytext` name (and re-slugifies `slug`) for the category in the company default language. Empty name → `Name required`. Auth via `resolveCategoryAccess` on the category (`Parent not found` / `Not authorized`). Missing text row → `Category not found`. Returns `{ id, name }`. For `PATCH /api/category/:id`.
|
||||
|
||||
---
|
||||
|
||||
## `article`
|
||||
@@ -210,6 +213,10 @@ Company-scoped brands. Auth via `resolveCompanyAccess`.
|
||||
| `getBySku` | `(googleAuthId, sku)` | detail object (see below) |
|
||||
| `updateBySku` | `(googleAuthId, sku, { name?, unitamount?, unitSi? })` | same shape as `getBySku` |
|
||||
| `setPricesBySku` | `(googleAuthId, sku, [{ price, tax, minmultiplier? }])` | same shape as `getBySku` |
|
||||
| `setEansBySku` | `(googleAuthId, sku, string[])` | same shape as `getBySku` |
|
||||
| `setBrandIdBySku` | `(googleAuthId, sku, brandId \| null)` | same shape as `getBySku` |
|
||||
| `setWeightBySku` | `(googleAuthId, sku, weight)` | same shape as `getBySku` |
|
||||
| `setCategoryIdsBySku` | `(googleAuthId, sku, number[])` | same shape as `getBySku` |
|
||||
| `addImage` | `(googleAuthId, sku, imageBuffer)` | image result (see below) |
|
||||
| `deleteImage` | `(googleAuthId, sku, fullHash)` | `void` |
|
||||
| `orderImages` | `(googleAuthId, sku, { fullHashes })` | `{ fullHashes }` |
|
||||
@@ -257,6 +264,14 @@ Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Err
|
||||
|
||||
`setPricesBySku` replaces all `price` rows for the article’s company `poscustomergroup_id` in one transaction. Each entry: `price` → `net`, `tax` → tax rate (ensured via `tax.ensure`), `minmultiplier` defaults to `1`. Empty array clears all POS prices. Returns the same object as `getBySku`. Errors: `Article not found`, `Not authorized`, `POS customer group required`, `Invalid prices`.
|
||||
|
||||
`setEansBySku` replaces all `articleean` rows for the article in one transaction (`replace-all` semantics). EANs must be non-empty unique strings; empty array clears all EANs. Returns the same object as `getBySku` (whose `eans` reflects the new list). Errors: `Article not found`, `Not authorized`, `Invalid eans`.
|
||||
|
||||
`setBrandIdBySku` sets the article’s `brand_id`. Pass `null` to clear the brand. Requires the brand to belong to the article’s company (else `Brand not found`). Non-null must be a finite id. Errors: `Article not found`, `Not authorized`, `Brand not found`.
|
||||
|
||||
`setWeightBySku` sets the article’s `weight`. Must be finite and `>= 0` (else `Invalid weight`). Errors: `Article not found`, `Not authorized`, `Invalid weight`.
|
||||
|
||||
`setCategoryIdsBySku` replaces the article’s category list (`categoryarticle`) in one transaction. Each category must belong to the article’s company and ids must be unique (else `Invalid categoryIds`); empty array removes the article from all categories. Returns the same object as `getBySku` (whose `categoryIds` reflects the new list). Errors: `Article not found`, `Not authorized`, `Invalid categoryIds`.
|
||||
|
||||
`deleteImage` removes `articlepic` and the `pic` row for this article (`fullHash`). Errors: `Article not found`, `Image not found`, `Not authorized`.
|
||||
|
||||
`orderImages` sets `articlepic.sort` to `0..n-1` in the given order. `fullHashes` must list every image of the article exactly once. Errors: `Article not found`, `Invalid image order`, `Not authorized`.
|
||||
@@ -301,6 +316,7 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `PUT /api/auth/selected-company` | `google_auth.setSelectedCompany(googleAuthId, companyId)` |
|
||||
| `GET /api/category/tree?companyId=` | `category.getTreeForCompany(googleAuthId, companyId)` |
|
||||
| `POST /api/category` | `category.createUnderParent({ googleAuthId, parentId, name })` |
|
||||
| `PATCH /api/category/:id` | `category.rename({ googleAuthId, id, name })` |
|
||||
| `DELETE /api/category/:id` | `category.delete({ googleAuthId, id })` |
|
||||
| `GET /api/unit?companyId=` | `unit.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/unit` | `unit.upsert(googleAuthId, companyId, { si, basesi, basesifactor, symbol, name })` |
|
||||
@@ -315,6 +331,10 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
||||
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
|
||||
| `PUT /api/article/:sku/prices` | `article.setPricesBySku(googleAuthId, sku, [{ price, tax, minmultiplier? }])` |
|
||||
| `PUT /api/article/:sku/eans` | `article.setEansBySku(googleAuthId, sku, string[])` |
|
||||
| `PUT /api/article/:sku/brand` | `article.setBrandIdBySku(googleAuthId, sku, brandId \| null)` |
|
||||
| `PUT /api/article/:sku/weight` | `article.setWeightBySku(googleAuthId, sku, weight)` |
|
||||
| `PUT /api/article/:sku/categories` | `article.setCategoryIdsBySku(googleAuthId, sku, number[])` |
|
||||
| `DELETE /api/article/:sku` | `article.delete(googleAuthId, sku)` |
|
||||
| `POST /api/article/:sku/image` | `article.addImage(googleAuthId, sku, imageBuffer)` |
|
||||
| `DELETE /api/article/:sku/image/:fullHash` | `article.deleteImage(googleAuthId, sku, fullHash)` |
|
||||
|
||||
@@ -162,6 +162,7 @@ const MENU = {
|
||||
label: 'category',
|
||||
items: [
|
||||
{ value: 'category.createUnderParent', label: 'createUnderParent', hint: 'googleAuthId, parentId, name → row' },
|
||||
{ value: 'category.rename', label: 'rename', hint: 'googleAuthId, id, name → row' },
|
||||
{ value: 'category.delete', label: 'delete', hint: 'googleAuthId, id' },
|
||||
{ value: 'category.getTreeForCompany', label: 'getTreeForCompany', hint: 'googleAuthId, companyId → rows' },
|
||||
{ value: 'category.getCategoryArticles', label: 'getCategoryArticles', hint: 'googleAuthId, categoryId → rows' },
|
||||
@@ -190,6 +191,26 @@ const MENU = {
|
||||
label: 'setPricesBySku',
|
||||
hint: 'googleAuthId, sku, JSON prices → row',
|
||||
},
|
||||
{
|
||||
value: 'article.setEansBySku',
|
||||
label: 'setEansBySku',
|
||||
hint: 'googleAuthId, sku, JSON eans → row',
|
||||
},
|
||||
{
|
||||
value: 'article.setBrandIdBySku',
|
||||
label: 'setBrandIdBySku',
|
||||
hint: 'googleAuthId, sku, brandId|null → row',
|
||||
},
|
||||
{
|
||||
value: 'article.setWeightBySku',
|
||||
label: 'setWeightBySku',
|
||||
hint: 'googleAuthId, sku, weight → row',
|
||||
},
|
||||
{
|
||||
value: 'article.setCategoryIdsBySku',
|
||||
label: 'setCategoryIdsBySku',
|
||||
hint: 'googleAuthId, sku, JSON categoryIds → row',
|
||||
},
|
||||
{
|
||||
value: 'article.addImage',
|
||||
label: 'addImage',
|
||||
@@ -631,6 +652,24 @@ async function runMethod(client, action) {
|
||||
}),
|
||||
];
|
||||
}
|
||||
case 'category.rename': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const id = await askInt('Category id');
|
||||
if (id == null) return;
|
||||
const name = await askText('name', {
|
||||
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||
});
|
||||
if (name == null) return;
|
||||
return [
|
||||
'row',
|
||||
await client.category.rename({
|
||||
googleAuthId: Number(googleAuthId),
|
||||
id: Number(id),
|
||||
name,
|
||||
}),
|
||||
];
|
||||
}
|
||||
case 'category.delete': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
@@ -755,6 +794,108 @@ async function runMethod(client, action) {
|
||||
),
|
||||
];
|
||||
}
|
||||
case 'article.setEansBySku': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const sku = await askText('sku', {
|
||||
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||
});
|
||||
if (sku == null) return;
|
||||
const eansJson = await askText('eans JSON', {
|
||||
placeholder: '["4006381333931","4012345678901"]',
|
||||
validate: (v) => {
|
||||
if (!v?.trim()) return 'Required';
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
if (!Array.isArray(parsed)) return 'Must be a JSON array';
|
||||
} catch {
|
||||
return 'Invalid JSON';
|
||||
}
|
||||
},
|
||||
});
|
||||
if (eansJson == null) return;
|
||||
return [
|
||||
'result',
|
||||
await client.article.setEansBySku(
|
||||
Number(googleAuthId),
|
||||
sku,
|
||||
JSON.parse(eansJson),
|
||||
),
|
||||
];
|
||||
}
|
||||
case 'article.setBrandIdBySku': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const sku = await askText('sku', {
|
||||
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||
});
|
||||
if (sku == null) return;
|
||||
const raw = await askText('brandId (or "null")', {
|
||||
validate: (v) => {
|
||||
if (v === null || v === undefined || v.trim() === '') return 'Required';
|
||||
const t = v.trim();
|
||||
if (t === 'null') return undefined;
|
||||
if (!Number.isFinite(Number(t))) return 'Must be a number or null';
|
||||
},
|
||||
});
|
||||
if (raw == null) return;
|
||||
const brandId = raw.trim() === 'null' ? null : Number(raw.trim());
|
||||
return [
|
||||
'result',
|
||||
await client.article.setBrandIdBySku(
|
||||
Number(googleAuthId),
|
||||
sku,
|
||||
brandId,
|
||||
),
|
||||
];
|
||||
}
|
||||
case 'article.setWeightBySku': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const sku = await askText('sku', {
|
||||
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||
});
|
||||
if (sku == null) return;
|
||||
const weight = await askInt('weight');
|
||||
if (weight == null) return;
|
||||
return [
|
||||
'result',
|
||||
await client.article.setWeightBySku(
|
||||
Number(googleAuthId),
|
||||
sku,
|
||||
weight,
|
||||
),
|
||||
];
|
||||
}
|
||||
case 'article.setCategoryIdsBySku': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const sku = await askText('sku', {
|
||||
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||
});
|
||||
if (sku == null) return;
|
||||
const categoryIdsJson = await askText('categoryIds JSON', {
|
||||
placeholder: '[10,23,42]',
|
||||
validate: (v) => {
|
||||
if (!v?.trim()) return 'Required';
|
||||
try {
|
||||
const parsed = JSON.parse(v);
|
||||
if (!Array.isArray(parsed)) return 'Must be a JSON array';
|
||||
} catch {
|
||||
return 'Invalid JSON';
|
||||
}
|
||||
},
|
||||
});
|
||||
if (categoryIdsJson == null) return;
|
||||
return [
|
||||
'result',
|
||||
await client.article.setCategoryIdsBySku(
|
||||
Number(googleAuthId),
|
||||
sku,
|
||||
JSON.parse(categoryIdsJson),
|
||||
),
|
||||
];
|
||||
}
|
||||
case 'article.addImage': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
|
||||
10
src/index.js
10
src/index.js
@@ -8,6 +8,7 @@ import companyList from './store/company/list.js';
|
||||
import categoryGetTreeForCompany from './store/category/getTreeForCompany.js';
|
||||
import categoryGetCategoryArticles from './store/category/getCategoryArticles.js';
|
||||
import categoryCreateUnderParent from './store/category/createUnderParent.js';
|
||||
import categoryRename from './store/category/rename.js';
|
||||
import categoryDelete from './store/category/delete.js';
|
||||
import articleDelete from './store/article/delete.js';
|
||||
import articleDeleteImage from './store/article/deleteImage.js';
|
||||
@@ -17,6 +18,10 @@ import articleCreateUnderParent from './store/article/createUnderParent.js';
|
||||
import articleGetBySku from './store/article/getBySku.js';
|
||||
import articleUpdateBySku from './store/article/updateBySku.js';
|
||||
import articleSetPricesBySku from './store/article/setPricesBySku.js';
|
||||
import articleSetEansBySku from './store/article/setEansBySku.js';
|
||||
import articleSetBrandIdBySku from './store/article/setBrandIdBySku.js';
|
||||
import articleSetWeightBySku from './store/article/setWeightBySku.js';
|
||||
import articleSetCategoryIdsBySku from './store/article/setCategoryIdsBySku.js';
|
||||
import unitList from './store/unit/list.js';
|
||||
import unitUpsert from './store/unit/upsert.js';
|
||||
import unitDelete from './store/unit/delete.js';
|
||||
@@ -67,6 +72,7 @@ export default class LibDashStore {
|
||||
getTreeForCompany: categoryGetTreeForCompany(pool),
|
||||
getCategoryArticles: categoryGetCategoryArticles(pool),
|
||||
createUnderParent: categoryCreateUnderParent(pool),
|
||||
rename: categoryRename(pool),
|
||||
delete: categoryDelete(pool),
|
||||
};
|
||||
|
||||
@@ -75,6 +81,10 @@ export default class LibDashStore {
|
||||
getBySku: articleGetBySku(pool),
|
||||
updateBySku: articleUpdateBySku(pool),
|
||||
setPricesBySku: articleSetPricesBySku(pool),
|
||||
setEansBySku: articleSetEansBySku(pool),
|
||||
setBrandIdBySku: articleSetBrandIdBySku(pool),
|
||||
setWeightBySku: articleSetWeightBySku(pool),
|
||||
setCategoryIdsBySku: articleSetCategoryIdsBySku(pool),
|
||||
addImage: articleAddImage(pool),
|
||||
deleteImage: articleDeleteImage(pool),
|
||||
orderImages: articleOrderImages(pool),
|
||||
|
||||
@@ -98,6 +98,18 @@ LEFT JOIN companyuser cu
|
||||
ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId
|
||||
`;
|
||||
|
||||
export const UPDATE_WEIGHT = `
|
||||
UPDATE article
|
||||
SET weight = @weight
|
||||
WHERE id = @articleId
|
||||
`;
|
||||
|
||||
export const UPDATE_BRAND = `
|
||||
UPDATE article
|
||||
SET brand_id = @brandId
|
||||
WHERE id = @articleId
|
||||
`;
|
||||
|
||||
export const UPDATE_UNIT = `
|
||||
UPDATE article
|
||||
SET unit_id = @unitId
|
||||
@@ -121,3 +133,29 @@ INSERT INTO article (sku, unit_id, unitamount, weight)
|
||||
OUTPUT INSERTED.id
|
||||
VALUES (@sku, @unitId, @unitamount, @weight)
|
||||
`;
|
||||
|
||||
export const DELETE_EANS_BY_ARTICLE = `
|
||||
DELETE FROM articleean
|
||||
WHERE article_id = @articleId
|
||||
`;
|
||||
|
||||
export const ADD_EAN = `
|
||||
INSERT INTO articleean (article_id, ean)
|
||||
VALUES (@articleId, @ean)
|
||||
`;
|
||||
|
||||
export const DELETE_CATEGORIES_BY_ARTICLE = `
|
||||
DELETE FROM categoryarticle
|
||||
WHERE article_id = @articleId
|
||||
`;
|
||||
|
||||
export const ADD_CATEGORY = `
|
||||
INSERT INTO categoryarticle (category_id, article_id)
|
||||
VALUES (@categoryId, @articleId)
|
||||
`;
|
||||
|
||||
export const GET_BRAND_BY_COMPANY_AND_ID = `
|
||||
SELECT id
|
||||
FROM brand
|
||||
WHERE id = @brandId AND company_id = @companyId
|
||||
`;
|
||||
|
||||
@@ -2,3 +2,9 @@ export const CREATE = `
|
||||
INSERT INTO categorytext (category_id, language_id, name, slug)
|
||||
VALUES (@categoryId, @languageId, @name, @slug)
|
||||
`;
|
||||
|
||||
export const UPDATE_BY_CATEGORY_AND_LANGUAGE = `
|
||||
UPDATE categorytext
|
||||
SET name = @name, slug = @slug
|
||||
WHERE category_id = @categoryId AND language_id = @languageId
|
||||
`;
|
||||
|
||||
52
src/store/article/setBrandIdBySku.js
Normal file
52
src/store/article/setBrandIdBySku.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
GET_BRAND_BY_COMPANY_AND_ID,
|
||||
UPDATE_BRAND,
|
||||
} from '../../sql/article.js';
|
||||
import queryBySku from './queryBySku.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
|
||||
export default function setBrandIdBySku(pool) {
|
||||
const resolve = resolveArticleBySku(pool);
|
||||
const load = queryBySku(pool);
|
||||
|
||||
return async (googleAuthId, sku, brandId) => {
|
||||
const { articleId, languageId, companyId } = await resolve(
|
||||
googleAuthId,
|
||||
sku,
|
||||
);
|
||||
|
||||
if (brandId !== null && !Number.isFinite(brandId)) {
|
||||
throw new Error('Brand not found');
|
||||
}
|
||||
|
||||
let resolvedBrandId = null;
|
||||
if (brandId !== null) {
|
||||
const brand = await pool
|
||||
.request()
|
||||
.input('brandId', brandId)
|
||||
.input('companyId', companyId)
|
||||
.query(GET_BRAND_BY_COMPANY_AND_ID);
|
||||
if (!brand.recordset[0]) {
|
||||
throw new Error('Brand not found');
|
||||
}
|
||||
resolvedBrandId = brandId;
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('articleId', articleId)
|
||||
.input('brandId', resolvedBrandId)
|
||||
.query(UPDATE_BRAND);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
const article = await load(articleId, languageId);
|
||||
if (!article) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
return article;
|
||||
};
|
||||
}
|
||||
74
src/store/article/setCategoryIdsBySku.js
Normal file
74
src/store/article/setCategoryIdsBySku.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
ADD_CATEGORY,
|
||||
DELETE_CATEGORIES_BY_ARTICLE,
|
||||
} from '../../sql/article.js';
|
||||
import request from '../request.js';
|
||||
import resolveCategoryAccess from '../category/resolveCategoryAccess.js';
|
||||
import queryBySku from './queryBySku.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
|
||||
export default function setCategoryIdsBySku(pool) {
|
||||
const resolve = resolveArticleBySku(pool);
|
||||
const categoryAccess = resolveCategoryAccess(pool);
|
||||
const load = queryBySku(pool);
|
||||
|
||||
return async (googleAuthId, sku, categoryIds) => {
|
||||
if (!Array.isArray(categoryIds)) {
|
||||
throw new Error('Invalid categoryIds');
|
||||
}
|
||||
const seen = new Set();
|
||||
const normalized = [];
|
||||
for (const categoryId of categoryIds) {
|
||||
if (!Number.isFinite(categoryId) || seen.has(categoryId)) {
|
||||
throw new Error('Invalid categoryIds');
|
||||
}
|
||||
seen.add(categoryId);
|
||||
normalized.push(categoryId);
|
||||
}
|
||||
|
||||
const { articleId, languageId, companyId } = await resolve(
|
||||
googleAuthId,
|
||||
sku,
|
||||
);
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
for (const categoryId of normalized) {
|
||||
// Ensures the category belongs to the same company as the article.
|
||||
const access = await categoryAccess(googleAuthId, categoryId);
|
||||
if (access.companyId !== companyId) {
|
||||
throw new Error('Invalid categoryIds');
|
||||
}
|
||||
}
|
||||
|
||||
await request(pool, tx)
|
||||
.input('articleId', articleId)
|
||||
.query(DELETE_CATEGORIES_BY_ARTICLE);
|
||||
|
||||
for (const categoryId of normalized) {
|
||||
await request(pool, tx)
|
||||
.input('categoryId', categoryId)
|
||||
.input('articleId', articleId)
|
||||
.query(ADD_CATEGORY);
|
||||
}
|
||||
|
||||
await tx.commit();
|
||||
} catch (err) {
|
||||
try {
|
||||
await tx.rollback();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const article = await load(articleId, languageId);
|
||||
if (!article) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
return article;
|
||||
};
|
||||
}
|
||||
65
src/store/article/setEansBySku.js
Normal file
65
src/store/article/setEansBySku.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
ADD_EAN,
|
||||
DELETE_EANS_BY_ARTICLE,
|
||||
} from '../../sql/article.js';
|
||||
import request from '../request.js';
|
||||
import queryBySku from './queryBySku.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
|
||||
export default function setEansBySku(pool) {
|
||||
const resolve = resolveArticleBySku(pool);
|
||||
const load = queryBySku(pool);
|
||||
|
||||
return async (googleAuthId, sku, eans) => {
|
||||
if (!Array.isArray(eans)) {
|
||||
throw new Error('Invalid eans');
|
||||
}
|
||||
const normalized = [];
|
||||
const seen = new Set();
|
||||
for (const ean of eans) {
|
||||
if (typeof ean !== 'string' || !ean.trim()) {
|
||||
throw new Error('Invalid eans');
|
||||
}
|
||||
const clean = ean.trim();
|
||||
if (seen.has(clean)) {
|
||||
throw new Error('Invalid eans');
|
||||
}
|
||||
seen.add(clean);
|
||||
normalized.push(clean);
|
||||
}
|
||||
|
||||
const { articleId, languageId } = await resolve(googleAuthId, sku);
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
await request(pool, tx)
|
||||
.input('articleId', articleId)
|
||||
.query(DELETE_EANS_BY_ARTICLE);
|
||||
|
||||
for (const ean of normalized) {
|
||||
await request(pool, tx)
|
||||
.input('articleId', articleId)
|
||||
.input('ean', ean)
|
||||
.query(ADD_EAN);
|
||||
}
|
||||
|
||||
await tx.commit();
|
||||
} catch (err) {
|
||||
try {
|
||||
await tx.rollback();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const article = await load(articleId, languageId);
|
||||
if (!article) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
return article;
|
||||
};
|
||||
}
|
||||
33
src/store/article/setWeightBySku.js
Normal file
33
src/store/article/setWeightBySku.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { UPDATE_WEIGHT } from '../../sql/article.js';
|
||||
import queryBySku from './queryBySku.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
|
||||
export default function setWeightBySku(pool) {
|
||||
const resolve = resolveArticleBySku(pool);
|
||||
const load = queryBySku(pool);
|
||||
|
||||
return async (googleAuthId, sku, weight) => {
|
||||
if (!Number.isFinite(weight) || weight < 0) {
|
||||
throw new Error('Invalid weight');
|
||||
}
|
||||
|
||||
const { articleId, languageId } = await resolve(googleAuthId, sku);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('articleId', articleId)
|
||||
.input('weight', weight)
|
||||
.query(UPDATE_WEIGHT);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
const article = await load(articleId, languageId);
|
||||
if (!article) {
|
||||
throw new Error('Article not found');
|
||||
}
|
||||
|
||||
return article;
|
||||
};
|
||||
}
|
||||
41
src/store/category/rename.js
Normal file
41
src/store/category/rename.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { UPDATE_BY_CATEGORY_AND_LANGUAGE } from '../../sql/categorytext.js';
|
||||
import resolveCategoryAccess from './resolveCategoryAccess.js';
|
||||
|
||||
function slugify(name) {
|
||||
return name
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/\p{M}/gu, '')
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
export default function rename(pool) {
|
||||
const access = resolveCategoryAccess(pool);
|
||||
|
||||
return async ({ googleAuthId, id, name }) => {
|
||||
if (!name?.trim()) {
|
||||
throw new Error('Name required');
|
||||
}
|
||||
const cleaned = name.trim();
|
||||
|
||||
const { languageId } = await access(googleAuthId, id);
|
||||
|
||||
const slug = slugify(cleaned);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('categoryId', id)
|
||||
.input('languageId', languageId)
|
||||
.input('name', cleaned)
|
||||
.input('slug', slug)
|
||||
.query(UPDATE_BY_CATEGORY_AND_LANGUAGE);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Category not found');
|
||||
}
|
||||
|
||||
return { id, name: cleaned };
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user