export const ARTICLE_LIST_JOINS = ` LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId LEFT JOIN price pr ON pr.article_id = a.id AND pr.customergroup_id = @posCustomerGroupId AND pr.minmultiplier = 1 LEFT JOIN tax t ON t.id = pr.tax_id OUTER APPLY ( SELECT TOP 1 ap.pic_id FROM articlepic ap WHERE ap.article_id = a.id ORDER BY ap.sort ASC, ap.id ASC ) first_pic LEFT JOIN pic ON pic.id = first_pic.pic_id LEFT JOIN brand b ON b.id = a.brand_id OUTER APPLY ( SELECT TOP 1 bp.pic_id FROM brandpic bp WHERE bp.brand_id = b.id ORDER BY bp.sort ASC, bp.id ASC ) first_brand_pic LEFT JOIN pic brand_pic ON brand_pic.id = first_brand_pic.pic_id LEFT JOIN article parent ON parent.id = a.parentarticle_id `; export const ARTICLE_LIST_COLUMNS = ` a.id, a.sku, at.name, at.slug, pr.net, t.rate AS taxRate, a.track_stock, a.is_parent AS isParent, CASE WHEN a.optionslist_id IS NOT NULL THEN ( SELECT COUNT(*) FROM optionslistarticle ola WHERE ola.optionslist_id = a.optionslist_id ) ELSE 0 END AS optionsCount, CASE WHEN a.articlelist_id IS NOT NULL THEN ( SELECT COUNT(*) FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id ) ELSE 0 END AS articleListCount, ( SELECT COUNT(*) FROM article child WHERE child.parentarticle_id = a.id ) AS childCount, a.parentarticle_id AS parentArticleId, parent.sku AS parentSku, pic.hash AS fullHash, pic.thumbw AS thumbW, pic.thumbh AS thumbH, pic.thumbhash AS thumbHash, pic.smallthumbw AS smallThumbW, pic.smallthumbh AS smallThumbH, pic.smallthumbbinary AS smallThumbBinary, b.id AS brandId, b.name AS brandName, brand_pic.hash AS brandFullHash, brand_pic.thumbw AS brandThumbW, brand_pic.thumbh AS brandThumbH, brand_pic.thumbhash AS brandThumbHash `; export const GET_CATEGORY_ID_BY_SKU = ` SELECT TOP 1 ca.category_id AS categoryId, a.id AS articleId FROM article a INNER JOIN categoryarticle ca ON ca.article_id = a.id WHERE a.sku = @sku AND (@companyId IS NULL OR a.company_id = @companyId) `; /** Resolve an article id by sku (used to wire parent/child compound links). */ export const GET_ID_BY_SKU = ` SELECT TOP 1 id AS articleId FROM article WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId) `; /** True if an EAN is already assigned to any article in the company (duplicate guard). */ export const EAN_EXISTS = ` SELECT TOP 1 1 AS used FROM articleean ae INNER JOIN article a ON a.id = ae.article_id WHERE ae.ean = @ean AND (@companyId IS NULL OR a.company_id = @companyId) `; export const EAN_EXISTS_FOR_OTHER_ARTICLE = ` SELECT TOP 1 1 AS used FROM articleean ae INNER JOIN article a ON a.id = ae.article_id WHERE ae.ean = @ean AND a.company_id = @companyId AND a.id <> @articleId `; /** Link an article to its parent article and mark the parent. */ export const UPDATE_PARENT_BY_SKU = ` UPDATE article SET parentarticle_id = @parentArticleId, parentsort = COALESCE(@parentsort, 0) WHERE sku = @sku AND (@companyId IS NULL OR company_id = @companyId) `; export const SET_IS_PARENT = ` UPDATE article SET is_parent = 1 WHERE id = @articleId `; export const TOGGLE_PARENT = ` UPDATE article SET is_parent = @isParent, parentattributename_id = @parentAttributeNameId WHERE id = @articleId `; export const CREATE_CHILD = ` INSERT INTO article ( company_id, sku, unit_id, unitamount, weight, track_stock, brand_id, is_parent, parentarticle_id, parentsort ) OUTPUT INSERTED.id VALUES ( @companyId, @sku, @unitId, @unitamount, @weight, @trackStock, @brandId, 0, @parentArticleId, @parentsort ) `; export const UNLINK_CHILD = ` UPDATE article SET parentarticle_id = NULL, parentsort = 0 WHERE id = @articleId AND parentarticle_id = @parentArticleId `; export const LIST_CHILDREN_BY_PARENT_ID = ` SELECT child.id, child.sku, child.parentsort, child.track_stock AS trackStock, child.unitamount, child.weight, at_child.name, at_child.slug, av.id AS attributeValueId, av.value AS attributeValue FROM article child LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = @languageId LEFT JOIN ( articleattributevalue aav INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = @attributeId ) ON aav.article_id = child.id WHERE child.parentarticle_id = @parentArticleId ORDER BY child.parentsort ASC, child.id ASC `; export const SET_PARENT_SORT = ` UPDATE article SET parentsort = @parentsort WHERE id = @articleId AND parentarticle_id = @parentArticleId `; /** SKU → article/category + company auth in one round-trip. */ export const RESOLVE_ARTICLE_ACCESS_BY_SKU = ` SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId, co.root_category_id AS rootCategoryId, co.id AS companyId, co.default_language_id AS languageId, co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId FROM article a INNER JOIN company co ON co.id = a.company_id INNER JOIN google_auth ga ON ga.id = @googleAuthId INNER JOIN companyuser cu ON cu.company_id = a.company_id AND cu.google_auth_id = @googleAuthId LEFT JOIN categoryarticle ca ON ca.article_id = a.id WHERE a.sku = @sku AND (ga.selected_company IS NULL OR a.company_id = ga.selected_company) `; export const UPDATE_WEIGHT = ` UPDATE article SET weight = @weight, updated_at = SYSUTCDATETIME() WHERE id = @articleId `; export const UPDATE_TRACK_STOCK = ` UPDATE article SET track_stock = @trackStock, updated_at = SYSUTCDATETIME() WHERE id = @articleId `; export const UPDATE_BRAND = ` UPDATE article SET brand_id = @brandId, updated_at = SYSUTCDATETIME() WHERE id = @articleId `; export const UPDATE_UNIT = ` UPDATE article SET unit_id = @unitId, updated_at = SYSUTCDATETIME() WHERE id = @articleId `; export const UPDATE_UNITAMOUNT = ` UPDATE article SET unitamount = @unitamount, updated_at = SYSUTCDATETIME() WHERE id = @articleId `; export const UPDATE_UNIT_AND_AMOUNT = ` UPDATE article SET unit_id = @unitId, unitamount = @unitamount, updated_at = SYSUTCDATETIME() WHERE id = @articleId `; export const CREATE = ` INSERT INTO article (company_id, sku, unit_id, unitamount, weight, track_stock) OUTPUT INSERTED.id VALUES (@companyId, @sku, @unitId, @unitamount, @weight, @trackStock) `; export const DELETE_EANS_BY_ARTICLE = ` DELETE FROM articleean WHERE article_id = @articleId `; export const ADD_EAN = ` INSERT INTO articleean (article_id, ean) VALUES (@articleId, @ean) `; export const DELETE_CATEGORIES_BY_ARTICLE = ` DELETE FROM categoryarticle WHERE article_id = @articleId `; export const ADD_CATEGORY = ` INSERT INTO categoryarticle (category_id, article_id) VALUES (@categoryId, @articleId) `; export const GET_BRAND_BY_COMPANY_AND_ID = ` SELECT id FROM brand WHERE id = @brandId AND company_id = @companyId `; /** * Company-wide article search index. * Returns one row per article with id, sku, name, and all eans as a JSON array. * Used by the client for local search / autocomplete. */ export const SEARCH_ARTICLES = ` ;WITH category_tree AS ( SELECT id FROM category WHERE id = @rootCategoryId UNION ALL SELECT c.id FROM category c INNER JOIN category_tree ct ON c.parent_id = ct.id ) SELECT a.id, a.sku, at.name, ( SELECT ae.ean FROM articleean ae WHERE ae.article_id = a.id ORDER BY ae.ean FOR JSON PATH ) AS eansJson, ( SELECT ca2.category_id FROM categoryarticle ca2 WHERE ca2.article_id = a.id ORDER BY ca2.category_id FOR JSON PATH ) AS categoryIdsJson FROM article a LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId WHERE EXISTS ( SELECT 1 FROM categoryarticle ca INNER JOIN category_tree ct ON ct.id = ca.category_id WHERE ca.article_id = a.id ) ORDER BY a.id `; /** * Flat category tree (id, parentId, name) for a root category, used to resolve * breadcrumbs for the article export. */ export const GET_CATEGORY_TREE_FLAT = ` ;WITH tree AS ( SELECT id, parent_id, sort FROM category WHERE id = @rootId UNION ALL SELECT c.id, c.parent_id, c.sort FROM category c INNER JOIN tree t ON c.parent_id = t.id ) SELECT t.id, t.parent_id AS parentId, ct.name FROM tree t LEFT JOIN categorytext ct ON ct.category_id = t.id AND ct.language_id = @languageId `; /** * Export rows for every article in a category subtree (root = @categoryId). * One row per article with the columns used by the export CSV, including the * POS price (minmultiplier = 1 for the company poscustomergroup), brand, unit, * eans, categoryIds and all photo hashes as JSON arrays. */ export const GET_ARTICLE_EXPORT = ` ;WITH category_tree AS ( SELECT id FROM category WHERE id = @categoryId UNION ALL SELECT c.id FROM category c INNER JOIN category_tree ct ON c.parent_id = ct.id ) SELECT a.id, a.sku, at.name, at.slug, u.si AS unitSi, ut.symbol AS unitSymbol, a.unitamount, a.track_stock, b.name AS brandName, b.name AS brandName, pr.net AS net, t.rate AS taxRate, ( SELECT ae.ean FROM articleean ae WHERE ae.article_id = a.id ORDER BY ae.ean FOR JSON PATH ) AS eansJson, ( SELECT ca2.category_id FROM categoryarticle ca2 WHERE ca2.article_id = a.id ORDER BY ca2.category_id FOR JSON PATH ) AS categoryIdsJson, ( SELECT p.hash 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 fullHashesJson FROM article a INNER JOIN categoryarticle ca ON ca.article_id = a.id INNER JOIN category_tree ct ON ct.id = ca.category_id LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @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 = @languageId LEFT JOIN brand b ON b.id = a.brand_id LEFT JOIN price pr ON pr.article_id = a.id AND pr.customergroup_id = @posCustomerGroupId AND pr.minmultiplier = 1 LEFT JOIN tax t ON t.id = pr.tax_id ORDER BY a.id `; /** * 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 auth AS ( SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId, co.root_category_id AS rootCategoryId, co.id AS companyId, co.default_language_id AS languageId, co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId FROM article a INNER JOIN company co ON co.id = a.company_id INNER JOIN google_auth ga ON ga.id = @googleAuthId INNER JOIN companyuser cu ON cu.company_id = a.company_id AND cu.google_auth_id = @googleAuthId LEFT JOIN categoryarticle ca ON ca.article_id = a.id WHERE a.sku = @sku AND (ga.selected_company IS NULL OR a.company_id = ga.selected_company) ) SELECT auth.articleId, auth.categoryId, auth.rootCategoryId, auth.companyId, auth.languageId, auth.posCustomerGroupId, auth.companyuserId, ( SELECT a.id, a.sku, at.name, at.slug, at.[desc] AS [desc], at.seo AS seo, a.unitamount, a.weight, a.track_stock, u.si AS unitSi, u.basesi AS unitBasesi, u.basesifactor AS unitBasesifactor, ut.symbol AS unitSymbol, ut.name AS unitName, b.id AS brandId, b.name AS brandName, bp.hash AS brandFullHash, 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, a.optionslist_id AS optionslistId, ( SELECT ola.id, ola.article_id AS articleId, a_opt.sku, at_opt.name, ola.count, ola.sort, ola.mandated FROM optionslistarticle ola INNER JOIN article a_opt ON a_opt.id = ola.article_id LEFT JOIN articletext at_opt ON at_opt.article_id = a_opt.id AND at_opt.language_id = auth.languageId WHERE ola.optionslist_id = a.optionslist_id ORDER BY ola.sort ASC, ola.id ASC FOR JSON PATH ) AS optionslistItemsJson, ( SELECT COUNT(*) FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id ) AS articleListCount, a.articlelist_id AS articlelistId, ( SELECT ala.id, ala.article_id AS articleId, a_item.sku, at_item.name, ala.count, ala.sort FROM articlelistarticle ala INNER JOIN article a_item ON a_item.id = ala.article_id LEFT JOIN articletext at_item ON at_item.article_id = a_item.id AND at_item.language_id = auth.languageId WHERE ala.articlelist_id = a.articlelist_id ORDER BY ala.sort ASC, ala.id ASC FOR JSON PATH ) AS articlelistItemsJson, ( SELECT COUNT(*) FROM article child WHERE child.parentarticle_id = a.id ) AS childCount, a.is_parent AS isParent, a.parentattributename_id AS parentAttributeNameId, fan.name AS parentAttributeName, fan.exclusive AS parentAttributeExclusive, a.parentarticle_id AS parentArticleId, fa.sku AS parentSku, ( SELECT child.id, child.sku, child.parentsort, at_child.name, av.id AS attributeValueId, av.value AS attributeValue FROM article child LEFT JOIN articletext at_child ON at_child.article_id = child.id AND at_child.language_id = auth.languageId LEFT JOIN ( articleattributevalue aav INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = a.parentattributename_id ) ON aav.article_id = child.id WHERE child.parentarticle_id = a.id ORDER BY child.parentsort ASC, child.id ASC FOR JSON PATH ) AS childrenJson, ( 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, ( 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.net AS [price.net], asup.tax_id AS [price.taxId], 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 WHERE asup.article_id = a.id ORDER BY asup.sort ASC, asup.id ASC FOR JSON PATH ) AS suppliersJson, ( SELECT w.id, w.name, w.sort, CASE WHEN a.articlelist_id IS NOT NULL AND EXISTS ( SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id ) THEN COALESCE(( SELECT CASE WHEN MIN(COALESCE(comp_stock.stock, 0) / ala.count) < 0 THEN 0 ELSE FLOOR(MIN(COALESCE(comp_stock.stock, 0) / ala.count)) END FROM articlelistarticle ala LEFT JOIN ( SELECT ca.id AS comp_id, SUM(l.amount) AS stock FROM ledger l INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id INNER JOIN manifest m ON m.id = ma.manifest_id INNER JOIN article ca ON ( ma.sku = ca.sku OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id) ) WHERE m.company_id = a.company_id AND l.warehouse_id = w.id GROUP BY ca.id ) comp_stock ON comp_stock.comp_id = ala.article_id WHERE ala.articlelist_id = a.articlelist_id ), 0) WHEN a.articlelist_id IS NOT NULL THEN 0 ELSE COALESCE(stock_calc.stock, 0) END AS stock FROM warehouse w LEFT JOIN ( SELECT l.warehouse_id, SUM(l.amount) AS stock FROM ledger l INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id INNER JOIN manifest m ON m.id = ma.manifest_id WHERE m.company_id = a.company_id AND ( ma.sku = a.sku OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id) ) GROUP BY l.warehouse_id ) stock_calc ON stock_calc.warehouse_id = w.id WHERE w.company_id = a.company_id ORDER BY w.sort ASC, w.name ASC FOR JSON PATH ) AS warehousesJson, ( SELECT m.id AS orderId, m.[number] AS orderNumber, m.[date] AS [date], COALESCE(os.customer_name, LTRIM(RTRIM(ISNULL(e.first_name + ' ', '') + ISNULL(e.name, '')))) AS customerName, ROUND(SUM(ma.amount) - COALESCE(( SELECT SUM(ma_s.amount) FROM manifest m_s INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = m_s.id LEFT JOIN shipmentsummary ss ON ss.manifest_id = m_s.id WHERE m_s.company_id = m.company_id AND m_s.[type] = 'shipment' AND ISNULL(ss.state, 'new') != 'cancelled' AND m_s.[number] NOT LIKE '%#cancelled#%' AND ma_s.sku = a.sku AND m_s.[number] LIKE m.[number] + COALESCE((SELECT prefix FROM numbers WHERE company_id = m.company_id AND [type] = 'shipment'), '-L-') + '%' ), 0), 3) AS amount FROM manifest m INNER JOIN manifestarticle ma ON ma.manifest_id = m.id LEFT JOIN ordersummary os ON os.manifest_id = m.id LEFT JOIN entity e ON e.id = m.entity_id WHERE m.company_id = a.company_id AND m.[type] = 'order' AND ma.sku = a.sku AND COALESCE(os.is_shipped, 0) = 0 GROUP BY m.id, m.[number], m.[date], os.customer_name, e.first_name, e.name HAVING (SUM(ma.amount) - COALESCE(( SELECT SUM(ma_s.amount) FROM manifest m_s INNER JOIN manifestarticle ma_s ON ma_s.manifest_id = m_s.id LEFT JOIN shipmentsummary ss ON ss.manifest_id = m_s.id WHERE m_s.company_id = m.company_id AND m_s.[type] = 'shipment' AND ISNULL(ss.state, 'new') != 'cancelled' AND m_s.[number] NOT LIKE '%#cancelled#%' AND ma_s.sku = a.sku AND m_s.[number] LIKE m.[number] + COALESCE((SELECT prefix FROM numbers WHERE company_id = m.company_id AND [type] = 'shipment'), '-L-') + '%' ), 0)) > 0 ORDER BY m.[date] ASC, m.id ASC FOR JSON PATH ) AS undeliveredOrdersJson 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.parentarticle_id LEFT JOIN attributename fan ON fan.id = a.parentattributename_id WHERE a.id = auth.articleId FOR JSON PATH, WITHOUT_ARRAY_WRAPPER ) AS articleJson FROM auth `; export const CHECK_HAS_WAREHOUSE = ` SELECT TOP 1 id FROM warehouse WHERE company_id = @companyId; `; export const CHECK_ARTICLE_WAREHOUSE_STOCKS = ` SELECT w.id AS warehouseId, w.name AS warehouseName, COALESCE(stock_calc.stock, 0) AS stock FROM warehouse w LEFT JOIN ( SELECT l.warehouse_id, SUM(l.amount) AS stock FROM ledger l INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id INNER JOIN manifest m ON m.id = ma.manifest_id WHERE m.company_id = @companyId AND ( ma.sku = @sku OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = @articleId) ) GROUP BY l.warehouse_id ) stock_calc ON stock_calc.warehouse_id = w.id WHERE w.company_id = @companyId; `;