This commit is contained in:
seb
2026-07-06 01:31:11 +02:00
commit 917930d0fa
34 changed files with 2912 additions and 0 deletions

151
src/queries/product-list.js Normal file
View File

@@ -0,0 +1,151 @@
import sql from 'mssql';
import { getPool } from '../db.js';
import { getCustomerGroupIds } from './customer-groups.js';
import { getProductAttributes } from './product-attributes.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)
)
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,
CONVERT(BIGINT, a.bRowversion) 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,
(
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
WHERE a.cAktiv = 'Y'
AND CONVERT(BIGINT, a.bRowversion) > @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)
.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 ?? '',
attributes: articleAttributes?.attributes ?? [],
...(articleAttributes?.deposit ?? {}),
};
});
}