499 lines
16 KiB
JavaScript
499 lines
16 KiB
JavaScript
import { getProvider } from './store.js';
|
|
|
|
export async function getCompanyCatalogMeta(companyId) {
|
|
const provider = await getProvider();
|
|
const result = await provider.query(
|
|
`SELECT
|
|
id,
|
|
name,
|
|
root_category_id AS rootCategoryId,
|
|
default_language_id AS languageId,
|
|
poscustomergroup_id AS posCustomerGroupId
|
|
FROM company
|
|
WHERE id = @companyId`,
|
|
{ companyId }
|
|
);
|
|
return result.rows[0] ?? null;
|
|
}
|
|
|
|
export async function listCategories(companyId) {
|
|
const company = await getCompanyCatalogMeta(companyId);
|
|
if (!company) {
|
|
throw new Error('Company not found');
|
|
}
|
|
if (!company.rootCategoryId) {
|
|
return [];
|
|
}
|
|
|
|
const provider = await getProvider();
|
|
const isSqlite = provider.dialect === 'sqlite3';
|
|
|
|
const sql = isSqlite
|
|
? `WITH RECURSIVE 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,
|
|
t.sort,
|
|
ct.name,
|
|
ct.slug,
|
|
p.hash AS fullHash,
|
|
p.thumbw AS thumbW,
|
|
p.thumbh AS thumbH,
|
|
p.thumbhash AS thumbHash
|
|
FROM tree t
|
|
LEFT JOIN categorytext ct
|
|
ON ct.category_id = t.id AND ct.language_id = @languageId
|
|
LEFT JOIN pic p ON p.id = (
|
|
SELECT cp.pic_id
|
|
FROM categorypic cp
|
|
WHERE cp.category_id = t.id
|
|
ORDER BY cp.sort, cp.id
|
|
LIMIT 1
|
|
)
|
|
ORDER BY t.sort, t.id`
|
|
: `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,
|
|
t.sort,
|
|
ct.name,
|
|
ct.slug,
|
|
p.hash AS fullHash,
|
|
p.thumbw AS thumbW,
|
|
p.thumbh AS thumbH,
|
|
p.thumbhash AS thumbHash
|
|
FROM tree t
|
|
LEFT JOIN categorytext ct
|
|
ON ct.category_id = t.id AND ct.language_id = @languageId
|
|
OUTER APPLY (
|
|
SELECT TOP 1 cp.pic_id
|
|
FROM categorypic cp
|
|
WHERE cp.category_id = t.id
|
|
ORDER BY cp.sort ASC, cp.id ASC
|
|
) first_pic
|
|
LEFT JOIN pic p ON p.id = first_pic.pic_id
|
|
ORDER BY t.sort, t.id`;
|
|
|
|
const result = await provider.query(sql, {
|
|
rootId: company.rootCategoryId,
|
|
languageId: company.languageId,
|
|
});
|
|
|
|
return result.rows.map((row) => ({
|
|
id: row.id,
|
|
parentId: row.parentId,
|
|
sort: row.sort,
|
|
name: row.name ?? '',
|
|
slug: row.slug ?? '',
|
|
fullHash: row.fullHash ?? null,
|
|
thumbW: row.thumbW ?? null,
|
|
thumbH: row.thumbH ?? null,
|
|
thumbHash: row.thumbHash ?? null,
|
|
}));
|
|
}
|
|
|
|
export async function listArticles(companyId, { categoryId, limit = 50, offset = 0, search } = {}) {
|
|
const company = await getCompanyCatalogMeta(companyId);
|
|
if (!company) {
|
|
throw new Error('Company not found');
|
|
}
|
|
|
|
const provider = await getProvider();
|
|
const isSqlite = provider.dialect === 'sqlite3';
|
|
|
|
const safeLimit = Math.min(Math.max(1, Number(limit) || 50), 200);
|
|
const safeOffset = Math.max(0, Number(offset) || 0);
|
|
const trimmedSearch = typeof search === 'string' ? search.trim() : '';
|
|
|
|
const params = {
|
|
companyId,
|
|
languageId: company.languageId,
|
|
posCustomerGroupId: company.posCustomerGroupId,
|
|
limit: safeLimit,
|
|
offset: safeOffset,
|
|
};
|
|
|
|
if (categoryId != null) {
|
|
params.categoryId = Number(categoryId);
|
|
}
|
|
if (trimmedSearch) {
|
|
params.searchPattern = `%${trimmedSearch}%`;
|
|
}
|
|
|
|
const whereClauses = [
|
|
'a.company_id = @companyId',
|
|
'a.parentarticle_id IS NULL',
|
|
];
|
|
if (categoryId != null) {
|
|
whereClauses.push('ca.category_id = @categoryId');
|
|
}
|
|
if (trimmedSearch) {
|
|
whereClauses.push('(a.sku LIKE @searchPattern OR at.name LIKE @searchPattern)');
|
|
}
|
|
const whereSql = whereClauses.join(' AND ');
|
|
|
|
const fromClause = categoryId != null
|
|
? 'categoryarticle ca INNER JOIN article a ON a.id = ca.article_id'
|
|
: 'article a';
|
|
|
|
const sql = isSqlite
|
|
? `SELECT
|
|
a.id,
|
|
a.sku,
|
|
at.name,
|
|
at.slug,
|
|
pr.net,
|
|
t.rate AS taxRate,
|
|
a.track_stock AS trackStock,
|
|
pic.hash AS fullHash,
|
|
pic.thumbw AS thumbW,
|
|
pic.thumbh AS thumbH,
|
|
pic.thumbhash AS thumbHash,
|
|
b.id AS brandId,
|
|
b.name AS brandName
|
|
FROM ${fromClause}
|
|
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
|
|
LEFT JOIN pic ON pic.id = (
|
|
SELECT ap.pic_id
|
|
FROM articlepic ap
|
|
WHERE ap.article_id = a.id
|
|
ORDER BY ap.sort, ap.id
|
|
LIMIT 1
|
|
)
|
|
LEFT JOIN brand b ON b.id = a.brand_id
|
|
WHERE ${whereSql}
|
|
ORDER BY a.id
|
|
LIMIT @limit OFFSET @offset`
|
|
: `SELECT
|
|
a.id,
|
|
a.sku,
|
|
at.name,
|
|
at.slug,
|
|
pr.net,
|
|
t.rate AS taxRate,
|
|
a.track_stock AS trackStock,
|
|
pic.hash AS fullHash,
|
|
pic.thumbw AS thumbW,
|
|
pic.thumbh AS thumbH,
|
|
pic.thumbhash AS thumbHash,
|
|
b.id AS brandId,
|
|
b.name AS brandName
|
|
FROM ${fromClause}
|
|
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
|
|
WHERE ${whereSql}
|
|
ORDER BY a.id
|
|
OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
|
|
|
|
const result = await provider.query(sql, params);
|
|
|
|
return result.rows.map((row) => {
|
|
const net = row.net != null ? Number(row.net) : null;
|
|
const taxRate = row.taxRate != null ? Number(row.taxRate) : null;
|
|
const gross = net != null && taxRate != null ? +(net * (1 + taxRate / 100)).toFixed(2) : null;
|
|
|
|
return {
|
|
id: row.id,
|
|
sku: row.sku,
|
|
name: row.name ?? '',
|
|
slug: row.slug ?? '',
|
|
net,
|
|
taxRate,
|
|
gross,
|
|
trackStock: Boolean(row.trackStock ?? 1),
|
|
brand: row.brandName
|
|
? {
|
|
id: row.brandId ?? null,
|
|
name: row.brandName,
|
|
}
|
|
: null,
|
|
fullHash: row.fullHash ?? null,
|
|
thumbW: row.thumbW ?? null,
|
|
thumbH: row.thumbH ?? null,
|
|
thumbHash: row.thumbHash ?? null,
|
|
};
|
|
});
|
|
}
|
|
|
|
export async function getArticleBySku(companyId, sku) {
|
|
const company = await getCompanyCatalogMeta(companyId);
|
|
if (!company) {
|
|
throw new Error('Company not found');
|
|
}
|
|
|
|
const provider = await getProvider();
|
|
const isSqlite = provider.dialect === 'sqlite3';
|
|
|
|
const sql = isSqlite
|
|
? `SELECT
|
|
a.id,
|
|
a.sku,
|
|
at.name,
|
|
at.slug,
|
|
at.desc AS description,
|
|
a.unitamount,
|
|
a.weight,
|
|
a.track_stock AS trackStock,
|
|
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,
|
|
brand_pic.hash AS brandFullHash,
|
|
brand_pic.thumbw AS brandThumbW,
|
|
brand_pic.thumbh AS brandThumbH,
|
|
brand_pic.thumbhash AS brandThumbHash,
|
|
a.is_parent AS isParent,
|
|
a.parentarticle_id AS parentArticleId,
|
|
parent.sku AS parentSku
|
|
FROM article a
|
|
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 pic brand_pic ON brand_pic.id = (
|
|
SELECT bp.pic_id
|
|
FROM brandpic bp
|
|
WHERE bp.brand_id = b.id
|
|
ORDER BY bp.sort, bp.id
|
|
LIMIT 1
|
|
)
|
|
LEFT JOIN article parent ON parent.id = a.parentarticle_id
|
|
WHERE a.sku = @sku AND a.company_id = @companyId`
|
|
: `SELECT
|
|
a.id,
|
|
a.sku,
|
|
at.name,
|
|
at.slug,
|
|
at.desc AS description,
|
|
a.unitamount,
|
|
a.weight,
|
|
a.track_stock AS trackStock,
|
|
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,
|
|
brand_pic.hash AS brandFullHash,
|
|
brand_pic.thumbw AS brandThumbW,
|
|
brand_pic.thumbh AS brandThumbH,
|
|
brand_pic.thumbhash AS brandThumbHash,
|
|
a.is_parent AS isParent,
|
|
a.parentarticle_id AS parentArticleId,
|
|
parent.sku AS parentSku
|
|
FROM article a
|
|
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
|
|
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
|
|
WHERE a.sku = @sku AND a.company_id = @companyId`;
|
|
|
|
const result = await provider.query(sql, {
|
|
sku,
|
|
companyId,
|
|
languageId: company.languageId,
|
|
});
|
|
|
|
const row = result.rows[0];
|
|
if (!row) {
|
|
throw new Error('Article not found');
|
|
}
|
|
|
|
const articleId = row.id;
|
|
|
|
const [categoriesRes, photosRes, eansRes, pricesRes, variantsRes] = await Promise.all([
|
|
provider.query(
|
|
`SELECT ca.category_id AS categoryId, ct.name, ct.slug
|
|
FROM categoryarticle ca
|
|
LEFT JOIN categorytext ct ON ct.category_id = ca.category_id AND ct.language_id = @languageId
|
|
WHERE ca.article_id = @articleId
|
|
ORDER BY ca.category_id`,
|
|
{ articleId, languageId: company.languageId }
|
|
),
|
|
provider.query(
|
|
`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 = @articleId
|
|
ORDER BY ap.sort ASC, ap.id ASC`,
|
|
{ articleId }
|
|
),
|
|
provider.query(
|
|
`SELECT ae.ean
|
|
FROM articleean ae
|
|
WHERE ae.article_id = @articleId
|
|
ORDER BY ae.ean`,
|
|
{ articleId }
|
|
),
|
|
provider.query(
|
|
`SELECT
|
|
pr.minmultiplier,
|
|
pr.net,
|
|
t.rate AS taxRate
|
|
FROM price pr
|
|
INNER JOIN tax t ON t.id = pr.tax_id
|
|
WHERE pr.article_id = @articleId
|
|
AND pr.customergroup_id = @posCustomerGroupId
|
|
ORDER BY pr.minmultiplier ASC`,
|
|
{ articleId, posCustomerGroupId: company.posCustomerGroupId }
|
|
),
|
|
row.isParent
|
|
? provider.query(
|
|
`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 ON aav.article_id = child.id
|
|
LEFT JOIN attributevalue av ON av.id = aav.attributevalue_id
|
|
WHERE child.parentarticle_id = @articleId
|
|
ORDER BY child.parentsort ASC, child.id ASC`,
|
|
{ articleId, languageId: company.languageId }
|
|
)
|
|
: Promise.resolve({ rows: [] }),
|
|
]);
|
|
|
|
const prices = pricesRes.rows.map((p) => {
|
|
const net = p.net != null ? Number(p.net) : null;
|
|
const taxRate = p.taxRate != null ? Number(p.taxRate) : null;
|
|
const gross = net != null && taxRate != null ? +(net * (1 + taxRate / 100)).toFixed(2) : null;
|
|
return {
|
|
minmultiplier: p.minmultiplier,
|
|
net,
|
|
taxRate,
|
|
gross,
|
|
};
|
|
});
|
|
|
|
const primaryPrice = prices[0] ?? null;
|
|
|
|
return {
|
|
id: row.id,
|
|
sku: row.sku,
|
|
name: row.name ?? '',
|
|
slug: row.slug ?? '',
|
|
description: row.description ?? '',
|
|
unitamount: row.unitamount,
|
|
weight: row.weight,
|
|
trackStock: Boolean(row.trackStock ?? 1),
|
|
unit: row.unitSi
|
|
? {
|
|
si: row.unitSi,
|
|
basesi: row.unitBasesi,
|
|
basesifactor: row.unitBasesifactor,
|
|
symbol: row.unitSymbol ?? null,
|
|
name: row.unitName ?? null,
|
|
}
|
|
: null,
|
|
brand: row.brandName
|
|
? {
|
|
id: row.brandId ?? null,
|
|
name: row.brandName,
|
|
fullHash: row.brandFullHash ?? null,
|
|
thumbW: row.brandThumbW ?? null,
|
|
thumbH: row.brandThumbH ?? null,
|
|
thumbHash: row.brandThumbHash ?? null,
|
|
}
|
|
: null,
|
|
price: primaryPrice,
|
|
prices,
|
|
categories: categoriesRes.rows.map((c) => ({
|
|
id: c.categoryId,
|
|
name: c.name ?? '',
|
|
slug: c.slug ?? '',
|
|
})),
|
|
photos: photosRes.rows.map((p) => ({
|
|
sort: p.sort,
|
|
fullHash: p.fullHash,
|
|
fullW: p.fullW,
|
|
fullH: p.fullH,
|
|
fullSize: p.fullSize,
|
|
thumbW: p.thumbW,
|
|
thumbH: p.thumbH,
|
|
thumbHash: p.thumbHash,
|
|
})),
|
|
eans: eansRes.rows.map((e) => e.ean),
|
|
isParent: Boolean(row.isParent),
|
|
parentSku: row.parentSku ?? null,
|
|
variants: variantsRes.rows.map((v) => ({
|
|
id: v.id,
|
|
sku: v.sku,
|
|
sort: v.parentsort,
|
|
name: v.name ?? '',
|
|
slug: v.slug ?? '',
|
|
attributeValueId: v.attributeValueId ?? null,
|
|
attributeValue: v.attributeValue ?? null,
|
|
unitamount: v.unitamount,
|
|
weight: v.weight,
|
|
trackStock: Boolean(v.trackStock ?? 1),
|
|
})),
|
|
};
|
|
}
|