feat(commerce-backend): importador masivo de productos via CSV
Nueva pagina "Importacion" en Medusa Admin para cargar varios productos de una sola vez (comprados en Amazon/eBay/etc, no dropshipping automatizado -- se evaluo y descarto raspar catalogos de terceros sin autorizacion por riesgo de copyright/ToS). src/api/admin/product-import/route.ts: GET sirve la plantilla CSV descargable, POST procesa fila por fila (una foto rota o categoria mal escrita no tumba el resto del archivo). Cada foto se descarga del origen y se re-aloja en el mismo MinIO/S3 del NAS que ya usa el catalogo (prefijo import-products/, separado de ari-products/), se crea el registro de inventario con la cantidad indicada y el producto queda vinculado con el mismo patron "Presentacion"/"Estandar" que usa el resto del catalogo -- el storefront no necesita cambios. src/lib/csv.ts: parser CSV propio (RFC4180), sin sumar dependencia nueva. Workflows y servicios de modulo (uploadFilesWorkflow, createInventoryItemsWorkflow, createProductsWorkflow, stock-location/ fulfillment/sales-channel/product module services) verificados contra los .d.ts reales instalados, no solo contra documentacion.
This commit is contained in:
1 parent
1dccd0aa6a
commit
1cd1078cdf
3 files changed
+435
No files matched your search
@@ -0,0 +1,70 @@
|
||||
// Parser CSV minimo (RFC4180: campos entre comillas, comillas escapadas
|
||||
// como "", comas y saltos de linea dentro de campos citados) -- se evita
|
||||
// sumar una dependencia nueva (papaparse/csv-parse) para un formato que
|
||||
// nosotros mismos controlamos via la plantilla descargable.
|
||||
export function parseCsv(text: string): string[][] {
|
||||
const rows: string[][] = [];
|
||||
let row: string[] = [];
|
||||
let field = "";
|
||||
let inQuotes = false;
|
||||
let i = 0;
|
||||
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||
|
||||
while (i < normalized.length) {
|
||||
const char = normalized[i];
|
||||
if (inQuotes) {
|
||||
if (char === '"') {
|
||||
if (normalized[i + 1] === '"') {
|
||||
field += '"';
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
inQuotes = false;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
field += char;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (char === '"') {
|
||||
inQuotes = true;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (char === ",") {
|
||||
row.push(field);
|
||||
field = "";
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (char === "\n") {
|
||||
row.push(field);
|
||||
rows.push(row);
|
||||
row = [];
|
||||
field = "";
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
field += char;
|
||||
i += 1;
|
||||
}
|
||||
if (field.length > 0 || row.length > 0) {
|
||||
row.push(field);
|
||||
rows.push(row);
|
||||
}
|
||||
return rows.filter((r) => r.some((cell) => cell.trim().length > 0));
|
||||
}
|
||||
|
||||
export function csvRowsToObjects(rows: string[][]): Record<string, string>[] {
|
||||
const [header, ...body] = rows;
|
||||
if (!header) return [];
|
||||
const keys = header.map((h) => h.trim().toLowerCase());
|
||||
return body.map((cols) => {
|
||||
const obj: Record<string, string> = {};
|
||||
keys.forEach((key, index) => {
|
||||
obj[key] = (cols[index] ?? "").trim();
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user