This commit is contained in:
seb
2026-10-19 02:28:15 +02:00
parent cce966d021
commit 5259e3bef9
6 changed files with 105 additions and 6 deletions

View File

@@ -186,7 +186,7 @@ Returns the full detail of an article by its SKU, including unit, brand, categor
**Returns**
`Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>` — The full article detail.
`Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null, incoming: Array<{date: string, amount: number}>}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>` — The full article detail.
**Throws**

2
API.md
View File

@@ -111,7 +111,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- `createUnderParent(input: {googleAuthId: number, parentId: number, name: string, desc?: string, seo?: string, track_stock?: boolean|number}, tx?: object): Promise<{id: number, parentId: number, name: string, sku: string, track_stock: boolean}>`
- `delete(googleAuthId: number, sku: string, optionsOrTx?: object|*, maybeTx?: object): Promise<void>`
- `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: object): Promise<void>`
- `getBySku(googleAuthId: number, sku: string, tx?: object): Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>`
- `getBySku(googleAuthId: number, sku: string, tx?: object): Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null, incoming: Array<{date: string, amount: number}>}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>`
- `getHistoryBySku(googleAuthId: number, sku: string, opts?: {limit?: number}, tx?: object): Promise<Array<{id: number, companyuserId: number|null, date: string, text: string, meta: string|null}>>`
- `importRows(input: {googleAuthId: number, rows: Array<object>}, tx?: object): Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>`
- `listChildren(googleAuthId: number, parentSku: string, tx?: object): Promise<Array<{id: number, sku: string, parentsort: number, trackStock: boolean, unitamount: number, weight: number, name: string|null, slug: string|null, attributeValueId: number|null, attributeValue: string|null}>>`

View File

@@ -574,7 +574,30 @@ SELECT
asup.sort,
asup.net AS [price.net],
asup.tax_id AS [price.taxId],
t.rate AS [price.taxRate]
t.rate AS [price.taxRate],
(
SELECT
m.[date] AS [date],
SUM(ma.amount - COALESCE(ma_ledger.booked_amount, 0)) AS amount
FROM manifest m
INNER JOIN manifestarticle ma ON ma.manifest_id = m.id
LEFT JOIN (
SELECT manifestarticle_id, SUM(amount) AS booked_amount
FROM ledger
WHERE warehouse_id IS NULL
GROUP BY manifestarticle_id
) ma_ledger ON ma_ledger.manifestarticle_id = ma.id
WHERE m.company_id = a.company_id
AND m.[type] = 'delivery'
AND m.entity_id = asup.entity_id
AND asup.sku IS NOT NULL
AND asup.sku <> ''
AND ma.sku = asup.sku
AND (ma.amount - COALESCE(ma_ledger.booked_amount, 0)) > 0
GROUP BY m.id, m.[date]
ORDER BY m.[date] ASC, m.id ASC
FOR JSON PATH
) AS incomingJson
FROM articlesupplier asup
INNER JOIN entity e ON e.id = asup.entity_id
LEFT JOIN tax t ON t.id = asup.tax_id

View File

@@ -207,11 +207,40 @@ SELECT auth.*,
'stock', stock, 'sort', sort,
'price', CASE WHEN net IS NULL THEN NULL ELSE json_object(
'net', net, 'taxId', tax_id, 'taxRate', tax_rate
) END
) END,
'incoming', json(incoming_json)
)), '[]') FROM (
SELECT s.id, s.article_id, s.entity_id, e.name AS supplier_name,
s.sku, s.pu, s.stock, s.sort,
s.net, s.tax_id, t.rate AS tax_rate
s.net, s.tax_id, t.rate AS tax_rate,
COALESCE((
SELECT json_group_array(json_object(
'date', inc.date,
'amount', inc.amount
))
FROM (
SELECT
m.date AS date,
SUM(ma.amount - COALESCE(ma_ledger.booked_amount, 0)) AS amount
FROM manifest m
INNER JOIN manifestarticle ma ON ma.manifest_id = m.id
LEFT JOIN (
SELECT manifestarticle_id, SUM(amount) AS booked_amount
FROM ledger
WHERE warehouse_id IS NULL
GROUP BY manifestarticle_id
) ma_ledger ON ma_ledger.manifestarticle_id = ma.id
WHERE m.company_id = a.company_id
AND m.type = 'delivery'
AND m.entity_id = s.entity_id
AND s.sku IS NOT NULL
AND s.sku <> ''
AND ma.sku = s.sku
AND (ma.amount - COALESCE(ma_ledger.booked_amount, 0)) > 0
GROUP BY m.id, m.date
ORDER BY m.date ASC, m.id ASC
) inc
), '[]') AS incoming_json
FROM articlesupplier s INNER JOIN entity e ON e.id = s.entity_id
LEFT JOIN tax t ON t.id = s.tax_id
WHERE s.article_id = a.id ORDER BY s.sort, s.id

View File

@@ -18,7 +18,7 @@ export default function getBySku(provider) {
* @param {number} googleAuthId - The id of the Google auth user.
* @param {string} sku - The article SKU.
* @param {object} [tx] - Optional transaction.
* @returns {Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>} The full article detail.
* @returns {Promise<{article: {id: number, sku: string, name: string, slug: string, desc: string|null, seo: string|null, unitamount: number|null, weight: number|null, track_stock: boolean, unit: {si: string|null, basesi: string|null, basesifactor: number|null, symbol: string|null, name: string|null}, brand: {id: number|null, name: string, fullHash: string|null, thumbW: number|null, thumbH: number|null, thumbHash: string|null}|null, categoryIds: number[], photos: Array<{sort: number, fullHash: string, fullW: number, fullH: number, fullSize: number, thumbW: number, thumbH: number, thumbHash: string}>, eans: string[], prices: Array<{minmultiplier: number, net: number, taxRate: number, customergroupId: number}>, suppliers: Array<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, price: {net: number, taxId: number, taxRate?: number}|null, incoming: Array<{date: string, amount: number}>}>, optionsCount: number, articleListCount: number, childCount: number, parentArticleId: number|null, parentSku: string|null}}>} The full article detail.
* @throws {Error} If the article is not found or the user is not authorized.
*
* @example
@@ -87,6 +87,10 @@ export default function getBySku(provider) {
stock: s.stock ?? null,
sort: s.sort,
price: s.price?.net != null ? s.price : null,
incoming: parseJsonArray(s.incomingJson ?? s.incoming).map((inc) => ({
date: inc.date,
amount: Number(inc.amount ?? 0),
})),
})),
warehouses: parseJsonArray(a.warehousesJson).map((w) => ({
id: w.id,

View File

@@ -651,4 +651,47 @@ describe('store.article', () => {
assert.deepEqual(untouched.eans, []);
});
});
describe('supplier incoming deliveries', () => {
it('returns incoming dates and unbooked amounts for each supplier', async () => {
const art = await createArticle(store, tenant, { name: 'Incoming Article', sku: 'INC-ART-1' });
const supp = await store.supplier.create(gid, { name: 'Supplier Incoming' });
await store.articlesupplier.create(gid, art.sku, {
entityId: supp.id,
sku: 'SUP-SKU-1',
pu: 10,
});
let res = await store.article.getBySku(gid, art.sku);
assert.equal(res.article.suppliers.length, 1);
assert.deepEqual(res.article.suppliers[0].incoming, []);
const d1 = await store.delivery.create(gid, {
supplierId: supp.id,
date: '2026-11-01T00:00:00.000Z',
lines: [{ sku: 'SUP-SKU-1', name: 'Item', pu: 10, amount: 25, net: 5, taxrate: 19 }],
});
const d2 = await store.delivery.create(gid, {
supplierId: supp.id,
date: '2026-11-15T00:00:00.000Z',
lines: [{ sku: 'SUP-SKU-1', name: 'Item', pu: 10, amount: 15, net: 5, taxrate: 19 }],
});
res = await store.article.getBySku(gid, art.sku);
assert.equal(res.article.suppliers[0].incoming.length, 2);
assert.equal(res.article.suppliers[0].incoming[0].amount, 25);
assert.equal(res.article.suppliers[0].incoming[0].date, '2026-11-01T00:00:00.000Z');
assert.equal(res.article.suppliers[0].incoming[1].amount, 15);
assert.equal(res.article.suppliers[0].incoming[1].date, '2026-11-15T00:00:00.000Z');
const wh = await store.warehouse.create(gid, { name: 'Inc WH' });
await store.stock.accept(gid, { deliveryId: d1.id, warehouseId: wh.id });
res = await store.article.getBySku(gid, art.sku);
assert.equal(res.article.suppliers[0].incoming.length, 1);
assert.equal(res.article.suppliers[0].incoming[0].amount, 15);
assert.equal(res.article.suppliers[0].incoming[0].date, '2026-11-15T00:00:00.000Z');
});
});
});