This commit is contained in:
seb
2026-10-07 21:23:03 +02:00
parent e02bbd65c5
commit 36dd81ee69
7 changed files with 170 additions and 91 deletions

View File

@@ -548,7 +548,7 @@ Replaces the price list of an article by its SKU.
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The article SKU. |
| `prices` | `Array<{price: number, tax: number, minmultiplier?: number}>` | The price entries (minmultiplier defaults to 1). |
| `prices` | `Array<{net?: number, gross?: number, price?: number, taxRate?: number, tax?: number, minmultiplier?: number}>` | The price entries (net or gross, taxRate or tax percentage, minmultiplier defaults to 1). |
| `[tx]` | `object` | Optional transaction to run within. |
**Returns**
@@ -562,7 +562,9 @@ Replaces the price list of an article by its SKU.
**Example**
```js
const article = await store.article.setPricesBySku(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
const article = await store.article.setPricesBySku(42, 'SKU-1', [{ net: 10.0, taxRate: 19, minmultiplier: 1 }]);
// or with gross:
const article = await store.article.setPricesBySku(42, 'SKU-1', [{ gross: 11.9, taxRate: 19 }]);
// => { id, sku, name, ... }
```

2
API.md
View File

@@ -105,7 +105,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- `setCategoryIdsBySku(googleAuthId: number, sku: string, categoryIds: Array<number>, tx?: object): Promise<object>`
- `setEansBySku(googleAuthId: number, sku: string, eans: Array<string>, tx?: object): Promise<object>`
- `setParentBySku(googleAuthId: number, sku: string, parentSku: string|null, tx?: object): Promise<object>`
- `setPricesBySku(googleAuthId: number, sku: string, prices: Array<{price: number, tax: number, minmultiplier?: number}>, tx?: object): Promise<object>`
- `setPricesBySku(googleAuthId: number, sku: string, prices: Array<{net?: number, gross?: number, price?: number, taxRate?: number, tax?: number, minmultiplier?: number}>, tx?: object): Promise<object>`
- `setTrackStockBySku(googleAuthId: number, sku: string, trackStock: boolean|number, tx?: object): Promise<object>`
- `setWeightBySku(googleAuthId: number, sku: string, weight: number, tx?: object): Promise<object>`
- `toggleParent(googleAuthId: number, sku: string, options: {isParent: boolean, attributeNameId?: number, parentAttributeNameId?: number}, tx?: object): Promise<object>`

View File

@@ -42,8 +42,17 @@ export default function addImage(provider) {
const sortResult = await runner.query(getSortSql, { articleId });
const sort = sortResult.rows[0].sort;
const fullHash = newFullHash();
const fullHash = newFullHash(processed.full.buffer);
let picId;
const existingPic = await runner.query(
'SELECT id FROM pic WHERE hash = @fullHash',
{ fullHash },
);
if (existingPic.rows[0]) {
picId = existingPic.rows[0].id;
} else {
if (isSqlite) {
if (runner.writePicFull) {
await runner.writePicFull(fullHash, processed.full.buffer);
@@ -67,17 +76,28 @@ export default function addImage(provider) {
thumbbinary: processed.thumb.buffer,
});
const picId = picResult.rows[0]?.id ?? picResult.insertId;
picId = picResult.rows[0]?.id ?? picResult.insertId;
}
const existingArticlePic = await runner.query(
'SELECT id, sort FROM articlepic WHERE article_id = @articleId AND pic_id = @picId',
{ articleId, picId },
);
let effectiveSort = sort;
if (existingArticlePic.rows[0]) {
effectiveSort = existingArticlePic.rows[0].sort;
} else {
await runner.query(addArticlePicSql, {
articleId,
picId,
sort,
});
}
return {
picId,
sort,
sort: effectiveSort,
full: {
w: processed.full.w,
h: processed.full.h,

View File

@@ -22,28 +22,37 @@ function normalizePrices(prices) {
const normalized = [];
for (const entry of prices) {
const price = entry?.price;
const tax = entry?.tax;
const rawTax = entry?.taxRate ?? entry?.tax;
const minmultiplier =
entry?.minmultiplier === undefined || entry?.minmultiplier === null
? 1
: entry.minmultiplier;
if (
typeof price !== 'number' || !Number.isFinite(price) || price < 0 ||
typeof tax !== 'number' || !Number.isFinite(tax) || tax < 0 ||
typeof rawTax !== 'number' || !Number.isFinite(rawTax) || rawTax < 0 ||
typeof minmultiplier !== 'number' || !Number.isFinite(minmultiplier) || minmultiplier <= 0
) {
throw new Error('Invalid prices');
}
let netPrice;
if (typeof entry?.net === 'number' && Number.isFinite(entry.net) && entry.net >= 0) {
netPrice = entry.net;
} else if (typeof entry?.price === 'number' && Number.isFinite(entry.price) && entry.price >= 0) {
netPrice = entry.price;
} else if (typeof entry?.gross === 'number' && Number.isFinite(entry.gross) && entry.gross >= 0) {
netPrice = +Number(entry.gross / (1 + rawTax / 100)).toFixed(4);
} else {
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 });
normalized.push({ price: netPrice, tax: rawTax, minmultiplier });
}
return normalized;
@@ -64,13 +73,15 @@ export default function setPricesBySku(provider) {
*
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {Array<{price: number, tax: number, minmultiplier?: number}>} prices - The price entries (minmultiplier defaults to 1).
* @param {Array<{net?: number, gross?: number, price?: number, taxRate?: number, tax?: number, minmultiplier?: number}>} prices - The price entries (net or gross, taxRate or tax percentage, minmultiplier defaults to 1).
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<object>} The updated article.
* @throws {Error} If the prices are invalid or the article is not found.
*
* @example
* const article = await store.article.setPricesBySku(42, 'SKU-1', [{ price: 1.5, tax: 19, minmultiplier: 1 }]);
* const article = await store.article.setPricesBySku(42, 'SKU-1', [{ net: 10.0, taxRate: 19, minmultiplier: 1 }]);
* // or with gross:
* const article = await store.article.setPricesBySku(42, 'SKU-1', [{ gross: 11.9, taxRate: 19 }]);
* // => { id, sku, name, ... }
*/
return async (googleAuthId, sku, prices, tx) => {

View File

@@ -40,8 +40,17 @@ export default function addImage(provider) {
await runner.query(deleteBrandPicSql, { brandId });
const sort = 0;
const fullHash = newFullHash();
const fullHash = newFullHash(processed.full.buffer);
let picId;
const existingPic = await runner.query(
'SELECT id FROM pic WHERE hash = @fullHash',
{ fullHash },
);
if (existingPic.rows[0]) {
picId = existingPic.rows[0].id;
} else {
if (isSqlite) {
if (runner.writePicFull) {
await runner.writePicFull(fullHash, processed.full.buffer);
@@ -65,7 +74,8 @@ export default function addImage(provider) {
thumbbinary: processed.thumb.buffer,
});
const picId = picResult.rows[0]?.id ?? picResult.insertId;
picId = picResult.rows[0]?.id ?? picResult.insertId;
}
const brandPicResult = await runner.query(addBrandPicSql, {
brandId,

View File

@@ -42,8 +42,17 @@ export default function addImage(provider) {
const sortResult = await runner.query(getSortSql, { categoryId });
const sort = sortResult.rows[0].sort;
const fullHash = newFullHash();
const fullHash = newFullHash(processed.full.buffer);
let picId;
const existingPic = await runner.query(
'SELECT id FROM pic WHERE hash = @fullHash',
{ fullHash },
);
if (existingPic.rows[0]) {
picId = existingPic.rows[0].id;
} else {
if (isSqlite) {
if (runner.writePicFull) {
await runner.writePicFull(fullHash, processed.full.buffer);
@@ -67,13 +76,21 @@ export default function addImage(provider) {
thumbbinary: processed.thumb.buffer,
});
const picId = picResult.rows[0]?.id ?? picResult.insertId;
picId = picResult.rows[0]?.id ?? picResult.insertId;
}
const existingCategoryPic = await runner.query(
'SELECT id, sort FROM categorypic WHERE category_id = @categoryId AND pic_id = @picId',
{ categoryId, picId },
);
if (!existingCategoryPic.rows[0]) {
await runner.query(addCategoryPicSql, {
categoryId,
picId,
sort,
});
}
return {
id: categoryId,

View File

@@ -1,5 +1,24 @@
import { randomBytes } from 'node:crypto';
import { createHash, randomBytes } from 'node:crypto';
export default function newFullHash() {
return randomBytes(32).toString('hex');
const CHARSET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
export function toBase62(buffer) {
let num = BigInt('0x' + buffer.toString('hex'));
if (num === 0n) return CHARSET[0].repeat(8);
let res = '';
while (num > 0n) {
res = CHARSET[Number(num % 62n)] + res;
num /= 62n;
}
return res;
}
export default function newFullHash(buffer) {
if (buffer) {
const digest = createHash('sha256').update(buffer).digest();
return toBase62(digest).slice(0, 8);
}
const bytes = randomBytes(16);
return toBase62(bytes).slice(0, 8);
}