diff options
| author | ertopogo <erwin.t.pombett@gmail.com> | 2026-02-05 23:48:50 +0100 |
|---|---|---|
| committer | ertopogo <erwin.t.pombett@gmail.com> | 2026-02-05 23:48:50 +0100 |
| commit | aa494fd8e24b0aabcc890655370a15358f9e6755 (patch) | |
| tree | 2eaf2d2c01624e8af6ede0faaef1cf3a0366f2b0 /backend/scripts | |
| parent | 9ca55d25b61bad0b9d875e78567adf6739f62937 (diff) | |
WIP: prepare import csvdevelop
Diffstat (limited to 'backend/scripts')
| -rw-r--r-- | backend/scripts/import-products.js | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/backend/scripts/import-products.js b/backend/scripts/import-products.js new file mode 100644 index 0000000..04b24aa --- /dev/null +++ b/backend/scripts/import-products.js @@ -0,0 +1,250 @@ +const fs = require("fs");
+const path = require("path");
+const express = require("express");
+const { parse } = require("csv-parse/sync");
+const loaders = require("@medusajs/medusa/dist/loaders/index").default;
+
+class CsvProductImporter {
+ constructor({ filePath, reportPath, dryRun, defaultCurrency }) {
+ this.filePath = filePath;
+ this.reportPath = reportPath;
+ this.dryRun = dryRun;
+ this.defaultCurrency = defaultCurrency;
+ this.report = {
+ file: filePath,
+ dryRun,
+ startedAt: new Date().toISOString(),
+ finishedAt: null,
+ totals: {
+ processed: 0,
+ created: 0,
+ updated: 0,
+ failed: 0,
+ },
+ errors: [],
+ };
+ }
+
+ async run() {
+ this.assertFileExists();
+ const rows = this.parseCsv();
+ const productGroups = this.groupByExternalId(rows);
+
+ const { container, dbConnection } = await this.initMedusa();
+ const productService = container.resolve("productService");
+
+ for (const productData of productGroups) {
+ try {
+ await this.upsertProduct(productService, productData);
+ this.report.totals.processed += 1;
+ } catch (error) {
+ this.report.totals.failed += 1;
+ this.report.errors.push({
+ external_id: productData.external_id,
+ message: error.message,
+ });
+ }
+ }
+
+ this.report.finishedAt = new Date().toISOString();
+ this.writeReport();
+ await dbConnection.close();
+ }
+
+ assertFileExists() {
+ if (!fs.existsSync(this.filePath)) {
+ throw new Error(`CSV introuvable: ${this.filePath}`);
+ }
+ }
+
+ parseCsv() {
+ const input = fs.readFileSync(this.filePath, "utf-8");
+ return parse(input, {
+ columns: true,
+ skip_empty_lines: true,
+ trim: true,
+ }).map((row) => this.normalizeRow(row));
+ }
+
+ normalizeRow(row) {
+ const toBool = (value, defaultValue) => {
+ if (value === undefined || value === "") {
+ return defaultValue;
+ }
+ const normalized = String(value).toLowerCase();
+ return ["true", "1", "yes", "y"].includes(normalized);
+ };
+
+ return {
+ external_id: row.external_id || "",
+ title: row.title || "",
+ handle: row.handle || this.slugify(row.title || ""),
+ description: row.description || "",
+ thumbnail: row.thumbnail || "",
+ option_title: row.option_title || "Taille",
+ option_value: row.option_value || row.variant_title || "Default",
+ variant_title: row.variant_title || row.option_value || "Default",
+ variant_sku: row.variant_sku || "",
+ price_amount: Number.parseInt(row.price_amount, 10) || 0,
+ currency_code: row.currency_code || this.defaultCurrency,
+ inventory_quantity: Number.parseInt(row.inventory_quantity, 10) || 0,
+ manage_inventory: toBool(row.manage_inventory, true),
+ };
+ }
+
+ groupByExternalId(rows) {
+ const groups = new Map();
+
+ for (const row of rows) {
+ if (!row.external_id) {
+ throw new Error("Champ requis manquant: external_id");
+ }
+ if (!row.title) {
+ throw new Error(`Champ requis manquant: title (external_id=${row.external_id})`);
+ }
+
+ const existing = groups.get(row.external_id);
+ const variant = {
+ title: row.variant_title,
+ sku: row.variant_sku || undefined,
+ prices: [
+ {
+ currency_code: row.currency_code,
+ amount: row.price_amount,
+ },
+ ],
+ options: [
+ {
+ value: row.option_value,
+ },
+ ],
+ inventory_quantity: row.inventory_quantity,
+ manage_inventory: row.manage_inventory,
+ };
+
+ if (!existing) {
+ groups.set(row.external_id, {
+ external_id: row.external_id,
+ title: row.title,
+ handle: row.handle,
+ description: row.description,
+ thumbnail: row.thumbnail || undefined,
+ options: [{ title: row.option_title }],
+ variants: [variant],
+ });
+ } else {
+ if (existing.options[0].title !== row.option_title) {
+ throw new Error(
+ `option_title incoherent pour external_id=${row.external_id} (${existing.options[0].title} vs ${row.option_title})`
+ );
+ }
+ existing.variants.push(variant);
+ }
+ }
+
+ return Array.from(groups.values());
+ }
+
+ async initMedusa() {
+ const app = express();
+ const directory = path.resolve(__dirname, "..");
+ return loaders({ directory, expressApp: app });
+ }
+
+ async upsertProduct(productService, productData) {
+ const existing = await this.findByExternalId(productService, productData.external_id);
+ const action = existing ? "update" : "create";
+
+ if (this.dryRun) {
+ console.log(`[dry-run] ${action} product external_id=${productData.external_id}`);
+ if (existing) {
+ this.report.totals.updated += 1;
+ } else {
+ this.report.totals.created += 1;
+ }
+ return;
+ }
+
+ if (existing) {
+ await productService.update(existing.id, productData);
+ this.report.totals.updated += 1;
+ return;
+ }
+
+ await productService.create(productData);
+ this.report.totals.created += 1;
+ }
+
+ async findByExternalId(productService, externalId) {
+ const results = await productService.list({ external_id: externalId }, { take: 1 });
+ return results[0] || null;
+ }
+
+ writeReport() {
+ if (!this.reportPath) {
+ return;
+ }
+ fs.writeFileSync(this.reportPath, JSON.stringify(this.report, null, 2), "utf-8");
+ }
+
+ slugify(value) {
+ return String(value)
+ .toLowerCase()
+ .trim()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-+|-+$/g, "");
+ }
+}
+
+function parseArgs(argv) {
+ const args = {
+ file: null,
+ report: null,
+ dryRun: false,
+ currency: "eur",
+ };
+
+ for (let i = 0; i < argv.length; i += 1) {
+ const current = argv[i];
+ if (current === "--file") {
+ args.file = argv[i + 1];
+ i += 1;
+ } else if (current === "--report") {
+ args.report = argv[i + 1];
+ i += 1;
+ } else if (current === "--dry-run") {
+ args.dryRun = true;
+ } else if (current === "--currency") {
+ args.currency = argv[i + 1];
+ i += 1;
+ }
+ }
+
+ return args;
+}
+
+async function main() {
+ const directory = path.resolve(__dirname, "..");
+ const args = parseArgs(process.argv.slice(2));
+ const filePath = args.file
+ ? path.resolve(directory, args.file)
+ : path.resolve(directory, "data", "products-import.csv");
+ const reportPath = args.report
+ ? path.resolve(directory, args.report)
+ : path.resolve(directory, "data", "import-report.json");
+
+ const importer = new CsvProductImporter({
+ filePath,
+ reportPath,
+ dryRun: args.dryRun,
+ defaultCurrency: args.currency,
+ });
+
+ await importer.run();
+ console.log("Import termine.");
+}
+
+main().catch((error) => {
+ console.error("Echec import:", error);
+ process.exitCode = 1;
+});
|
