Add article.setPricesBySku to replace POS prices for a SKU.
Gives dashback a single store call that atomically replaces all price rows for the article’s poscustomergroup.
This commit is contained in:
@@ -144,6 +144,7 @@ Backend mapping:
|
||||
| `createUnderParent` | `({ googleAuthId, parentId, name })` | `{ id, parentId, name, sku }` |
|
||||
| `getBySku` | `(googleAuthId, sku)` | detail object (see below) |
|
||||
| `updateBySku` | `(googleAuthId, sku, { name })` | same shape as `getBySku` |
|
||||
| `setPricesBySku` | `(googleAuthId, sku, [{ price, tax, minmultiplier? }])` | same shape as `getBySku` |
|
||||
| `addImage` | `(googleAuthId, sku, imageBuffer)` | image result (see below) |
|
||||
| `deleteImage` | `(googleAuthId, sku, fullHash)` | `void` |
|
||||
| `orderImages` | `(googleAuthId, sku, { fullHashes })` | `{ fullHashes }` |
|
||||
@@ -164,7 +165,7 @@ Backend mapping:
|
||||
}
|
||||
```
|
||||
|
||||
`getBySku` / `updateBySku` return:
|
||||
`getBySku` / `updateBySku` / `setPricesBySku` return:
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -189,6 +190,8 @@ Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Err
|
||||
|
||||
`updateBySku` updates `articletext` (company default language) in a transaction (`name` + slugified `slug`), then returns the same object as `getBySku`. Errors: `Article not found`, `Not authorized`, `Name required`.
|
||||
|
||||
`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`.
|
||||
|
||||
`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`.
|
||||
@@ -237,6 +240,7 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
||||
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
||||
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name })` |
|
||||
| `PUT /api/article/:sku/prices` | `article.setPricesBySku(googleAuthId, sku, [{ price, tax, minmultiplier? }])` |
|
||||
| `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)` |
|
||||
|
||||
@@ -120,6 +120,11 @@ const MENU = {
|
||||
label: 'updateBySku',
|
||||
hint: 'googleAuthId, sku, name → row',
|
||||
},
|
||||
{
|
||||
value: 'article.setPricesBySku',
|
||||
label: 'setPricesBySku',
|
||||
hint: 'googleAuthId, sku, JSON prices → row',
|
||||
},
|
||||
{
|
||||
value: 'article.addImage',
|
||||
label: 'addImage',
|
||||
@@ -456,6 +461,35 @@ async function runMethod(client, action) {
|
||||
await client.article.updateBySku(Number(googleAuthId), sku, { name }),
|
||||
];
|
||||
}
|
||||
case 'article.setPricesBySku': {
|
||||
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 pricesJson = await askText('prices JSON', {
|
||||
placeholder: '[{"price":9.99,"tax":19}]',
|
||||
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 (pricesJson == null) return;
|
||||
return [
|
||||
'result',
|
||||
await client.article.setPricesBySku(
|
||||
Number(googleAuthId),
|
||||
sku,
|
||||
JSON.parse(pricesJson),
|
||||
),
|
||||
];
|
||||
}
|
||||
case 'article.addImage': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
|
||||
@@ -16,6 +16,7 @@ import articleAddImage from './store/article/addImage.js';
|
||||
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 langCreate from './store/lang/create.js';
|
||||
import langDelete from './store/lang/delete.js';
|
||||
import langList from './store/lang/list.js';
|
||||
@@ -63,6 +64,7 @@ export default class LibDashStore {
|
||||
createUnderParent: articleCreateUnderParent(pool),
|
||||
getBySku: articleGetBySku(pool),
|
||||
updateBySku: articleUpdateBySku(pool),
|
||||
setPricesBySku: articleSetPricesBySku(pool),
|
||||
addImage: articleAddImage(pool),
|
||||
deleteImage: articleDeleteImage(pool),
|
||||
orderImages: articleOrderImages(pool),
|
||||
|
||||
11
src/sql/price.js
Normal file
11
src/sql/price.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const DELETE_BY_ARTICLE_AND_CUSTOMERGROUP = `
|
||||
DELETE FROM price
|
||||
WHERE article_id = @articleId
|
||||
AND customergroup_id = @customerGroupId
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO price (tax_id, article_id, minmultiplier, customergroup_id, net)
|
||||
OUTPUT INSERTED.id
|
||||
VALUES (@taxId, @articleId, @minmultiplier, @customerGroupId, @net)
|
||||
`;
|
||||
104
src/store/article/setPricesBySku.js
Normal file
104
src/store/article/setPricesBySku.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
CREATE as CREATE_PRICE,
|
||||
DELETE_BY_ARTICLE_AND_CUSTOMERGROUP,
|
||||
} from '../../sql/price.js';
|
||||
import request from '../request.js';
|
||||
import taxEnsure from '../tax/ensure.js';
|
||||
import queryBySku from './queryBySku.js';
|
||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||
|
||||
function normalizePrices(prices) {
|
||||
if (!Array.isArray(prices)) {
|
||||
throw new Error('Invalid prices');
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
const normalized = [];
|
||||
|
||||
for (const entry of prices) {
|
||||
const price = entry?.price;
|
||||
const tax = entry?.tax;
|
||||
const minmultiplier =
|
||||
entry?.minmultiplier === undefined || entry?.minmultiplier === null
|
||||
? 1
|
||||
: entry.minmultiplier;
|
||||
|
||||
if (!Number.isFinite(price) || !Number.isFinite(tax) || !Number.isFinite(minmultiplier)) {
|
||||
throw new Error('Invalid prices');
|
||||
}
|
||||
if (minmultiplier <= 0) {
|
||||
throw new Error('Invalid prices');
|
||||
}
|
||||
|
||||
const key = String(minmultiplier);
|
||||
if (seen.has(key)) {
|
||||
throw new Error('Invalid prices');
|
||||
}
|
||||
seen.add(key);
|
||||
|
||||
normalized.push({ price, tax, minmultiplier });
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export default function setPricesBySku(pool) {
|
||||
const resolve = resolveArticleBySku(pool);
|
||||
const ensureTax = taxEnsure(pool);
|
||||
const load = queryBySku(pool);
|
||||
|
||||
return async (googleAuthId, sku, prices) => {
|
||||
const normalized = normalizePrices(prices);
|
||||
|
||||
const { articleId, languageId, posCustomerGroupId } = await resolve(
|
||||
googleAuthId,
|
||||
sku,
|
||||
);
|
||||
if (!posCustomerGroupId) {
|
||||
throw new Error('POS customer group required');
|
||||
}
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
const taxIds = new Map();
|
||||
for (const entry of normalized) {
|
||||
if (!taxIds.has(entry.tax)) {
|
||||
taxIds.set(entry.tax, await ensureTax(entry.tax, tx));
|
||||
}
|
||||
}
|
||||
|
||||
await request(pool, tx)
|
||||
.input('articleId', articleId)
|
||||
.input('customerGroupId', posCustomerGroupId)
|
||||
.query(DELETE_BY_ARTICLE_AND_CUSTOMERGROUP);
|
||||
|
||||
for (const entry of normalized) {
|
||||
await request(pool, tx)
|
||||
.input('taxId', taxIds.get(entry.tax))
|
||||
.input('articleId', articleId)
|
||||
.input('minmultiplier', entry.minmultiplier)
|
||||
.input('customerGroupId', posCustomerGroupId)
|
||||
.input('net', entry.price)
|
||||
.query(CREATE_PRICE);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user