diff --git a/API.articlesupplier.md b/API.articlesupplier.md index 7bd1e03..8eeeca6 100644 --- a/API.articlesupplier.md +++ b/API.articlesupplier.md @@ -100,6 +100,35 @@ const suppliers = await store.articlesupplier.listByArticleSku(42, 'SKU-1'); // => [{ id, articleId, entityId, supplierName, sku, pu, stock, priceId, price }] ``` +## `articlesupplier.listByEntityAndSkus` + +*Source: `src/store/articlesupplier/listByEntityAndSkus.js`* + +Lists article-supplier assignments for a given supplier (entityId) and SKU list, including linked article details (name, slug, photos/articlepic, eans/articleean). + +**Parameters** + +| Name | Type | Description | +| --- | --- | --- | +| `googleAuthId` | `number` | The id of the Google auth user. | +| `entityIdOrOptions` | `number|{entityId?: number, supplierId?: number, skus?: string[]|string}` | The supplier entity id or an options object. | +| `[maybeSkus]` | `string[]|string` | The list of SKUs to filter by (or single SKU string). | + +**Returns** + +`Promise, articlepic: Array<{sort: number, thumbhash: string, hash: string, thumbw: number, thumbh: number}>, eans: string[], articleean: string[]}}>>` — The supplier assignments with article details. + +**Throws** + +`Error` — If user is not authorized, entityId is missing, or supplier does not exist. + +**Example** + +```js +const items = await store.articlesupplier.listByEntityAndSkus(42, 5, ['SKU-1', 'SKU-2']); +// => [{ id, articleId, entityId, supplierName, sku, pu, stock, sort, price, article: { id, sku, name, slug, photos, eans } }] +``` + ## `articlesupplier.order` *Source: `src/store/articlesupplier/order.js`* diff --git a/API.md b/API.md index 6dfa892..803418c 100644 --- a/API.md +++ b/API.md @@ -102,6 +102,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - `create(googleAuthId: number, sku: string, input: {entityId: number, sku?: string, pu?: number, stock?: number|null, sort?: number, netPrice?: number|null, taxId?: number, taxRate?: number}): Promise<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>` - `delete(googleAuthId: number, sku: string, id: number): Promise` - `listByArticleSku(googleAuthId: number, sku: string): Promise>` + - `listByEntityAndSkus(googleAuthId: number, entityIdOrOptions: number|{entityId?: number, supplierId?: number, skus?: string[]|string}, maybeSkus?: string[]|string): Promise, articlepic: Array<{sort: number, thumbhash: string, hash: string, thumbw: number, thumbh: number}>, eans: string[], articleean: string[]}}>>` - `order(googleAuthId: number, sku: string, input: {ids: Array}): Promise<{ids: Array}>` - `update(googleAuthId: number, sku: string, id: number, patch: {sku?: string|null, pu?: number, stock?: number|null, sort?: number, netPrice?: number|null, taxId?: number, taxRate?: number}): Promise<{id: number, articleId: number, entityId: number, supplierName: string|null, sku: string|null, pu: number, stock: number|null, sort: number, priceId: number|null, price: {net: number, taxId: number, taxRate?: number}|null}>` - [delivery](API.delivery.md) diff --git a/src/index.js b/src/index.js index fec6598..0a55f9f 100644 --- a/src/index.js +++ b/src/index.js @@ -66,6 +66,7 @@ import supplierCreate from './store/supplier/create.js'; import supplierUpdate from './store/supplier/update.js'; import supplierDelete from './store/supplier/delete.js'; import articlesupplierListByArticleSku from './store/articlesupplier/listByArticleSku.js'; +import articlesupplierListByEntityAndSkus from './store/articlesupplier/listByEntityAndSkus.js'; import articlesupplierCreate from './store/articlesupplier/create.js'; import articlesupplierUpdate from './store/articlesupplier/update.js'; import articlesupplierDelete from './store/articlesupplier/delete.js'; @@ -245,6 +246,8 @@ export default class LibDashStore { this.articlesupplier = { listByArticleSku: articlesupplierListByArticleSku(pool), + listByEntityAndSkus: articlesupplierListByEntityAndSkus(pool), + listBySupplierAndSkus: articlesupplierListByEntityAndSkus(pool), create: articlesupplierCreate(pool), update: articlesupplierUpdate(pool), order: articlesupplierOrder(pool), diff --git a/src/sql/articlesupplier.js b/src/sql/articlesupplier.js index 3d0f0df..60f81b6 100644 --- a/src/sql/articlesupplier.js +++ b/src/sql/articlesupplier.js @@ -117,3 +117,58 @@ SELECT taxrate FROM company WHERE id = @companyId; `; + +export const LIST_BY_ENTITY_AND_SKUS = ` +SELECT + asup.id, + asup.article_id AS articleId, + asup.entity_id AS entityId, + e.name AS supplierName, + asup.sku, + asup.pu, + asup.stock, + asup.sort, + asup.price_id AS priceId, + p.net AS priceNet, + p.tax_id AS priceTaxId, + t.rate AS priceTaxRate, + a.id AS a_id, + a.sku AS a_sku, + at.name AS a_name, + at.slug AS a_slug, + ( + SELECT + ap.sort, + p.hash, + p.thumbhash, + p.thumbw, + p.thumbh + FROM articlepic ap + INNER JOIN pic p ON p.id = ap.pic_id + WHERE ap.article_id = a.id + ORDER BY ap.sort ASC, ap.id ASC + FOR JSON PATH + ) AS photosJson, + ( + SELECT ae.ean + FROM articleean ae + WHERE ae.article_id = a.id + ORDER BY ae.ean + FOR JSON PATH + ) AS eansJson +FROM articlesupplier asup +INNER JOIN entity e ON e.id = asup.entity_id +INNER JOIN customergroup cg ON cg.id = e.customergroup_id +INNER JOIN article a ON a.id = asup.article_id +LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId +LEFT JOIN price p ON p.id = asup.price_id +LEFT JOIN tax t ON t.id = p.tax_id +WHERE asup.entity_id = @entityId + AND cg.company_id = @companyId + AND ( + @skusJson IS NULL + OR a.sku IN (SELECT value FROM OPENJSON(@skusJson)) + OR asup.sku IN (SELECT value FROM OPENJSON(@skusJson)) + ) +ORDER BY asup.sort ASC, asup.id ASC; +`; diff --git a/src/store/articlesupplier/listByEntityAndSkus.js b/src/store/articlesupplier/listByEntityAndSkus.js new file mode 100644 index 0000000..3d1223d --- /dev/null +++ b/src/store/articlesupplier/listByEntityAndSkus.js @@ -0,0 +1,130 @@ +import { LIST_BY_ENTITY_AND_SKUS } from '../../sql/articlesupplier.js'; +import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js'; +import { assertSupplier } from './helpers.js'; + +function parseJsonArray(raw) { + if (!raw) return []; + try { + const val = typeof raw === 'string' ? JSON.parse(raw) : raw; + return Array.isArray(val) ? val : []; + } catch { + return []; + } +} + +export default function listByEntityAndSkus(pool) { + const resolveAccess = resolveSelectedCompanyAccess(pool); + + /** + * Lists article-supplier assignments for a given supplier (entityId) and SKU list, + * including linked article details (name, slug, photos/articlepic, eans/articleean). + * + * @param {number} googleAuthId - The id of the Google auth user. + * @param {number|{entityId?: number, supplierId?: number, skus?: string[]|string}} entityIdOrOptions - The supplier entity id or an options object. + * @param {string[]|string} [maybeSkus] - The list of SKUs to filter by (or single SKU string). + * @returns {Promise, articlepic: Array<{sort: number, thumbhash: string, hash: string, thumbw: number, thumbh: number}>, eans: string[], articleean: string[]}}>>} The supplier assignments with article details. + * @throws {Error} If user is not authorized, entityId is missing, or supplier does not exist. + * + * @example + * const items = await store.articlesupplier.listByEntityAndSkus(42, 5, ['SKU-1', 'SKU-2']); + * // => [{ id, articleId, entityId, supplierName, sku, pu, stock, sort, price, article: { id, sku, name, slug, photos, eans } }] + */ + return async (googleAuthId, entityIdOrOptions, maybeSkus) => { + let entityId; + let skus; + + if ( + typeof entityIdOrOptions === 'object' && + entityIdOrOptions !== null && + !Array.isArray(entityIdOrOptions) + ) { + entityId = entityIdOrOptions.entityId ?? entityIdOrOptions.supplierId; + skus = entityIdOrOptions.skus; + } else { + entityId = entityIdOrOptions; + skus = maybeSkus; + } + + if (entityId == null || !Number.isFinite(Number(entityId))) { + throw new Error('Supplier required'); + } + const parsedEntityId = Number(entityId); + + // Fast return if an empty array of skus was explicitly requested + if (Array.isArray(skus) && skus.length === 0) { + return []; + } + + let skusJson = null; + if (skus != null) { + const rawList = Array.isArray(skus) ? skus : [skus]; + const cleanList = rawList + .map((s) => (s == null ? '' : String(s).trim())) + .filter(Boolean); + + if (cleanList.length === 0) { + return []; + } + skusJson = JSON.stringify(Array.from(new Set(cleanList))); + } + + const { companyId, languageId } = await resolveAccess(googleAuthId); + + await assertSupplier(pool, null, companyId, parsedEntityId); + + const result = await pool + .request() + .input('entityId', parsedEntityId) + .input('companyId', companyId) + .input('languageId', languageId) + .input('skusJson', skusJson) + .query(LIST_BY_ENTITY_AND_SKUS); + + return result.recordset.map((row) => { + const rawPhotos = parseJsonArray(row.photosJson); + const photos = rawPhotos.map((p) => ({ + sort: p.sort, + thumbhash: p.thumbhash, + hash: p.hash, + thumbw: p.thumbw, + thumbh: p.thumbh, + thumbHash: p.thumbhash, + fullHash: p.hash, + thumbW: p.thumbw, + thumbH: p.thumbh, + })); + + const eans = parseJsonArray(row.eansJson).map((r) => r.ean); + + return { + id: row.id, + articleId: row.articleId, + entityId: row.entityId, + supplierName: row.supplierName ?? null, + sku: row.sku ?? null, + pu: row.pu, + stock: row.stock ?? null, + sort: row.sort, + priceId: row.priceId ?? null, + price: + row.priceId != null && row.priceNet != null + ? { + net: row.priceNet, + taxId: row.priceTaxId, + ...(row.priceTaxRate != null ? { taxRate: row.priceTaxRate } : {}), + } + : null, + article: { + id: row.a_id, + sku: row.a_sku, + name: row.a_name ?? null, + slug: row.a_slug ?? null, + photos, + articlepic: photos, + eans, + articleean: eans, + }, + }; + }); + }; +}