diff --git a/src/sql/article.js b/src/sql/article.js index af3257b..903c15a 100644 --- a/src/sql/article.js +++ b/src/sql/article.js @@ -159,3 +159,152 @@ SELECT id FROM brand WHERE id = @brandId AND company_id = @companyId `; + +/** + * Single-query replacement for resolveArticleBySku + queryBySku. + * Resolves SKU → article + company + auth, then loads full article detail + * including categories, photos, eans, and prices via FOR JSON sub-queries. + * Returns one row with articleJson, or empty if not found/unauthorized. + */ +export const GET_ARTICLE_BY_SKU_FULL = ` +;WITH article_cat AS ( + SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId + FROM article a + INNER JOIN categoryarticle ca ON ca.article_id = a.id + WHERE a.sku = @sku +), +ancestors AS ( + SELECT c.id, c.parent_id + FROM category c + INNER JOIN article_cat ac ON c.id = ac.categoryId + + UNION ALL + + SELECT c.id, c.parent_id + FROM category c + INNER JOIN ancestors a ON c.id = a.parent_id +), +root AS ( + SELECT TOP 1 id AS rootCategoryId + FROM ancestors + WHERE parent_id IS NULL +), +auth AS ( + SELECT + ac.articleId, + ac.categoryId, + r.rootCategoryId, + co.id AS companyId, + co.default_language_id AS languageId, + co.poscustomergroup_id AS posCustomerGroupId, + cu.id AS companyuserId + FROM article_cat ac + LEFT JOIN root r ON 1 = 1 + LEFT JOIN company co ON co.root_category_id = r.rootCategoryId + LEFT JOIN companyuser cu + ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId +) +SELECT + auth.articleId, + auth.categoryId, + auth.rootCategoryId, + auth.companyId, + auth.languageId, + auth.posCustomerGroupId, + auth.companyuserId, + ( + SELECT + a.id, + a.sku, + at.name, + at.slug, + a.unitamount, + a.weight, + u.si AS unitSi, + u.basesi AS unitBasesi, + u.basesifactor AS unitBasesifactor, + ut.symbol AS unitSymbol, + ut.name AS unitName, + b.name AS brandName, + bp.thumbw AS brandThumbW, + bp.thumbh AS brandThumbH, + bp.thumbhash AS brandThumbHash, + ( + SELECT COUNT(*) + FROM optionslistarticle ola + WHERE ola.optionslist_id = a.optionslist_id + ) AS optionsCount, + ( + SELECT COUNT(*) + FROM articlelistarticle ala + WHERE ala.articlelist_id = a.articlelist_id + ) AS articleListCount, + ( + SELECT COUNT(*) + FROM article child + WHERE child.fatherarticle_id = a.id + ) AS childCount, + a.fatherarticle_id AS fatherArticleId, + fa.sku AS fatherSku, + ( + SELECT ca2.category_id + FROM categoryarticle ca2 + WHERE ca2.article_id = a.id + ORDER BY ca2.category_id + FOR JSON PATH + ) AS categoryIdsJson, + ( + SELECT + ap.sort, + p.hash AS fullHash, + p.w AS fullW, + p.h AS fullH, + p.size AS fullSize, + p.thumbw AS thumbW, + p.thumbh AS thumbH, + p.thumbhash AS thumbHash + 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, + ( + SELECT + pr.minmultiplier, + pr.net, + t.rate AS taxRate, + pr.customergroup_id AS customergroupId + FROM price pr + INNER JOIN tax t ON t.id = pr.tax_id + WHERE pr.article_id = a.id + ORDER BY pr.customergroup_id, pr.minmultiplier + FOR JSON PATH + ) AS pricesJson + FROM article a + LEFT JOIN articletext at + ON at.article_id = a.id AND at.language_id = auth.languageId + LEFT JOIN unit u ON u.id = a.unit_id + LEFT JOIN unittext ut + ON ut.unit_id = u.id AND ut.language_id = auth.languageId + LEFT JOIN brand b ON b.id = a.brand_id + OUTER APPLY ( + SELECT TOP 1 bp2.pic_id + FROM brandpic bp2 + WHERE bp2.brand_id = b.id + ORDER BY bp2.sort ASC, bp2.id ASC + ) first_brand_pic + LEFT JOIN pic bp ON bp.id = first_brand_pic.pic_id + LEFT JOIN article fa ON fa.id = a.fatherarticle_id + WHERE a.id = auth.articleId + FOR JSON PATH, WITHOUT_ARRAY_WRAPPER + ) AS articleJson +FROM auth +`; diff --git a/src/store/article/getBySku.js b/src/store/article/getBySku.js index bd6a8b7..7bb9fcc 100644 --- a/src/store/article/getBySku.js +++ b/src/store/article/getBySku.js @@ -1,22 +1,70 @@ -import resolveArticleBySku from './resolveArticleBySku.js'; -import queryBySku from './queryBySku.js'; +import { GET_ARTICLE_BY_SKU_FULL } from '../../sql/article.js'; + +function parseJsonArray(raw) { + if (!raw) return []; + const arr = typeof raw === 'string' ? JSON.parse(raw) : raw; + return Array.isArray(arr) ? arr : []; +} export default function getBySku(pool) { - const resolve = resolveArticleBySku(pool); - const load = queryBySku(pool); - return async (googleAuthId, sku) => { - let t0 = performance.now(); - const { articleId, languageId } = await resolve(googleAuthId, sku); - console.log(`[getBySku] resolveArticleBySku ${(performance.now() - t0).toFixed(1)}ms`); + const t0 = performance.now(); + const result = await pool + .request() + .input('sku', sku) + .input('googleAuthId', googleAuthId) + .query(GET_ARTICLE_BY_SKU_FULL); + console.log(`[getBySku] single query ${(performance.now() - t0).toFixed(1)}ms`); - t0 = performance.now(); - const article = await load(articleId, languageId); - console.log(`[getBySku] queryBySku (parallel) ${(performance.now() - t0).toFixed(1)}ms`); - if (!article) { + const row = result.recordset[0]; + if (!row) { throw new Error('Article not found'); } + if (!row.articleId || !row.categoryId) { + throw new Error('Article not found'); + } + if (!row.companyId || !row.rootCategoryId || !row.languageId) { + throw new Error('Parent not found'); + } + if (!row.companyuserId) { + throw new Error('Not authorized'); + } - return article; + const a = typeof row.articleJson === 'string' ? JSON.parse(row.articleJson) : row.articleJson; + + return { + article: { + id: a.id, + sku: a.sku, + name: a.name, + slug: a.slug, + unitamount: a.unitamount, + weight: a.weight, + unit: { + si: a.unitSi, + basesi: a.unitBasesi, + basesifactor: a.unitBasesifactor, + symbol: a.unitSymbol ?? null, + name: a.unitName ?? null, + }, + brand: a.brandName + ? { + name: a.brandName, + thumbW: a.brandThumbW, + thumbH: a.brandThumbH, + thumbHash: a.brandThumbHash, + } + : null, + categoryIds: parseJsonArray(a.categoryIdsJson).map((r) => r.category_id), + photos: parseJsonArray(a.photosJson), + eans: parseJsonArray(a.eansJson).map((r) => r.ean), + prices: parseJsonArray(a.pricesJson), + optionsCount: a.optionsCount, + articleListCount: a.articleListCount, + childCount: a.childCount, + fatherArticleId: a.fatherArticleId, + fatherSku: a.fatherSku, + }, + }; }; }