// MARK: - Imports // #region Imports import fs from 'fs'; import { cleanValue } from './csvParser.js'; import { computeSkeleHash } from './imageHasher.js'; import { createLogger } from '../../util/logger.js'; // #endregion const log = createLogger('import'); // MARK: - Category Importer // #region Category Importer /** * Imports category tree and attaches category images. * * @param {object} params * @param {object} params.store - Store singleton * @param {number} params.googleAuthId * @param {Array>} params.includedCategories * @param {Array} params.categoryImageTasks * @param {object} params.progressTracker * @returns {Promise<{ jtlCatIdToDashCatId: Map, hauptDashId: number, createdCatCount: number, catPicCount: number }>} */ export async function importCategories({ store, googleAuthId, includedCategories, categoryImageTasks, progressTracker, }) { const treeResult = await store.category.getTreeForCompany(googleAuthId); const existingTree = treeResult.tree || []; const rootCategory = existingTree.find((c) => c.parentId == null) || existingTree[0]; if (!rootCategory) { throw new Error('Company root category not found'); } const rootCategoryId = rootCategory.id; const existingMap = new Map(); for (const c of existingTree) { const key = `${c.parentId ?? 'root'}:${String(c.name || '').trim().toLowerCase()}`; existingMap.set(key, c.id); } const jtlCatIdToDashCatId = new Map(); jtlCatIdToDashCatId.set('0', rootCategoryId); // Group category images by JTL category ID const catImagesByJtlId = new Map(); for (const task of categoryImageTasks) { if (!catImagesByJtlId.has(task.catJtlId)) { catImagesByJtlId.set(task.catJtlId, []); } catImagesByJtlId.get(task.catJtlId).push(task); } const pendingCats = [...includedCategories]; let createdCatCount = 0; let catPicCount = 0; let safety = 0; while (pendingCats.length > 0 && safety < 10) { safety++; const remaining = []; for (const c of pendingCats) { const jtlId = String(c.id ?? c.kKategorie); const jtlParent = String(c.parentid ?? c.kOberKategorie ?? '0'); let name = cleanValue(c.name ?? c.cName); if (!name) { try { const tr = JSON.parse(c.translations || '[]'); name = tr[0]?.name || tr[0]?.cName || ''; } catch {} } if (!name) name = `Kategorie ${jtlId}`; let dashParentId; if (jtlParent === '0' || !jtlParent) { dashParentId = rootCategoryId; } else if (jtlCatIdToDashCatId.has(jtlParent)) { dashParentId = jtlCatIdToDashCatId.get(jtlParent); } else { remaining.push(c); continue; } const key = `${dashParentId}:${name.toLowerCase()}`; let dashCatId = existingMap.get(key); if (!dashCatId) { try { const created = await store.category.createUnderParent({ googleAuthId, parentId: dashParentId, name, }); dashCatId = created.id; existingMap.set(key, dashCatId); createdCatCount++; } catch (err) { log.warn(`Failed to create category "${name}":`, err.message); } } if (dashCatId) { jtlCatIdToDashCatId.set(jtlId, dashCatId); // Add category images const imgs = catImagesByJtlId.get(jtlId) || []; for (const imgTask of imgs) { try { const imgBuf = fs.readFileSync(imgTask.fullPath); await store.category.addImage(googleAuthId, dashCatId, imgBuf); catPicCount++; const skele = await computeSkeleHash(imgBuf); progressTracker.emitSkeleHash(skele?.base62 || null); } catch (err) { log.warn(`Category image error (${imgTask.relPath}):`, err.message); } progressTracker.counts.imagesdone++; } } progressTracker.counts.categoriesdone++; progressTracker.emitProgress(); } pendingCats.length = 0; pendingCats.push(...remaining); } progressTracker.flushSkeleHashes(); progressTracker.emitProgress(true); log.info(`Categories resolved: ${jtlCatIdToDashCatId.size}/${includedCategories.length}, created: ${createdCatCount}, images: ${catPicCount}`); const hauptDashId = jtlCatIdToDashCatId.get('1') || rootCategoryId; return { jtlCatIdToDashCatId, hauptDashId, createdCatCount, catPicCount, }; } // #endregion