u
This commit is contained in:
@@ -186,7 +186,7 @@ Returns the full detail of an article by its SKU, including unit, brand, categor
|
|||||||
|
|
||||||
**Returns**
|
**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**
|
**Throws**
|
||||||
|
|
||||||
|
|||||||
2
API.md
2
API.md
@@ -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}>`
|
- `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>`
|
- `delete(googleAuthId: number, sku: string, optionsOrTx?: object|*, maybeTx?: object): Promise<void>`
|
||||||
- `deleteImage(googleAuthId: number, sku: string, fullHash: string, tx?: 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}>>`
|
- `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>}>`
|
- `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}>>`
|
- `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}>>`
|
||||||
|
|||||||
@@ -574,7 +574,30 @@ SELECT
|
|||||||
asup.sort,
|
asup.sort,
|
||||||
asup.net AS [price.net],
|
asup.net AS [price.net],
|
||||||
asup.tax_id AS [price.taxId],
|
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
|
FROM articlesupplier asup
|
||||||
INNER JOIN entity e ON e.id = asup.entity_id
|
INNER JOIN entity e ON e.id = asup.entity_id
|
||||||
LEFT JOIN tax t ON t.id = asup.tax_id
|
LEFT JOIN tax t ON t.id = asup.tax_id
|
||||||
|
|||||||
@@ -207,11 +207,40 @@ SELECT auth.*,
|
|||||||
'stock', stock, 'sort', sort,
|
'stock', stock, 'sort', sort,
|
||||||
'price', CASE WHEN net IS NULL THEN NULL ELSE json_object(
|
'price', CASE WHEN net IS NULL THEN NULL ELSE json_object(
|
||||||
'net', net, 'taxId', tax_id, 'taxRate', tax_rate
|
'net', net, 'taxId', tax_id, 'taxRate', tax_rate
|
||||||
) END
|
) END,
|
||||||
|
'incoming', json(incoming_json)
|
||||||
)), '[]') FROM (
|
)), '[]') FROM (
|
||||||
SELECT s.id, s.article_id, s.entity_id, e.name AS supplier_name,
|
SELECT s.id, s.article_id, s.entity_id, e.name AS supplier_name,
|
||||||
s.sku, s.pu, s.stock, s.sort,
|
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
|
FROM articlesupplier s INNER JOIN entity e ON e.id = s.entity_id
|
||||||
LEFT JOIN tax t ON t.id = s.tax_id
|
LEFT JOIN tax t ON t.id = s.tax_id
|
||||||
WHERE s.article_id = a.id ORDER BY s.sort, s.id
|
WHERE s.article_id = a.id ORDER BY s.sort, s.id
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default function getBySku(provider) {
|
|||||||
* @param {number} googleAuthId - The id of the Google auth user.
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
* @param {string} sku - The article SKU.
|
* @param {string} sku - The article SKU.
|
||||||
* @param {object} [tx] - Optional transaction.
|
* @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.
|
* @throws {Error} If the article is not found or the user is not authorized.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
@@ -87,6 +87,10 @@ export default function getBySku(provider) {
|
|||||||
stock: s.stock ?? null,
|
stock: s.stock ?? null,
|
||||||
sort: s.sort,
|
sort: s.sort,
|
||||||
price: s.price?.net != null ? s.price : null,
|
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) => ({
|
warehouses: parseJsonArray(a.warehousesJson).map((w) => ({
|
||||||
id: w.id,
|
id: w.id,
|
||||||
|
|||||||
@@ -651,4 +651,47 @@ describe('store.article', () => {
|
|||||||
assert.deepEqual(untouched.eans, []);
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user