Files
jtlPosSync/src/queries/product-list.js
2026-07-12 21:55:10 +02:00

175 lines
6.0 KiB
JavaScript

import sql from 'mssql';
import { getPool } from '../db.js';
import { getCustomerGroupIds } from './customer-groups.js';
import { getProductAttributes } from './product-attributes.js';
import { getActiveShopId } from '../shop.js';
const LANGUAGE_ID = Number(process.env.LANGUAGE_ID) || 1;
const TAX_ZONE_NAME = process.env.TAX_ZONE_NAME || 'Zone-EU';
const IMAGE_PLATFORM_ID = Number(process.env.IMAGE_PLATFORM_ID) || 1;
const IMAGE_SHOP_ID = Number(process.env.IMAGE_SHOP_ID) || 0;
const PRODUCT_LIST_SQL = `
WITH TaxRates AS (
SELECT kSteuerklasse, fSteuersatz
FROM dbo.tSteuersatz
WHERE kSteuerzone IN (SELECT kSteuerzone FROM dbo.tSteuerzone WHERE cName = @taxZoneName)
),
ImageRV AS (
SELECT kArtikel, MAX(CONVERT(BIGINT, bRowversion)) AS maxImageRV
FROM dbo.tArtikelbildPlattform
WHERE kShop = @kShop
GROUP BY kArtikel
)
SELECT TOP (@limit)
a.kArtikel AS id,
a.cArtNr AS sku,
ab.cName AS name,
a.fVKNetto AS netPrice,
tr.fSteuersatz AS taxRate,
a.dErstelldatum AS createdAt,
CASE
WHEN ir.maxImageRV IS NOT NULL AND ir.maxImageRV > CONVERT(BIGINT, a.bRowversion)
THEN ir.maxImageRV
ELSE CONVERT(BIGINT, a.bRowversion)
END AS lastChanged,
(
SELECT TOP 1 img.cHash
FROM dbo.tArtikelbildPlattform abp
INNER JOIN dbo.tBild img ON img.kBild = abp.kBild
WHERE abp.kArtikel = a.kArtikel
AND abp.kPlattform = @imagePlatformId
AND abp.kShop = @imageShopId
ORDER BY abp.nNr
) AS imgHash,
(
SELECT STRING_AGG(CAST(ka.kKategorie AS varchar(20)), ',')
FROM dbo.tkategorieartikel ka
WHERE ka.kArtikel = a.kArtikel
) AS categoryIds,
a.nIstVater AS isParent,
a.kVaterArtikel AS parentArticleId,
CASE WHEN a.kStueckliste <> 0 THEN '1' ELSE '0' END AS isCompositeProduct,
(
SELECT TOP 1 pv.cVariantName
FROM Pos.vProductVariant pv
WHERE pv.kProduct = a.kArtikel
) AS variantName
FROM dbo.tArtikel a
INNER JOIN dbo.tArtikelBeschreibung ab ON ab.kArtikel = a.kArtikel AND ab.kSprache = @languageId
LEFT JOIN TaxRates tr ON tr.kSteuerklasse = a.kSteuerklasse
LEFT JOIN ImageRV ir ON ir.kArtikel = a.kArtikel
WHERE a.cAktiv = 'Y'
AND (@kShop = 0 OR EXISTS (
SELECT 1 FROM dbo.tKategorieArtikel ka
INNER JOIN dbo.tKategorieShop ks ON ks.kKategorie = ka.kKategorie AND ks.kShop = @kShop
WHERE ka.kArtikel = a.kArtikel
))
AND (
CONVERT(BIGINT, a.bRowversion) > @cursor
OR (ir.maxImageRV IS NOT NULL AND ir.maxImageRV > @cursor)
)
ORDER BY lastChanged ASC;
`;
function priceOverridesSql(articleIds) {
const idList = articleIds.join(',');
return `
SELECT p.kArtikel AS articleId, p.kKundenGruppe AS customerGroupId, MIN(pd.fNettoPreis) AS netPrice
FROM dbo.tPreis p
INNER JOIN dbo.tPreisDetail pd ON pd.kPreis = p.kPreis
WHERE p.kArtikel IN (${idList}) AND p.kShop = 0 AND pd.nAnzahlAb = 0
GROUP BY p.kArtikel, p.kKundenGruppe;
`;
}
function formatDateTime(date) {
if (!date) {
return '0001-01-01 00:00:00';
}
const pad = (n) => String(n).padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
function grossPrice(netPrice, taxRate) {
return (Number(netPrice) * (1 + Number(taxRate || 0) / 100)).toFixed(2);
}
export async function getProductList({ cursor = 0, limit = 20 } = {}) {
const pool = getPool();
const [productResult, customerGroupIds] = await Promise.all([
pool
.request()
.input('cursor', sql.BigInt, cursor)
.input('limit', sql.Int, limit)
.input('languageId', sql.Int, LANGUAGE_ID)
.input('taxZoneName', sql.NVarChar, TAX_ZONE_NAME)
.input('imagePlatformId', sql.Int, IMAGE_PLATFORM_ID)
.input('imageShopId', sql.Int, IMAGE_SHOP_ID)
.input('kShop', sql.Int, getActiveShopId())
.query(PRODUCT_LIST_SQL),
getCustomerGroupIds(),
]);
const products = productResult.recordset;
const articleIds = products.map((p) => p.id);
const overridesByArticle = new Map();
let attributesByArticle = new Map();
if (articleIds.length > 0) {
const [overrideResult, attributeMap] = await Promise.all([
pool.request().query(priceOverridesSql(articleIds)),
getProductAttributes(pool, articleIds),
]);
attributesByArticle = attributeMap;
for (const row of overrideResult.recordset) {
if (!overridesByArticle.has(row.articleId)) {
overridesByArticle.set(row.articleId, new Map());
}
overridesByArticle.get(row.articleId).set(row.customerGroupId, row.netPrice);
}
}
return products.map((product) => {
const overrides = overridesByArticle.get(product.id);
const basePrice = grossPrice(product.netPrice, product.taxRate);
const prices = customerGroupIds.map((customerGroupId) => {
const overrideNetPrice = overrides?.get(customerGroupId);
const price =
overrideNetPrice !== undefined ? grossPrice(overrideNetPrice, product.taxRate) : basePrice;
return {
customerGroupId: String(customerGroupId),
customerId: '0',
price,
quantity: '0',
};
});
const categoryIds = product.categoryIds ? product.categoryIds.split(',') : [];
const articleAttributes = attributesByArticle.get(product.id);
return {
_id: String(product.id),
imghash: product.imgHash ?? null,
imgsrc: product.imgHash ?? null,
sku: product.sku,
name: product.name,
tax_rate: String(Math.round(Number(product.taxRate || 0))),
price: basePrice,
created_at: formatDateTime(product.createdAt),
lastChanged: String(product.lastChanged),
categories_id: categoryIds[0] ?? '0',
categories: categoryIds.map((categoryId) => ({ categoryId })),
prices,
is_parent: product.isParent ? '1' : '0',
parent: product.parentArticleId > 0 ? String(product.parentArticleId) : '0',
variants: product.variantName ?? '',
isCompositeProduct: product.isCompositeProduct,
attributes: articleAttributes?.attributes ?? [],
...(articleAttributes?.deposit ?? {}),
};
});
}