From 79a1b722925bc6c240796d4069063fa091c252b4 Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 8 Oct 2026 04:32:13 +0200 Subject: [PATCH] u --- src/api/routes/company.js | 3 +- src/services/companyImport.js | 86 ++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index 2eb21ae..8fed2ee 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -474,7 +474,8 @@ export function register(app, basePath = '') { } try { - fs.rmSync(importDir, { recursive: true, force: true }); + // Do not clean the import upload directory so files remain available for inspection/analysis + // fs.rmSync(importDir, { recursive: true, force: true }); fs.mkdirSync(importDir, { recursive: true }); // Preserve the raw zip archive diff --git a/src/services/companyImport.js b/src/services/companyImport.js index aa487c2..96208dc 100644 --- a/src/services/companyImport.js +++ b/src/services/companyImport.js @@ -1,6 +1,9 @@ import fs from 'fs'; import path from 'path'; import { getStore } from '../db/store.js'; +import { createLogger } from '../util/logger.js'; + +const log = createLogger('import'); /** * Parses RFC 4180 CSV text into an array of objects keyed by header. @@ -254,7 +257,6 @@ export async function executeCompanyImport({ // Fallback: Haupt category ID for unmapped items const hauptDashId = jtlCatIdToDashCatId.get('1') || rootCategoryId; - // ─── Phase 2: Article Import Pass 1 (Create or Update) ─────────────────── const includedArticles = artRecords.filter((a) => { let cats = []; try { @@ -268,8 +270,63 @@ export async function executeCompanyImport({ return true; }); + // ─── Phase 2: Brand (Hersteller) Import ────────────────────────────────── + const brandMap = new Map(); // brandName -> Set of picture relative paths + for (const a of includedArticles) { + const hersteller = String(a.Hersteller || '').trim(); + if (!hersteller) continue; + if (!brandMap.has(hersteller)) { + brandMap.set(hersteller, new Set()); + } + const rawPics = String(a.manufacturerPictureFiles || '').trim(); + if (rawPics) { + for (const p of rawPics.split(/[;,]/).map((s) => s.trim()).filter(Boolean)) { + brandMap.get(hersteller).add(p); + } + } + } + + const brandNameToId = new Map(); + let createdBrandCount = 0; + let brandPicCount = 0; + const brandEntries = Array.from(brandMap.entries()); + log.info(`Processing ${brandEntries.length} brands (Hersteller)...`); + + for (let i = 0; i < brandEntries.length; i++) { + const [brandName, picSet] = brandEntries[i]; + try { + const brand = await store.brand.upsert(googleAuthId, { name: brandName }); + brandNameToId.set(brandName.toLowerCase(), brand.id); + createdBrandCount++; + + // If brand doesn't have an image yet, import from manufacturerPictureFiles + if ((!brand.pics || brand.pics.length === 0) && picSet.size > 0) { + for (const picRel of picSet) { + const picPath = path.resolve(importDir, picRel); + if (fs.existsSync(picPath)) { + try { + const imgBuf = fs.readFileSync(picPath); + await store.brand.addImage(googleAuthId, brand.id, imgBuf); + brandPicCount++; + break; // 1 primary logo per brand + } catch (err) { + log.warn(`Brand image error for "${brandName}" (${picRel}):`, err.message); + } + } + } + } + } catch (err) { + log.warn(`Failed to upsert brand "${brandName}":`, err.message); + } + + onProgress('brands', i + 1, brandEntries.length, brandName); + } + + log.info(`Brands resolved: ${brandNameToId.size}/${brandEntries.length}, brand images: ${brandPicCount}`); + + // ─── Phase 3: Article Import Pass 1 (Create or Update) ─────────────────── const totalArticles = includedArticles.length; - console.log(`[Import] Articles to process: ${totalArticles}`); + log.info(`Articles to process: ${totalArticles}`); onProgress('articles', 0, totalArticles, 'Starting articles import'); let articlesImported = 0; @@ -355,6 +412,17 @@ export async function executeCompanyImport({ } } + // Update brand if provided + const hersteller = String(a.Hersteller || '').trim(); + const brandId = hersteller ? (brandNameToId.get(hersteller.toLowerCase()) || null) : null; + if (brandId && existingArticle.brand?.id !== brandId) { + try { + await store.article.setBrandIdBySku(googleAuthId, sku, brandId); + } catch (bErr) { + log.warn(`Could not set brand for ${sku}:`, bErr.message); + } + } + articlesUpdated++; } else { // Create new article @@ -385,6 +453,17 @@ export async function executeCompanyImport({ } } + // Set brand if provided + const hersteller = String(a.Hersteller || '').trim(); + const brandId = hersteller ? (brandNameToId.get(hersteller.toLowerCase()) || null) : null; + if (brandId) { + try { + await store.article.setBrandIdBySku(googleAuthId, sku, brandId); + } catch (bErr) { + log.warn(`Could not set brand for ${sku}:`, bErr.message); + } + } + articlesImported++; } } catch (err) { @@ -470,6 +549,9 @@ export async function executeCompanyImport({ categories: jtlCatIdToDashCatId.size, categoriesCreated: createdCatCount, categoryPictures: catPicCount, + brands: brandNameToId.size, + brandsCreated: createdBrandCount, + brandPictures: brandPicCount, articlesTotal: totalArticles, articlesImported, articlesUpdated,