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 }` |
|
| `createUnderParent` | `({ googleAuthId, parentId, name })` | `{ id, parentId, name, sku }` |
|
||||||
| `getBySku` | `(googleAuthId, sku)` | detail object (see below) |
|
| `getBySku` | `(googleAuthId, sku)` | detail object (see below) |
|
||||||
| `updateBySku` | `(googleAuthId, sku, { name })` | same shape as `getBySku` |
|
| `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) |
|
| `addImage` | `(googleAuthId, sku, imageBuffer)` | image result (see below) |
|
||||||
| `deleteImage` | `(googleAuthId, sku, fullHash)` | `void` |
|
| `deleteImage` | `(googleAuthId, sku, fullHash)` | `void` |
|
||||||
| `orderImages` | `(googleAuthId, sku, { fullHashes })` | `{ fullHashes }` |
|
| `orderImages` | `(googleAuthId, sku, { fullHashes })` | `{ fullHashes }` |
|
||||||
@@ -164,7 +165,7 @@ Backend mapping:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`getBySku` / `updateBySku` return:
|
`getBySku` / `updateBySku` / `setPricesBySku` return:
|
||||||
|
|
||||||
```js
|
```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`.
|
`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`.
|
`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`.
|
`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 })` |
|
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
||||||
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
||||||
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name })` |
|
| `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)` |
|
| `DELETE /api/article/:sku` | `article.delete(googleAuthId, sku)` |
|
||||||
| `POST /api/article/:sku/image` | `article.addImage(googleAuthId, sku, imageBuffer)` |
|
| `POST /api/article/:sku/image` | `article.addImage(googleAuthId, sku, imageBuffer)` |
|
||||||
| `DELETE /api/article/:sku/image/:fullHash` | `article.deleteImage(googleAuthId, sku, fullHash)` |
|
| `DELETE /api/article/:sku/image/:fullHash` | `article.deleteImage(googleAuthId, sku, fullHash)` |
|
||||||
|
|||||||
@@ -120,6 +120,11 @@ const MENU = {
|
|||||||
label: 'updateBySku',
|
label: 'updateBySku',
|
||||||
hint: 'googleAuthId, sku, name → row',
|
hint: 'googleAuthId, sku, name → row',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'article.setPricesBySku',
|
||||||
|
label: 'setPricesBySku',
|
||||||
|
hint: 'googleAuthId, sku, JSON prices → row',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'article.addImage',
|
value: 'article.addImage',
|
||||||
label: 'addImage',
|
label: 'addImage',
|
||||||
@@ -456,6 +461,35 @@ async function runMethod(client, action) {
|
|||||||
await client.article.updateBySku(Number(googleAuthId), sku, { name }),
|
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': {
|
case 'article.addImage': {
|
||||||
const googleAuthId = await askInt('google_auth id');
|
const googleAuthId = await askInt('google_auth id');
|
||||||
if (googleAuthId == null) return;
|
if (googleAuthId == null) return;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import articleAddImage from './store/article/addImage.js';
|
|||||||
import articleCreateUnderParent from './store/article/createUnderParent.js';
|
import articleCreateUnderParent from './store/article/createUnderParent.js';
|
||||||
import articleGetBySku from './store/article/getBySku.js';
|
import articleGetBySku from './store/article/getBySku.js';
|
||||||
import articleUpdateBySku from './store/article/updateBySku.js';
|
import articleUpdateBySku from './store/article/updateBySku.js';
|
||||||
|
import articleSetPricesBySku from './store/article/setPricesBySku.js';
|
||||||
import langCreate from './store/lang/create.js';
|
import langCreate from './store/lang/create.js';
|
||||||
import langDelete from './store/lang/delete.js';
|
import langDelete from './store/lang/delete.js';
|
||||||
import langList from './store/lang/list.js';
|
import langList from './store/lang/list.js';
|
||||||
@@ -63,6 +64,7 @@ export default class LibDashStore {
|
|||||||
createUnderParent: articleCreateUnderParent(pool),
|
createUnderParent: articleCreateUnderParent(pool),
|
||||||
getBySku: articleGetBySku(pool),
|
getBySku: articleGetBySku(pool),
|
||||||
updateBySku: articleUpdateBySku(pool),
|
updateBySku: articleUpdateBySku(pool),
|
||||||
|
setPricesBySku: articleSetPricesBySku(pool),
|
||||||
addImage: articleAddImage(pool),
|
addImage: articleAddImage(pool),
|
||||||
deleteImage: articleDeleteImage(pool),
|
deleteImage: articleDeleteImage(pool),
|
||||||
orderImages: articleOrderImages(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