feat: Add product article number, barcode, and calculated gross price display with updated data fetching and tax zone configuration.
This commit is contained in:
@@ -9,6 +9,7 @@ ROOT_CATEGORY_ID=0
|
|||||||
JTL_SHOP_ID=0
|
JTL_SHOP_ID=0
|
||||||
JTL_SPRACHE_ID=1
|
JTL_SPRACHE_ID=1
|
||||||
JTL_PLATTFORM_ID=1
|
JTL_PLATTFORM_ID=1
|
||||||
|
JTL_STEUERZONE_ID=1
|
||||||
SERVER_PORT=3991
|
SERVER_PORT=3991
|
||||||
SERVER_HOST=127.0.0.1
|
SERVER_HOST=127.0.0.1
|
||||||
SYNC_INTERVAL_MS=600000
|
SYNC_INTERVAL_MS=600000
|
||||||
|
|||||||
110
index.html
110
index.html
@@ -136,13 +136,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-item {
|
.product-item {
|
||||||
padding: 0.25rem 0;
|
padding: 0.5rem 0;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-details {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-meta {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #888;
|
||||||
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2d3748;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
.product-image {
|
.product-image {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -771,6 +791,49 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderProducts(products) {
|
||||||
|
if (!products) return '<div class="loading">Loading products...</div>';
|
||||||
|
if (products.length === 0) return '<div class="category-products">No products found.</div>';
|
||||||
|
|
||||||
|
const items = products.map(p => {
|
||||||
|
const image = p.images && p.images.length > 0
|
||||||
|
? `<img src="/api/images/${p.images[0]}/thumbnail" class="product-image" loading="lazy">`
|
||||||
|
: '<div class="product-image"></div>';
|
||||||
|
|
||||||
|
// Calculate Gross Price
|
||||||
|
let priceDisplay = '';
|
||||||
|
if (p.fVKNetto !== undefined && p.fVKNetto !== null) {
|
||||||
|
const net = parseFloat(p.fVKNetto);
|
||||||
|
const rate = p.fSteuersatz ? parseFloat(p.fSteuersatz) : 19.0; // Default to 19% if missing
|
||||||
|
const gross = net * (1 + rate / 100);
|
||||||
|
priceDisplay = `<div class="product-price">${gross.toFixed(2)} €</div>`;
|
||||||
|
console.log('Product:', p.cName, 'Net:', net, 'Rate:', rate, 'Gross:', gross.toFixed(2));
|
||||||
|
} else {
|
||||||
|
console.log('Product missing price:', p.cName, 'fVKNetto:', p.fVKNetto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta info
|
||||||
|
const metaParts = [];
|
||||||
|
if (p.cArtNr) metaParts.push(`Art: ${p.cArtNr}`);
|
||||||
|
if (p.cBarcode) metaParts.push(`EAN: ${p.cBarcode}`);
|
||||||
|
const metaHtml = metaParts.length > 0 ? `<div class="product-meta">${metaParts.join(' • ')}</div>` : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="product-item">
|
||||||
|
${image}
|
||||||
|
<div class="product-details">
|
||||||
|
<div>
|
||||||
|
<a class="product-name-link" onclick="showProductDetails(${p.kArtikel}, '${p.cName.replace(/'/g, "\\'")}')">${p.cName}</a>
|
||||||
|
</div>
|
||||||
|
${metaHtml}
|
||||||
|
</div>
|
||||||
|
${priceDisplay}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
return `<div class="category-products">${items}</div>`;
|
||||||
|
}
|
||||||
function updateCategoryProducts(id) {
|
function updateCategoryProducts(id) {
|
||||||
const findAndReload = (nodes) => {
|
const findAndReload = (nodes) => {
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
@@ -971,6 +1034,7 @@
|
|||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = 'product-item';
|
li.className = 'product-item';
|
||||||
|
|
||||||
|
// Image
|
||||||
if (p.images && p.images.length > 0) {
|
if (p.images && p.images.length > 0) {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.className = 'product-image';
|
img.className = 'product-image';
|
||||||
@@ -981,14 +1045,46 @@
|
|||||||
li.appendChild(img);
|
li.appendChild(img);
|
||||||
}
|
}
|
||||||
|
|
||||||
const span = document.createElement('span');
|
// Product details container
|
||||||
span.className = 'product-name-link';
|
const detailsDiv = document.createElement('div');
|
||||||
span.textContent = p.cName;
|
detailsDiv.className = 'product-details';
|
||||||
span.onclick = (e) => {
|
|
||||||
|
// Product name
|
||||||
|
const nameDiv = document.createElement('div');
|
||||||
|
const nameLink = document.createElement('span');
|
||||||
|
nameLink.className = 'product-name-link';
|
||||||
|
nameLink.textContent = p.cName;
|
||||||
|
nameLink.onclick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
showProductDetails(p.kArtikel);
|
showProductDetails(p.kArtikel, p.cName);
|
||||||
};
|
};
|
||||||
li.appendChild(span);
|
nameDiv.appendChild(nameLink);
|
||||||
|
detailsDiv.appendChild(nameDiv);
|
||||||
|
|
||||||
|
// Meta info (Article number and barcode)
|
||||||
|
const metaParts = [];
|
||||||
|
if (p.cArtNr) metaParts.push(`Art: ${p.cArtNr}`);
|
||||||
|
if (p.cBarcode) metaParts.push(`EAN: ${p.cBarcode}`);
|
||||||
|
if (metaParts.length > 0) {
|
||||||
|
const metaDiv = document.createElement('div');
|
||||||
|
metaDiv.className = 'product-meta';
|
||||||
|
metaDiv.textContent = metaParts.join(' • ');
|
||||||
|
detailsDiv.appendChild(metaDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
li.appendChild(detailsDiv);
|
||||||
|
|
||||||
|
// Price
|
||||||
|
if (p.fVKNetto !== undefined && p.fVKNetto !== null) {
|
||||||
|
const net = parseFloat(p.fVKNetto);
|
||||||
|
const rate = p.fSteuersatz ? parseFloat(p.fSteuersatz) : 19.0;
|
||||||
|
const gross = net * (1 + rate / 100);
|
||||||
|
const priceDiv = document.createElement('div');
|
||||||
|
priceDiv.className = 'product-price';
|
||||||
|
priceDiv.textContent = `${gross.toFixed(2)} €`;
|
||||||
|
li.appendChild(priceDiv);
|
||||||
|
}
|
||||||
|
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,18 @@ export class ProductDataFetcher {
|
|||||||
SELECT
|
SELECT
|
||||||
ka.kKategorie,
|
ka.kKategorie,
|
||||||
ka.kArtikel,
|
ka.kArtikel,
|
||||||
ab.cName
|
ab.cName,
|
||||||
|
a.cArtNr,
|
||||||
|
a.fVKNetto,
|
||||||
|
a.cBarcode,
|
||||||
|
a.kSteuerklasse,
|
||||||
|
sk.cName AS cSteuerklasseName,
|
||||||
|
ss.fSteuersatz
|
||||||
FROM tkategorieartikel ka
|
FROM tkategorieartikel ka
|
||||||
JOIN tArtikelBeschreibung ab ON ka.kArtikel = ab.kArtikel
|
JOIN tArtikelBeschreibung ab ON ka.kArtikel = ab.kArtikel
|
||||||
JOIN tArtikel a ON ka.kArtikel = a.kArtikel
|
JOIN tArtikel a ON ka.kArtikel = a.kArtikel
|
||||||
|
LEFT JOIN tSteuerklasse sk ON a.kSteuerklasse = sk.kSteuerklasse
|
||||||
|
LEFT JOIN tSteuersatz ss ON sk.kSteuerklasse = ss.kSteuerklasse AND ss.kSteuerzone = ${process.env.JTL_STEUERZONE_ID}
|
||||||
WHERE ab.kSprache = ${process.env.JTL_SPRACHE_ID}
|
WHERE ab.kSprache = ${process.env.JTL_SPRACHE_ID}
|
||||||
AND a.cAktiv = 'Y'
|
AND a.cAktiv = 'Y'
|
||||||
AND ab.kPlattform = ${process.env.JTL_PLATTFORM_ID}
|
AND ab.kPlattform = ${process.env.JTL_PLATTFORM_ID}
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ class CategoryProductsSyncer extends EventEmitter {
|
|||||||
productsByCategory[record.kKategorie].push({
|
productsByCategory[record.kKategorie].push({
|
||||||
kArtikel: record.kArtikel,
|
kArtikel: record.kArtikel,
|
||||||
cName: record.cName,
|
cName: record.cName,
|
||||||
|
cArtNr: record.cArtNr,
|
||||||
|
fVKNetto: record.fVKNetto,
|
||||||
|
cBarcode: record.cBarcode,
|
||||||
|
fSteuersatz: record.fSteuersatz,
|
||||||
images: images
|
images: images
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user