222 lines
7.5 KiB
JavaScript
222 lines
7.5 KiB
JavaScript
// MARK: - Imports
|
|
// #region Imports
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { getStore } from '../db/store.js';
|
|
import { createLogger } from '../util/logger.js';
|
|
|
|
import { cleanValue, parseGermanFloat, parseCsvRecords } from './companyImport/csvParser.js';
|
|
import { computeSkeleHash } from './companyImport/imageHasher.js';
|
|
import { normalizeAttributeName, extractVariantValue } from './companyImport/variantHelpers.js';
|
|
import { createProgressTracker } from './companyImport/progressTracker.js';
|
|
import { filterCatalogRecords, prepareImportTasks } from './companyImport/catalogPreprocessor.js';
|
|
import { importCategories } from './companyImport/categoryImporter.js';
|
|
import { importBrands } from './companyImport/brandImporter.js';
|
|
import { importArticles } from './companyImport/articleImporter.js';
|
|
import { importVariants } from './companyImport/variantImporter.js';
|
|
import { importPfand, extractPfandValue, formatPfandSku, formatPfandName } from './companyImport/pfandImporter.js';
|
|
import { importArticleImages } from './companyImport/articleImageImporter.js';
|
|
import { importOrders } from './companyImport/orderImporter.js';
|
|
// #endregion
|
|
|
|
// MARK: - Re-exports
|
|
// #region Re-exports
|
|
export {
|
|
cleanValue,
|
|
parseGermanFloat,
|
|
parseCsvRecords,
|
|
computeSkeleHash,
|
|
normalizeAttributeName,
|
|
extractVariantValue,
|
|
extractPfandValue,
|
|
formatPfandSku,
|
|
formatPfandName,
|
|
importPfand,
|
|
};
|
|
// #endregion
|
|
|
|
const log = createLogger('import');
|
|
|
|
// MARK: - Company Import Orchestrator
|
|
// #region Company Import Orchestrator
|
|
|
|
/**
|
|
* Executes full catalog import from preserved files in importDir.
|
|
*
|
|
* @param {object} opts
|
|
* @param {number} opts.googleAuthId
|
|
* @param {number} opts.companyId
|
|
* @param {string} opts.importDir
|
|
* @param {function} [opts.onProgress] - (counts) => void; throttled to 250ms
|
|
* @param {function} [opts.onImages] - (skelehashes: string[]) => void; batched every 50ms
|
|
* @param {number} [opts.progressThrottleMs=250] - Progress emission throttle/debounce interval in ms
|
|
* @returns {Promise<object>} Summary outcome with statistics
|
|
*/
|
|
export async function executeCompanyImport({
|
|
googleAuthId,
|
|
companyId,
|
|
importDir,
|
|
onProgress = () => {},
|
|
onImages = () => {},
|
|
progressThrottleMs = 250,
|
|
}) {
|
|
const store = await getStore();
|
|
|
|
// MARK: - Verify and Read Input Files
|
|
// #region Read CSVs
|
|
const catCsvPath = path.join(importDir, 'categories.csv');
|
|
const artCsvPath = path.join(importDir, 'articles.csv');
|
|
|
|
if (!fs.existsSync(catCsvPath) || !fs.existsSync(artCsvPath)) {
|
|
throw new Error('Import files missing: categories.csv and articles.csv are required');
|
|
}
|
|
|
|
const catRecords = parseCsvRecords(fs.readFileSync(catCsvPath, 'utf8'));
|
|
const artRecords = parseCsvRecords(fs.readFileSync(artCsvPath, 'utf8'));
|
|
log.info(`Read ${catRecords.length} category rows and ${artRecords.length} article rows`);
|
|
// #endregion
|
|
|
|
// MARK: - Filter Records & Prepare Tasks
|
|
// #region Preprocessing & Task Discovery
|
|
const { includedCategories, includedArticles } = filterCatalogRecords(catRecords, artRecords);
|
|
|
|
const {
|
|
categoryImageTasks,
|
|
brandMap,
|
|
brandImageTasks,
|
|
articleImageTasks,
|
|
totalImagesCount,
|
|
manRecords,
|
|
itemRecords,
|
|
totalReceiptsCount,
|
|
} = prepareImportTasks({ importDir, includedCategories, includedArticles });
|
|
// #endregion
|
|
|
|
// MARK: - Progress Tracker Initialization
|
|
// #region Progress Setup
|
|
const progressTracker = createProgressTracker({
|
|
initialCounts: {
|
|
categoriestotal: includedCategories.length,
|
|
categoriesdone: 0,
|
|
articlestotal: includedArticles.length,
|
|
articlesdone: 0,
|
|
imagestotal: totalImagesCount,
|
|
imagesdone: 0,
|
|
receiptstotal: totalReceiptsCount,
|
|
receiptsdone: 0,
|
|
},
|
|
onProgress,
|
|
onImages,
|
|
progressThrottleMs,
|
|
});
|
|
|
|
// Emit baseline counts immediately
|
|
progressTracker.emitProgress(true);
|
|
// #endregion
|
|
|
|
// MARK: - Phase 1: Categories
|
|
// #region Phase 1: Categories
|
|
const categoriesResult = await importCategories({
|
|
store,
|
|
googleAuthId,
|
|
includedCategories,
|
|
categoryImageTasks,
|
|
progressTracker,
|
|
});
|
|
// #endregion
|
|
|
|
// MARK: - Phase 2: Brands
|
|
// #region Phase 2: Brands
|
|
const brandsResult = await importBrands({
|
|
store,
|
|
googleAuthId,
|
|
brandMap,
|
|
brandImageTasks,
|
|
progressTracker,
|
|
});
|
|
// #endregion
|
|
|
|
// MARK: - Phase 3: Articles (base data, variants, pfand, pictures)
|
|
// #region Phase 3: Articles
|
|
const articlesResult = await importArticles({
|
|
store,
|
|
googleAuthId,
|
|
includedArticles,
|
|
articleImageTasks,
|
|
jtlCatIdToDashCatId: categoriesResult.jtlCatIdToDashCatId,
|
|
hauptDashId: categoriesResult.hauptDashId,
|
|
brandNameToId: brandsResult.brandNameToId,
|
|
progressTracker,
|
|
});
|
|
// #endregion
|
|
|
|
// MARK: - Phase 4: Orders / Receipts
|
|
// #region Phase 4: Orders / Receipts
|
|
const allArtIdToSku = new Map();
|
|
const parentSkus = new Set(articlesResult.parentSkus || []);
|
|
const parentIds = new Set();
|
|
for (const a of artRecords) {
|
|
const kArt = cleanValue(a.id ?? a.kArtikel);
|
|
const sku = cleanValue(a.sku ?? a.cArtNr);
|
|
if (kArt && sku) allArtIdToSku.set(kArt, sku);
|
|
|
|
const pid = cleanValue(a.parentid ?? a.kVaterArtikel);
|
|
if (pid && pid !== '0') parentIds.add(pid);
|
|
const isParent = a.is_parent === '1' || a.nIstVater === '1';
|
|
if (isParent) {
|
|
if (kArt) parentIds.add(kArt);
|
|
if (sku) parentSkus.add(sku);
|
|
}
|
|
}
|
|
for (const a of artRecords) {
|
|
const kArt = cleanValue(a.id ?? a.kArtikel);
|
|
if (kArt && parentIds.has(kArt)) {
|
|
const sku = cleanValue(a.sku ?? a.cArtNr);
|
|
if (sku) parentSkus.add(sku);
|
|
}
|
|
}
|
|
|
|
const ordersResult = await importOrders({
|
|
store,
|
|
googleAuthId,
|
|
manRecords,
|
|
itemRecords,
|
|
jtlArtIdToSku: allArtIdToSku,
|
|
parentSkus,
|
|
progressTracker,
|
|
});
|
|
// #endregion
|
|
|
|
// Flush any pending images and progress updates
|
|
progressTracker.flushSkeleHashes();
|
|
progressTracker.emitProgress(true);
|
|
|
|
return {
|
|
ok: true,
|
|
message: 'Import erfolgreich abgeschlossen.',
|
|
stats: {
|
|
categories: categoriesResult.jtlCatIdToDashCatId.size,
|
|
categoriesCreated: categoriesResult.createdCatCount,
|
|
categoryPictures: categoriesResult.catPicCount,
|
|
brands: brandsResult.brandNameToId.size,
|
|
brandsCreated: brandsResult.createdBrandCount,
|
|
brandPictures: brandsResult.brandPicCount,
|
|
articlesTotal: includedArticles.length,
|
|
articlesImported: articlesResult.articlesImported,
|
|
articlesUpdated: articlesResult.articlesUpdated,
|
|
variantsLinked: articlesResult.variantsLinked,
|
|
pfandArticlesCreated: articlesResult.pfandArticlesCreated,
|
|
pfandArticlesUpdated: articlesResult.pfandArticlesUpdated,
|
|
pfandOptionsLinked: articlesResult.pfandOptionsLinked,
|
|
articlePictures: articlesResult.totalImagesImported,
|
|
ordersTotal: totalReceiptsCount,
|
|
ordersImported: ordersResult.ordersImported,
|
|
ordersSkipped: ordersResult.ordersSkipped,
|
|
paymentsImported: ordersResult.paymentsImported,
|
|
shippingsImported: ordersResult.shippingsImported,
|
|
},
|
|
};
|
|
}
|
|
|
|
// #endregion
|