diff --git a/.env.example b/.env.example
index e0b7b6e..289fbd0 100644
--- a/.env.example
+++ b/.env.example
@@ -9,6 +9,7 @@ ROOT_CATEGORY_ID=0
JTL_SHOP_ID=0
JTL_SPRACHE_ID=1
JTL_PLATTFORM_ID=1
+JTL_STEUERZONE_ID=1
SERVER_PORT=3991
SERVER_HOST=127.0.0.1
SYNC_INTERVAL_MS=600000
diff --git a/index.html b/index.html
index c3905c1..44d1ff3 100644
--- a/index.html
+++ b/index.html
@@ -136,13 +136,33 @@
}
.product-item {
- padding: 0.25rem 0;
+ padding: 0.5rem 0;
border-bottom: 1px solid #eee;
display: flex;
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;
}
+ .product-price {
+ font-weight: bold;
+ color: #2d3748;
+ font-size: 0.95rem;
+ }
+
.product-image {
width: 32px;
height: 32px;
@@ -771,6 +791,49 @@
});
}
+ function renderProducts(products) {
+ if (!products) return '
Loading products...
';
+ if (products.length === 0) return 'No products found.
';
+
+ const items = products.map(p => {
+ const image = p.images && p.images.length > 0
+ ? `
`
+ : '';
+
+ // 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 = `${gross.toFixed(2)} €
`;
+ 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 ? `${metaParts.join(' • ')}
` : '';
+
+ return `
+
+ ${image}
+
+ ${priceDisplay}
+
+ `;
+ }).join('');
+
+ return `${items}
`;
+ }
function updateCategoryProducts(id) {
const findAndReload = (nodes) => {
for (const node of nodes) {
@@ -971,6 +1034,7 @@
const li = document.createElement('li');
li.className = 'product-item';
+ // Image
if (p.images && p.images.length > 0) {
const img = document.createElement('img');
img.className = 'product-image';
@@ -981,14 +1045,46 @@
li.appendChild(img);
}
- const span = document.createElement('span');
- span.className = 'product-name-link';
- span.textContent = p.cName;
- span.onclick = (e) => {
+ // Product details container
+ const detailsDiv = document.createElement('div');
+ detailsDiv.className = 'product-details';
+
+ // 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();
- 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);
});
diff --git a/src/services/category-products-data-fetcher.js b/src/services/category-products-data-fetcher.js
index 7c38a1c..15b9a5c 100644
--- a/src/services/category-products-data-fetcher.js
+++ b/src/services/category-products-data-fetcher.js
@@ -18,10 +18,18 @@ export class ProductDataFetcher {
SELECT
ka.kKategorie,
ka.kArtikel,
- ab.cName
+ ab.cName,
+ a.cArtNr,
+ a.fVKNetto,
+ a.cBarcode,
+ a.kSteuerklasse,
+ sk.cName AS cSteuerklasseName,
+ ss.fSteuersatz
FROM tkategorieartikel ka
JOIN tArtikelBeschreibung ab ON ka.kArtikel = ab.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}
AND a.cAktiv = 'Y'
AND ab.kPlattform = ${process.env.JTL_PLATTFORM_ID}
diff --git a/src/syncers/category-products-syncer.js b/src/syncers/category-products-syncer.js
index 8a648eb..c0a654f 100644
--- a/src/syncers/category-products-syncer.js
+++ b/src/syncers/category-products-syncer.js
@@ -85,6 +85,10 @@ class CategoryProductsSyncer extends EventEmitter {
productsByCategory[record.kKategorie].push({
kArtikel: record.kArtikel,
cName: record.cName,
+ cArtNr: record.cArtNr,
+ fVKNetto: record.fVKNetto,
+ cBarcode: record.cBarcode,
+ fSteuersatz: record.fSteuersatz,
images: images
});
}