From f8643458225e790e2a2eabd1e413f48cfeb82389 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 2 Dec 2025 12:21:08 +0530 Subject: [PATCH] Bug fixed - company profile bulk upload and establishment use restriction email removed --- app/controllers/establishment.controller.js | 1624 ++--------------- app/uploads/company_profile_upload_sample.csv | 2 +- 2 files changed, 138 insertions(+), 1488 deletions(-) diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index 0ae354d..fe16f07 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -16,6 +16,8 @@ const logger = require("../services/logger"); const fs = require("fs"); const csv = require("csv-parser"); const path = require("path"); +const { version } = require("os"); +const sequelize = db.sequelize; exports.testEmail = async (req, res) => { placeHolderData = { @@ -586,13 +588,13 @@ exports.updateEstablishment = async (req, res) => { const { id } = req.params; const { establishment_products, establishment_user, ...estData } = req.body; const user = await EstablishmentUser.findOne({where:{establishment_id:id}}); - console.log("Establishment User email: ",user.email) - if (user?.email === 'bhavinkumar.chandulal@fcsc.gov.ae') { - return res.status(403).json({ - status: "error", - message: "You cannot modify Super Admin data." - }); - } + // console.log("Establishment User email: ",user.email) + // if (user?.email === 'bhavinkumar.chandulal@fcsc.gov.ae') { + // return res.status(403).json({ + // status: "error", + // message: "You cannot modify Super Admin data." + // }); + // } const resolveActionDoneBy = (item) => { if (item?.action_done_by) return item.action_done_by; @@ -1086,7 +1088,13 @@ const GENERIC_ERROR_MSG = "Invalid data. Please check the instructions given and upload again."; exports.establishmentBulkUpload = async (req, res) => { + let transaction = null; + let filePath = null; + try { + // ------------------------------------------------------- + // FILE VALIDATION + // ------------------------------------------------------- if (!req.file) { return res.status(400).send({ status: "failed", @@ -1094,7 +1102,7 @@ exports.establishmentBulkUpload = async (req, res) => { }); } - const filePath = req.file.path; + filePath = req.file.path; if (!req.file.originalname.toLowerCase().endsWith(".csv")) { fs.unlinkSync(filePath); @@ -1116,10 +1124,16 @@ exports.establishmentBulkUpload = async (req, res) => { const row = {}; for (const key in rawRow) { + // Remove asterisks and special chars, normalize to lowercase with underscores const normalizedKey = key - .replace(/[\s\W]+/g, "_") - .trim() - .toLowerCase(); + .replace(/\*/g, "") // Remove asterisks + .replace(/\([^)]*\)/g, "") // Remove content in parentheses like (M or (W + .trim() // Remove leading/trailing spaces + .replace(/[\/\-\s]+/g, "_") // Replace /, -, and spaces with underscore + .replace(/[^\w]+/g, "") // Remove remaining special characters + .toLowerCase() + .replace(/_{2,}/g, "_") // Replace multiple underscores with single + .replace(/^_+|_+$/g, ""); // Remove leading/trailing underscores row[normalizedKey] = rawRow[key]?.trim() || ""; } @@ -1179,17 +1193,26 @@ exports.establishmentBulkUpload = async (req, res) => { }); const existing = await Establishment.findAll({ - attributes: ["establishment_code", "factory_name", "establishment_contact_email"] + attributes: [ + "establishment_code", + "factory_name", + "establishment_contact_email", + "industry_code_production" + ] }); const existingEstSet = new Set(existing.map(e => e.establishment_code)); const existingFactorySet = new Set(existing.map(e => e.factory_name.toLowerCase())); const existingEmailSet = new Set(existing.map(e => e.establishment_contact_email.toLowerCase())); + const existingIndustryCodeSet = new Set( + existing.map(e => e.industry_code_production).filter(Boolean) + ); // FILE duplicate trackers const fileEstSet = new Set(); const fileFactorySet = new Set(); const fileEmailSet = new Set(); + const fileIndustryCodeSet = new Set(); const errors = []; const prepared = []; @@ -1241,6 +1264,20 @@ exports.establishmentBulkUpload = async (req, res) => { continue; } + // Check industry_code_production duplicates (if provided) + const industryCodeProd = r.industry_code_current_production?.trim(); + if (industryCodeProd) { + if (fileIndustryCodeSet.has(industryCodeProd)) { + errors.push({ row: rowNum, error: `Duplicate Industry Code (Current Production) in file: ${industryCodeProd}` }); + continue; + } + if (existingIndustryCodeSet.has(industryCodeProd)) { + errors.push({ row: rowNum, error: `Industry Code (Current Production) already exists in database: ${industryCodeProd}` }); + continue; + } + fileIndustryCodeSet.add(industryCodeProd); + } + fileEstSet.add(estKey); fileFactorySet.add(factoryKey); fileEmailSet.add(emailKey); @@ -1330,8 +1367,10 @@ exports.establishmentBulkUpload = async (req, res) => { } // ------------------------------------------------------- - // PHASE 3: INSERT INTO DATABASE + // PHASE 3: START TRANSACTION AND INSERT INTO DATABASE // ------------------------------------------------------- + transaction = await sequelize.transaction(); + for (const p of prepared) { const r = p.r; @@ -1372,7 +1411,7 @@ exports.establishmentBulkUpload = async (req, res) => { Number(r.number_of_non_emirati_female || 0), created_by: req.user.id - }); + }, { transaction }); const autoPassword = Math.random().toString(36).slice(-10); const hashed = await bcrypt.hash(autoPassword, 10); @@ -1383,17 +1422,33 @@ exports.establishmentBulkUpload = async (req, res) => { email: p.email, password: hashed, created_by: req.user.id - }); + }, { transaction }); + + const placeHolderData = { + contact_name: r.user_name, + portal_url: process.env.FE_BASE_URL, + username: p.email, + password: autoPassword, + support_email: process.env.SUPPORT_EMAIL, + support_phone: process.env.SUPPORT_PHONE + }; + await sendEmailService(p.email, "establishment_user_creation_to_user", placeHolderData); await EstablishmentProduct.bulkCreate( p.productIds.map(pid => ({ establishment_id: est.id, product_id: pid, created_by: req.user.id - })) + })), + { transaction } ); } + // COMMIT TRANSACTION - All inserts successful + await transaction.commit(); + transaction = null; // Set to null after commit + + // Clean up file after successful commit fs.unlinkSync(filePath); return res.status(200).send({ @@ -1407,10 +1462,74 @@ exports.establishmentBulkUpload = async (req, res) => { }); } catch (err) { - logger.error("Fatal Error: " + err.message); + // ROLLBACK TRANSACTION if it exists + if (transaction) { + try { + await transaction.rollback(); + logger.error("Transaction rolled back successfully due to error"); + } catch (rollbackErr) { + logger.error("Rollback error: " + rollbackErr.message); + } + } + + // Clean up file if it exists + if (filePath && fs.existsSync(filePath)) { + try { + fs.unlinkSync(filePath); + } catch (unlinkErr) { + logger.error("File cleanup error: " + unlinkErr.message); + } + } + + // Log detailed error information + logger.error("Fatal Error in bulk upload: " + err.message); + + if (err.name) { + logger.error("Error Name: " + err.name); + } + + // Handle Sequelize validation errors + if (err.name === "SequelizeUniqueConstraintError" || err.name === "SequelizeValidationError") { + logger.error("Validation Errors:"); + + if (err.errors && Array.isArray(err.errors)) { + err.errors.forEach(validationError => { + logger.error(` - Field: ${validationError.path}, Value: ${validationError.value}, Message: ${validationError.message}`); + }); + } + + return res.status(400).send({ + status: "failed", + message: "Database validation error: " + (err.errors?.[0]?.message || err.message), + errors: err.errors?.map(e => ({ + field: e.path, + value: e.value, + message: e.message + })) + }); + } + + // Handle foreign key constraint errors + if (err.name === "SequelizeForeignKeyConstraintError") { + logger.error("Foreign Key Constraint Error: " + err.message); + + return res.status(400).send({ + status: "failed", + message: "Foreign key constraint error. Please check your reference data.", + error: err.message + }); + } + + // Log stack trace in development + if (process.env.NODE_ENV === 'development') { + logger.error("Stack trace: " + err.stack); + } + + // Generic error response return res.status(500).send({ status: "failed", - message: "Unexpected error occurred." + message: "Unexpected error occurred during bulk upload", + error: err.message }); } }; @@ -1424,1472 +1543,3 @@ exports.downloadCompanyProfileSample = async (req, res) => { } }; - - -// exports.establishmentBulkUpload = async (req, res) => { -// try { -// if (!req.file) { -// return res.status(400).send({ -// status: "failed", -// message: "No file uploaded." -// }); -// } - -// const filePath = req.file.path; - -// if (!req.file.originalname.toLowerCase().endsWith(".csv")) { -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Invalid file type. Only CSV allowed." -// }); -// } - -// const rows = []; - -// // ------------------------------------------------------- -// // READ CSV AND SKIP EMPTY ROWS -// // ------------------------------------------------------- -// await new Promise((resolve, reject) => { -// fs.createReadStream(filePath) -// .pipe(csv()) -// .on("data", (rawRow) => { -// const row = {}; - -// // Normalize headers -// for (const key in rawRow) { -// const normalizedKey = key.replace(/[\s\W]+/g, "_") -// .trim() -// .toLowerCase(); - -// row[normalizedKey] = rawRow[key]?.trim() || ""; -// } - -// const cleanedValues = Object.values(row).map(v =>(v || "").replace(/\s+/g, "").trim()); -// const isEmpty = cleanedValues.every(v => v === ""); -// if (isEmpty) return; - -// rows.push(row); -// }) -// .on("end", resolve) -// .on("error", reject); -// }); - -// if (rows.length === 0) { -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "CSV file is empty.", -// errors: [{ error: "Uploaded file is empty." }] -// }); -// } -// // ------------------------------------------------------- -// // REQUIRED HEADER CHECK -// // ------------------------------------------------------- -// const required = [ -// "establishment_id", -// "factory_name", -// "email", -// "emirate", -// "total_employment" -// ]; - -// const firstRowKeys = Object.keys(rows[0]); - -// const missing = required.filter(r => !firstRowKeys.includes(r)); -// if (missing.length > 0) { -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Invalid data. Please check the instructions given and upload again.", -// errors: [ -// { error: "Missing required columns: " + missing.join(", ") } -// ] -// }); -// } - -// // ------------------------------------------------------- -// // LOAD REFERENCE TABLES -// // ------------------------------------------------------- -// const emirates = await Emirate.findAll({ attributes: ["id", "name"] }); -// const emirateMap = {}; -// emirates.forEach(e => { -// emirateMap[e.name.trim().toLowerCase()] = e.id; -// }); - -// function normalizeHS(val) { -// if (!val) return ""; - -// return val -// .toString() -// .normalize("NFKD") -// .replace(/[^\d]/g, "") -// .trim(); -// } - -// const products = await Product.findAll({ attributes: ["id", "hs_code"] }); - -// const productMap = {}; - -// products.forEach(p => { -// const cleaned = normalizeHS(p.hs_code); - -// // Only store valid HS codes 1–10 digits -// if (cleaned.length >= 1 && cleaned.length <= 10) { -// productMap[cleaned] = p.id; -// } -// }); -// const existingEsts = await Establishment.findAll({ -// attributes: ["establishment_code", "factory_name", "establishment_contact_email"] -// }); - -// const existingEstIdSet = new Set(existingEsts.map(e => e.establishment_code)); -// const existingFactorySet = new Set(existingEsts.map(e => e.factory_name.toLowerCase())); -// const existingEmailSet = new Set(existingEsts.map(e => e.establishment_contact_email.toLowerCase())); - -// // ------------------------------------------------------- -// // FILE-LEVEL DUPLICATE TRACKING -// // ------------------------------------------------------- -// const errors = []; -// const fileEstSet = new Set(); -// const fileFactorySet = new Set(); -// const fileEmailSet = new Set(); - -// const prepared = []; - -// // ------------------------------------------------------- -// // VALIDATE EACH ROW -// // ------------------------------------------------------- -// for (let i = 0; i < rows.length; i++) { -// const r = rows[i]; -// const rowNum = i + 2; - -// const est = r.establishment_id; -// const factory = r.factory_name; -// const email = r.email; -// const emirate = r.emirate; -// const employment = r.total_employment; - -// // Required validation -// const missingFields = []; - -// if (!est) missingFields.push("Establishment ID"); -// if (!factory) missingFields.push("Factory Name"); -// if (!email) missingFields.push("Email"); -// if (!emirate) missingFields.push("Emirate"); -// if (!employment) missingFields.push("Total Employment"); - -// if (missingFields.length > 0) { -// errors.push({ -// row: rowNum, -// error: `Missing Data: ${missingFields.join(", ")} ${missingFields.length === 1 ? "is" : "are"} required.` -// }); -// continue; -// } - -// if (isNaN(employment) || !/^\d+$/.test(employment)) { -// errors.push({ -// row: rowNum, -// error: "Total Employment must be numeric." -// }); -// continue; -// } - -// const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - -// if (!emailRegex.test(email)) { -// errors.push({ -// row: rowNum, -// error: "Invalid Email: Please check the Email address." -// }); -// continue; -// } -// // FILE-level duplicates -// const estKey = est.trim(); -// const factoryKey = factory.trim().toLowerCase(); -// const emailKey = email.trim().toLowerCase(); - -// if (fileEstSet.has(estKey)) { -// errors.push({ row: rowNum, error: `Duplicate Establishment ID in file: ${est}` }); -// continue; -// } -// if (fileFactorySet.has(factoryKey)) { -// errors.push({ row: rowNum, error: `Duplicate Factory Name in file: ${factory}` }); -// continue; -// } -// if (fileEmailSet.has(emailKey)) { -// errors.push({ row: rowNum, error: `Duplicate Email in file: ${email}` }); -// continue; -// } - -// fileEstSet.add(estKey); -// fileFactorySet.add(factoryKey); -// fileEmailSet.add(emailKey); - -// // DB-level duplicates -// if (existingEstIdSet.has(estKey)) { -// errors.push({ row: rowNum, error: `Establishment ID already exists: ${est}` }); -// continue; -// } -// if (existingFactorySet.has(factoryKey)) { -// errors.push({ row: rowNum, error: `Factory Name already exists: ${factory}` }); -// continue; -// } -// if (existingEmailSet.has(emailKey)) { -// errors.push({ row: rowNum, error: `Email already exists: ${email}` }); -// continue; -// } - -// // Emirate validation -// const emirateId = emirateMap[emirate.toLowerCase()]; -// if (!emirateId) { -// errors.push({ row: rowNum, error: `Invalid Emirate: ${emirate}` }); -// continue; -// } - -// // ------------------------------------------------------- -// // HS CODE VALIDATION -// // ------------------------------------------------------- -// const hsCols = Object.keys(r).filter(k => k.startsWith("hs")); -// const hsRaw = hsCols.map(k => r[k]).filter(Boolean); - -// if (hsRaw.length === 0) { -// errors.push({ row: rowNum, error: "At least one HS Code is required" }); -// continue; -// } - -// // Clean & remove leading zeros -// const cleaned = hsRaw.map(v => -// v.replace(/[^0-9]/g, "").replace(/^0+/, "") -// ); - -// // Check duplicates in same row -// const rowDuplicates = cleaned.filter((c, idx) => cleaned.indexOf(c) !== idx); -// if (rowDuplicates.length > 0) { -// errors.push({ -// row: rowNum, -// error: `Duplicate HS Code(s) in the same row: ${[...new Set(rowDuplicates)].join(", ")}` -// }); -// continue; -// } - -// // DB-level HS Code validation -// const invalid = []; - -// for (const raw of hsRaw) { -// const cleaned = normalizeHS(raw); - -// if (cleaned.length < 1 || cleaned.length > 10) { -// invalid.push(raw); -// continue; -// } - -// if (!productMap[cleaned]) { -// invalid.push(raw); -// } -// } - -// if (invalid.length > 0) { -// errors.push({ -// row: rowNum, -// error: `HS Code not found in database: ${invalid.join(", ")}` -// }); -// continue; -// } - -// prepared.push({ -// est, -// factory, -// email, -// emirateId, -// employment: parseInt(employment), -// productIds: cleaned.map(c => productMap[c]), -// rowNum -// }); -// } - -// // ------------------------------------------------------- -// // STOP IF ERRORS -// // ------------------------------------------------------- -// if (errors.length > 0) { -// logger.error("Establishment Upload Failed: " + JSON.stringify(errors)); -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Invalid data. Please check the instructions given and upload again.", -// errors -// }); -// } - -// // ------------------------------------------------------- -// // INSERT VALID RECORDS -// // ------------------------------------------------------- -// for (const rec of prepared) { -// const est = await Establishment.create({ -// establishment_code: rec.est, -// factory_name: rec.factory, -// establishment_contact_email: rec.email, -// establishment_emirate_id: rec.emirateId, -// total_employees: rec.employment, -// created_by: req.user.id -// }); - -// const autoPassword = Math.random().toString(36).slice(-10); -// const hashedPassword = await bcrypt.hash(autoPassword, 10); -// const user = await EstablishmentUser.create({ -// establishment_id: est.id, -// name: rec.factory, -// email: rec.email, -// password: hashedPassword, -// created_by: req.user.id -// }); - -// const placeHolderData = { -// contact_name: rec.factory || rec.est, -// portal_url: process.env.FE_BASE_URL, -// username: rec.email, -// password: autoPassword, -// support_email: process.env.SUPPORT_EMAIL, -// support_phone: process.env.SUPPORT_PHONE -// }; - -// await sendEmailService(rec.email, "establishment_user_creation_to_user", placeHolderData); - -// await EstablishmentProduct.bulkCreate( -// rec.productIds.map(pid => ({ -// establishment_id: est.id, -// product_id: pid, -// created_by: req.user.id -// })) -// ); -// } - -// fs.unlinkSync(filePath); - -// return res.status(200).send({ -// status: "success", -// message: `${prepared.length} establishments inserted successfully.`, -// summary: { -// total_records: rows.length, -// imported: prepared.length, -// errors: [] -// } -// }); - -// } catch (err) { -// logger.error("Establishment Import Fatal Error: " + err.message); - -// return res.status(500).send({ -// status: "failed", -// message: "Invalid data. Please check the instructions given and upload again." -// }); -// } -// }; - -// exports.establishmentBulkUpload = async (req, res) => { -// try { -// if (!req.file) { -// return res.status(400).send({ -// status: "failed", -// message: "No file uploaded." -// }); -// } - -// const filePath = req.file.path; - -// if (!req.file.originalname.toLowerCase().endsWith(".csv")) { -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Invalid file type. Only CSV allowed." -// }); -// } - -// const rows = []; - -// // ------------------------------------------------------- -// // READ CSV AND SKIP EMPTY ROWS -// // ------------------------------------------------------- -// await new Promise((resolve, reject) => { -// fs.createReadStream(filePath) -// .pipe(csv()) -// .on("data", (rawRow) => { -// const row = {}; - -// // Normalize headers -// for (const key in rawRow) { -// const normalizedKey = key.replace(/[\s\W]+/g, "_") -// .trim() -// .toLowerCase(); - -// row[normalizedKey] = rawRow[key]?.trim() || ""; -// } - -// const cleanedValues = Object.values(row).map(v =>(v || "").replace(/\s+/g, "").trim()); -// const isEmpty = cleanedValues.every(v => v === ""); -// if (isEmpty) return; - -// rows.push(row); -// }) -// .on("end", resolve) -// .on("error", reject); -// }); - -// if (rows.length === 0) { -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "CSV file is empty.", -// errors: [{ error: "Uploaded file is empty." }] -// }); -// } -// // ------------------------------------------------------- -// // REQUIRED HEADER CHECK -// // ------------------------------------------------------- -// const required = [ -// "establishment_id", -// "factory_name", -// "email", -// "emirate", -// "total_employment" -// ]; - -// const firstRowKeys = Object.keys(rows[0]); - -// const missing = required.filter(r => !firstRowKeys.includes(r)); -// if (missing.length > 0) { -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Invalid data. Please check the instructions given and upload again.", -// errors: [ -// { error: "Missing required columns: " + missing.join(", ") } -// ] -// }); -// } - -// // ------------------------------------------------------- -// // LOAD REFERENCE TABLES -// // ------------------------------------------------------- -// const emirates = await Emirate.findAll({ attributes: ["id", "name"] }); -// const emirateMap = {}; -// emirates.forEach(e => { -// emirateMap[e.name.trim().toLowerCase()] = e.id; -// }); - -// function normalizeHS(val) { -// if (!val) return ""; - -// return val -// .toString() -// .normalize("NFKD") -// .replace(/[^\d]/g, "") -// .trim(); -// } - -// const products = await Product.findAll({ attributes: ["id", "hs_code"] }); - -// const productMap = {}; - -// products.forEach(p => { -// const cleaned = normalizeHS(p.hs_code); - -// // Only store valid HS codes 1–10 digits -// if (cleaned.length >= 1 && cleaned.length <= 10) { -// productMap[cleaned] = p.id; -// } -// }); -// const existingEsts = await Establishment.findAll({ -// attributes: ["establishment_code", "factory_name", "establishment_contact_email"] -// }); - -// const existingEstIdSet = new Set(existingEsts.map(e => e.establishment_code)); -// const existingFactorySet = new Set(existingEsts.map(e => e.factory_name.toLowerCase())); -// const existingEmailSet = new Set(existingEsts.map(e => e.establishment_contact_email.toLowerCase())); - -// // ------------------------------------------------------- -// // FILE-LEVEL DUPLICATE TRACKING -// // ------------------------------------------------------- -// const errors = []; -// const fileEstSet = new Set(); -// const fileFactorySet = new Set(); -// const fileEmailSet = new Set(); - -// const prepared = []; - -// // ------------------------------------------------------- -// // VALIDATE EACH ROW -// // ------------------------------------------------------- -// for (let i = 0; i < rows.length; i++) { -// const r = rows[i]; -// const rowNum = i + 2; - -// const est = r.establishment_id; -// const factory = r.factory_name; -// const email = r.email; -// const emirate = r.emirate; -// const employment = r.total_employment; - -// // Required validation -// const missingFields = []; - -// if (!est) missingFields.push("Establishment ID"); -// if (!factory) missingFields.push("Factory Name"); -// if (!email) missingFields.push("Email"); -// if (!emirate) missingFields.push("Emirate"); -// if (!employment) missingFields.push("Total Employment"); - -// if (missingFields.length > 0) { -// errors.push({ -// row: rowNum, -// error: `Missing Data: ${missingFields.join(", ")} ${missingFields.length === 1 ? "is" : "are"} required.` -// }); -// continue; -// } - -// if (isNaN(employment) || !/^\d+$/.test(employment)) { -// errors.push({ -// row: rowNum, -// error: "Total Employment must be numeric." -// }); -// continue; -// } - -// const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - -// if (!emailRegex.test(email)) { -// errors.push({ -// row: rowNum, -// error: "Invalid Email: Please check the Email address." -// }); -// continue; -// } -// // FILE-level duplicates -// const estKey = est.trim(); -// const factoryKey = factory.trim().toLowerCase(); -// const emailKey = email.trim().toLowerCase(); - -// if (fileEstSet.has(estKey)) { -// errors.push({ row: rowNum, error: `Duplicate Establishment ID in file: ${est}` }); -// continue; -// } -// if (fileFactorySet.has(factoryKey)) { -// errors.push({ row: rowNum, error: `Duplicate Factory Name in file: ${factory}` }); -// continue; -// } -// if (fileEmailSet.has(emailKey)) { -// errors.push({ row: rowNum, error: `Duplicate Email in file: ${email}` }); -// continue; -// } - -// fileEstSet.add(estKey); -// fileFactorySet.add(factoryKey); -// fileEmailSet.add(emailKey); - -// // DB-level duplicates -// if (existingEstIdSet.has(estKey)) { -// errors.push({ row: rowNum, error: `Establishment ID already exists: ${est}` }); -// continue; -// } -// if (existingFactorySet.has(factoryKey)) { -// errors.push({ row: rowNum, error: `Factory Name already exists: ${factory}` }); -// continue; -// } -// if (existingEmailSet.has(emailKey)) { -// errors.push({ row: rowNum, error: `Email already exists: ${email}` }); -// continue; -// } - -// // Emirate validation -// const emirateId = emirateMap[emirate.toLowerCase()]; -// if (!emirateId) { -// errors.push({ row: rowNum, error: `Invalid Emirate: ${emirate}` }); -// continue; -// } - -// // ------------------------------------------------------- -// // HS CODE VALIDATION -// // ------------------------------------------------------- -// const hsCols = Object.keys(r).filter(k => k.startsWith("hs")); -// const hsRaw = hsCols.map(k => r[k]).filter(Boolean); - -// if (hsRaw.length === 0) { -// errors.push({ row: rowNum, error: "At least one HS Code is required" }); -// continue; -// } - -// // Clean & remove leading zeros -// const cleaned = hsRaw.map(v => -// v.replace(/[^0-9]/g, "").replace(/^0+/, "") -// ); - -// // Check duplicates in same row -// const rowDuplicates = cleaned.filter((c, idx) => cleaned.indexOf(c) !== idx); -// if (rowDuplicates.length > 0) { -// errors.push({ -// row: rowNum, -// error: `Duplicate HS Code(s) in the same row: ${[...new Set(rowDuplicates)].join(", ")}` -// }); -// continue; -// } - -// // DB-level HS Code validation -// const invalid = []; - -// for (const raw of hsRaw) { -// const cleaned = normalizeHS(raw); - -// if (cleaned.length < 1 || cleaned.length > 10) { -// invalid.push(raw); -// continue; -// } - -// if (!productMap[cleaned]) { -// invalid.push(raw); -// } -// } - -// if (invalid.length > 0) { -// errors.push({ -// row: rowNum, -// error: `HS Code not found in database: ${invalid.join(", ")}` -// }); -// continue; -// } - -// prepared.push({ -// est, -// factory, -// email, -// emirateId, -// employment: parseInt(employment), -// productIds: cleaned.map(c => productMap[c]), -// rowNum -// }); -// } - -// // ------------------------------------------------------- -// // STOP IF ERRORS -// // ------------------------------------------------------- -// if (errors.length > 0) { -// logger.error("Establishment Upload Failed: " + JSON.stringify(errors)); -// fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Invalid data. Please check the instructions given and upload again.", -// errors -// }); -// } - -// // ------------------------------------------------------- -// // INSERT VALID RECORDS -// // ------------------------------------------------------- -// for (const rec of prepared) { -// const est = await Establishment.create({ -// establishment_code: rec.est, -// factory_name: rec.factory, -// establishment_contact_email: rec.email, -// establishment_emirate_id: rec.emirateId, -// total_employees: rec.employment, -// created_by: req.user.id, -// created_at: new Date() -// }); - -// await EstablishmentProduct.bulkCreate( -// rec.productIds.map(pid => ({ -// establishment_id: est.id, -// product_id: pid, -// created_by: req.user.id, -// created_at: new Date(), -// is_active: true -// })) -// ); -// } - -// fs.unlinkSync(filePath); - -// return res.status(200).send({ -// status: "success", -// message: `${prepared.length} establishments inserted successfully.`, -// summary: { -// total_records: rows.length, -// imported: prepared.length, -// errors: [] -// } -// }); - -// } catch (err) { -// logger.error("Establishment Import Fatal Error: " + err.message); - -// return res.status(500).send({ -// status: "failed", -// message: "Invalid data. Please check the instructions given and upload again." -// }); -// } -// }; - -// exports.establishmentBulkUpload = async (req, res) => { -// try { -// // Basic file & user validations -// if (!req.file) { -// return res.status(400).send({ status: "failed", message: "No file uploaded" }); -// } -// const filePath = req.file.path; - -// if (!req.file.originalname.toLowerCase().endsWith(".csv")) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "Invalid file type. Only CSV allowed." }); -// } - -// if (!req.user?.id || isNaN(req.user.id)) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "Invalid or missing User Id." }); -// } - -// const stats = fs.statSync(filePath); -// if (stats.size === 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "Uploaded file is empty." }); -// } - -// // Read CSV into memory (array of normalized rows) -// const rows = []; -// fs.createReadStream(filePath) -// .pipe(csv()) -// .on("data", (rawRow) => { -// // Normalize headers and map to canonical keys -// const row = {}; -// for (const key in rawRow) { -// const normalizedKey = key.replace(/[\s\W]+/g, "_").trim().toLowerCase(); - -// // Standard canonical keys -// if (normalizedKey === "establishment_id" || normalizedKey === "establishment_code" || normalizedKey.startsWith("establishment")) { -// row["Establishment Id"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("factory name") || normalizedKey.includes("factory_name") || normalizedKey.includes("Factory Name")) { -// row["Factory Name"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("Email") || normalizedKey.includes("email") ) { -// row["Email"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("Emirate") || normalizedKey.includes("emirate") ) { -// row["Emirate"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("total employment") || normalizedKey.includes("total_employment") || normalizedKey.includes("total employments")) { -// row["Total Employment"] = rawRow[key]?.trim() || null; -// continue; -// } - -// // HS code columns: keep any header that contains 'hs' (hs_code, hs_code_1, hs1, etc.) -// if (/^hs|hs_code|hs_code_\d|hs\d/i.test(normalizedKey) || normalizedKey.includes("hs")) { -// // keep the exact normalized header so we can iterate later -// row[normalizedKey] = rawRow[key]?.trim() || null; -// continue; -// } -// // Fallback - keep other columns too -// row[normalizedKey] = rawRow[key]?.trim() || null; -// } - -// rows.push(row); -// }) -// .on("end", async () => { -// try { -// if (!rows.length) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "CSV file is empty or invalid." }); -// } - -// // Check required canonical columns exist in the header (based on first row keys) -// const firstRowKeys = Object.keys(rows[0]); -// const required = ["Establishment Id", "Factory Name", "Email", "Emirate", "Total Employment"]; -// const missing = required.filter((k) => !firstRowKeys.includes(k)); -// if (missing.length > 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: `Missing required columns: ${missing.join(", ")}`, -// }); -// } - -// // Prepare maps: emirates and products -// const emirates = await Emirate.findAll({ attributes: ["id", "name"] }); -// const emirateMap = {}; -// emirates.forEach((e) => { -// if (e.name) emirateMap[e.name.trim().toLowerCase()] = e.id; -// }); - -// // Build product map by normalized hs_code (digits only, no leading zeros) -// const products = await Product.findAll({ attributes: ["id", "hs_code"] }); -// const productMap = {}; -// products.forEach((p) => { -// if (!p.hs_code) return; -// const normalized = String(p.hs_code).replace(/[^0-9]/g, "").replace(/^0+/, ""); -// if (normalized) productMap[normalized] = p.id; -// }); - - -// // Existing establishment codes in DB -// const existingEsts = await Establishment.findAll({ -// attributes: ["establishment_code", "factory_name", "establishment_contact_email"] -// }); - -// const existingEstIdSet = new Set(existingEsts.map((e) => e.establishment_code)); -// const existingFactorySet = new Set(existingEsts.map((e) => e.factory_name?.trim().toLowerCase())); -// const existingEmailSet = new Set(existingEsts.map((e) => e.establishment_contact_email?.trim().toLowerCase())); - -// const errors = []; -// const fileDuplicates = new Set(); -// const seenEstIds = new Set(); -// const toInsert = []; - -// // Process each row: validate & prepare -// for (let i = 0; i < rows.length; i++) { -// const raw = rows[i]; -// const rowNumber = i + 1; - -// const estId = raw["Establishment Id"]?.trim(); -// const factory = raw["Factory Name"]?.trim(); -// const email = raw["Email"]?.trim(); -// const emirateRaw = raw["Emirate"]?.trim(); -// const employmentRaw = raw["Total Employment"]?.trim(); - -// // Required fields -// if (!estId || !factory || !email || !emirateRaw) { -// errors.push({ -// row: rowNumber, -// error: "Missing required fields (Establishment Id, Factory Name, Email, Emirate).", -// }); -// continue; -// } - -// //Checking repeating data -// const estIdRowMap = {}; -// const factoryRowMap = {}; -// const emailRowMap = {}; - -// // During row loop — replace your tracking code with this: -// const keyEst = estId?.trim(); -// const keyFactory = factory?.trim().toLowerCase(); -// const keyEmail = email?.trim().toLowerCase(); - -// // Helper for inserting row numbers -// const pushRow = (map, key) => -// key ? (map[key] = map[key] ? [...map[key], rowNumber] : [rowNumber]) : null; - -// // Track all 3 -// pushRow(estIdRowMap, keyEst); -// pushRow(factoryRowMap, keyFactory); -// pushRow(emailRowMap, keyEmail); - -// const duplicateDetails = []; - -// [ -// ["Establishment Id", estIdRowMap], -// ["Factory Name", factoryRowMap], -// ["Email", emailRowMap], -// ].forEach(([field, map]) => { -// Object.entries(map).forEach(([value, rows]) => { -// if (rows.length > 1) { -// duplicateDetails.push({ -// field, -// value, -// rows -// }); -// } -// }); -// }); -// if (duplicateDetails.length > 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Repeated values found in uploaded file.", -// duplicates: duplicateDetails, -// }); -// } - -// const hsRawCodes = []; -// for (const k of Object.keys(raw)) { -// if (/^hs|hs_code|hs_code_\d|hs\d/i.test(k) || k.includes("hs")) { -// const v = raw[k]; -// if (v && String(v).trim()) { -// hsRawCodes.push(String(v).trim()); -// } -// } -// } - -// // Validation: at least one HS code must be present -// if (hsRawCodes.length === 0) { -// errors.push({ -// row: rowNumber, -// error: "Missing HS Code columns or no HS Code values provided.", -// }); -// continue; -// } - -// const seenEstIds = new Set(); -// const seenFactories = new Set(); -// const seenEmails = new Set(); - -// if (seenEstIds.has(keyEst)) { -// fileDuplicates.add(`Establishment ID: ${keyEst}`); -// continue; -// } -// if (seenFactories.has(keyFactory)) { -// fileDuplicates.add(`Factory Name: ${factory}`); -// continue; -// } -// if (seenEmails.has(keyEmail)) { -// fileDuplicates.add(`Email: ${email}`); -// continue; -// } - -// seenEstIds.add(keyEst); -// seenFactories.add(keyFactory); -// seenEmails.add(keyEmail); - -// if (existingEstIdSet.has(keyEst)) { -// fileDuplicates.add(`Establishment ID already exists: ${keyEst}`); -// continue; -// } -// if (existingFactorySet.has(keyFactory)) { -// fileDuplicates.add(`Factory Name already exists: ${factory}`); -// continue; -// } -// if (existingEmailSet.has(keyEmail)) { -// fileDuplicates.add(`Email already exists: ${email}`); -// continue; -// } - -// // Emirate mapping -// const emirateId = emirateMap[emirateRaw.toLowerCase()]; -// if (!emirateId) { -// errors.push({ -// row: rowNumber, -// error: `Invalid Emirate: '${emirateRaw}'`, -// }); -// continue; -// } - -// // Clean HS codes: remove non-digits and leading zeros -// const cleanedHs = hsRawCodes -// .map((hc) => hc.replace(/[^0-9]/g, "").replace(/^0+/, "")) -// .filter(Boolean); // remove empty after cleaning - -// const mappedProducts = cleanedHs.map((c) => productMap[c] || null); - -// // Find which HS codes do NOT match product master -// const notMapped = cleanedHs.filter((c, i) => mappedProducts[i] === null); - -// if (notMapped.length > 0) { -// errors.push({ -// row: rowNumber, -// error: `${notMapped} HS Codes do not exist.`, -// not_mapped: notMapped -// }); -// continue; -// } - -// if (mappedProducts.every((id) => id === null)) { -// errors.push({ -// row: rowNumber, -// error: "None of the provided HS Codes match existing products.", -// hs_codes: cleanedHs -// }); -// continue; -// } - -// // parse employment -// const employment = parseInt(employmentRaw) || 0; - -// toInsert.push({ -// estCode: estId, -// factory, -// email, -// emirateId, -// employment, -// productIds: [...new Set(mappedProducts)], -// rowIndex: rowNumber, -// }); -// } - -// // If file duplicates found (either in file or in DB), return error - consistent with product controller -// if (fileDuplicates.size > 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Duplicate Establishment IDs found (in file or already exist in system). Resolve and re-upload.", -// duplicates: [...fileDuplicates], -// }); -// } - -// // Insert prepared establishments and associated products -// let inserted = 0; -// for (const rec of toInsert) { -// try { -// const createdEst = await Establishment.create({ -// establishment_code: rec.estCode, -// factory_name: rec.factory, -// establishment_contact_email: rec.email, -// establishment_emirate_id: rec.emirateId, -// total_employees: rec.employment, -// created_by: req.user.id, -// created_at: new Date(), -// }); - -// // prepare bulk create payload for establishment_products -// const payload = rec.productIds.map((pid) => ({ -// establishment_id: createdEst.id, -// product_id: pid, -// created_by: req.user.id, -// created_at: new Date(), -// is_active: true, -// })); - -// await EstablishmentProduct.bulkCreate(payload); -// inserted++; -// } catch (errInner) { -// // Collect row-level DB insert errors for partial success reporting -// errors.push({ -// row: rec.rowIndex, -// error: `DB insert error: ${errInner.message}`, -// }); -// } -// } - -// // cleanup uploaded file -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); - -// // Build final response to match product import style -// let finalStatus = "success"; -// let message = `${inserted} establishments inserted successfully.`; - -// if (errors.length > 0) { -// finalStatus = inserted > 0 ? "partial_success" : "failed"; -// if (finalStatus === "partial_success") { -// message = `${inserted} establishments inserted, ${errors.length} rows failed.`; -// } else { -// message = `No establishments imported. ${errors.length} validation errors found.`; -// } -// } - -// return res.status(200).send({ -// status: finalStatus, -// message, -// summary: { -// total_records: rows.length, -// imported: inserted, -// errors, -// }, -// }); -// } catch (err) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(500).send({ status: "failed", message: err.message }); -// } -// }) -// .on("error", (err) => { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(500).send({ status: "failed", message: err.message }); -// }); -// } catch (error) { -// return res.status(500).send({ status: "failed", message: error.message }); -// } -// }; - -// exports.establishmentBulkUpload = async (req, res) => { -// try { -// // Basic file & user validations -// if (!req.file) { -// return res.status(400).send({ status: "failed", message: "No file uploaded" }); -// } -// const filePath = req.file.path; - -// if (!req.file.originalname.toLowerCase().endsWith(".csv")) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "Invalid file type. Only CSV allowed." }); -// } - -// if (!req.user?.id || isNaN(req.user.id)) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "Invalid or missing User Id." }); -// } - -// const mode = (req.body.mode || "add").toLowerCase(); -// if (mode !== "add") { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "Only 'Add Only' mode is supported currently." }); -// } - -// const stats = fs.statSync(filePath); -// if (stats.size === 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "Uploaded file is empty." }); -// } - -// // Read CSV into memory (array of normalized rows) -// const rows = []; -// fs.createReadStream(filePath) -// .pipe(csv()) -// .on("data", (rawRow) => { -// // Normalize headers and map to canonical keys -// const row = {}; -// for (const key in rawRow) { -// const normalizedKey = key.replace(/[\s\W]+/g, "_").trim().toLowerCase(); - -// // Standard canonical keys -// if (normalizedKey === "establishment_id" || normalizedKey === "establishment_code" || normalizedKey.startsWith("establishment")) { -// row["Establishment Id"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("factory name") || normalizedKey.includes("factory_name") || normalizedKey.includes("Factory Name")) { -// row["Factory Name"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("Email") || normalizedKey.includes("email") ) { -// row["Email"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("Emirate") || normalizedKey.includes("emirate") ) { -// row["Emirate"] = rawRow[key]?.trim() || null; -// continue; -// } - -// if (normalizedKey.includes("total employment") || normalizedKey.includes("total_employment") || normalizedKey.includes("total employments")) { -// row["Total Employment"] = rawRow[key]?.trim() || null; -// continue; -// } - -// // HS code columns: keep any header that contains 'hs' (hs_code, hs_code_1, hs1, etc.) -// if (/^hs|hs_code|hs_code_\d|hs\d/i.test(normalizedKey) || normalizedKey.includes("hs")) { -// // keep the exact normalized header so we can iterate later -// row[normalizedKey] = rawRow[key]?.trim() || null; -// continue; -// } - -// // Fallback - keep other columns too -// row[normalizedKey] = rawRow[key]?.trim() || null; -// } - -// rows.push(row); -// }) -// .on("end", async () => { -// try { -// if (!rows.length) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ status: "failed", message: "CSV file is empty or invalid." }); -// } - -// // Check required canonical columns exist in the header (based on first row keys) -// const firstRowKeys = Object.keys(rows[0]); -// const required = ["Establishment Id", "Factory Name", "Email", "Emirate", "Total Employment"]; -// const missing = required.filter((k) => !firstRowKeys.includes(k)); -// if (missing.length > 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: `Missing required columns: ${missing.join(", ")}`, -// }); -// } - -// // Prepare maps: emirates and products -// const emirates = await Emirate.findAll({ attributes: ["id", "name"] }); -// const emirateMap = {}; -// emirates.forEach((e) => { -// if (e.name) emirateMap[e.name.trim().toLowerCase()] = e.id; -// }); - -// // Build product map by normalized hs_code (digits only, no leading zeros) -// const products = await Product.findAll({ attributes: ["id", "hs_code"] }); -// const productMap = {}; -// products.forEach((p) => { -// if (!p.hs_code) return; -// const normalized = String(p.hs_code).replace(/[^0-9]/g, "").replace(/^0+/, ""); -// if (normalized) productMap[normalized] = p.id; -// }); - - -// // Existing establishment codes in DB -// const existingEsts = await Establishment.findAll({ -// attributes: ["establishment_code", "factory_name", "establishment_contact_email"] -// }); - -// const existingEstIdSet = new Set(existingEsts.map((e) => e.establishment_code)); -// const existingFactorySet = new Set(existingEsts.map((e) => e.factory_name?.trim().toLowerCase())); -// const existingEmailSet = new Set(existingEsts.map((e) => e.establishment_contact_email?.trim().toLowerCase())); - -// const errors = []; -// const fileDuplicates = new Set(); -// const seenEstIds = new Set(); -// const toInsert = []; - -// // Process each row: validate & prepare -// for (let i = 0; i < rows.length; i++) { -// const raw = rows[i]; -// const rowNumber = i + 1; - -// const estId = raw["Establishment Id"]?.trim(); -// const factory = raw["Factory Name"]?.trim(); -// const email = raw["Email"]?.trim(); -// const emirateRaw = raw["Emirate"]?.trim(); -// const employmentRaw = raw["Total Employment"]?.trim(); - -// // Required fields -// if (!estId || !factory || !email || !emirateRaw) { -// errors.push({ -// row: rowNumber, -// error: "Missing required fields (Establishment Id, Factory Name, Email, Emirate).", -// }); -// continue; -// } - -// //Checking repeating data -// const estIdRowMap = {}; -// const factoryRowMap = {}; -// const emailRowMap = {}; - -// // During row loop — replace your tracking code with this: -// const keyEst = estId?.trim(); -// const keyFactory = factory?.trim().toLowerCase(); -// const keyEmail = email?.trim().toLowerCase(); - -// // Helper for inserting row numbers -// const pushRow = (map, key) => -// key ? (map[key] = map[key] ? [...map[key], rowNumber] : [rowNumber]) : null; - -// // Track all 3 -// pushRow(estIdRowMap, keyEst); -// pushRow(factoryRowMap, keyFactory); -// pushRow(emailRowMap, keyEmail); - -// const duplicateDetails = []; - -// [ -// ["Establishment Id", estIdRowMap], -// ["Factory Name", factoryRowMap], -// ["Email", emailRowMap], -// ].forEach(([field, map]) => { -// Object.entries(map).forEach(([value, rows]) => { -// if (rows.length > 1) { -// duplicateDetails.push({ -// field, -// value, -// rows -// }); -// } -// }); -// }); -// if (duplicateDetails.length > 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Repeated values found in uploaded file.", -// duplicates: duplicateDetails, -// }); -// } - -// const hsRawCodes = []; -// for (const k of Object.keys(raw)) { -// if (/^hs|hs_code|hs_code_\d|hs\d/i.test(k) || k.includes("hs")) { -// const v = raw[k]; -// if (v && String(v).trim()) { -// hsRawCodes.push(String(v).trim()); -// } -// } -// } - -// // Validation: at least one HS code must be present -// if (hsRawCodes.length === 0) { -// errors.push({ -// row: rowNumber, -// error: "Missing HS Code columns or no HS Code values provided.", -// }); -// continue; -// } - -// const seenEstIds = new Set(); -// const seenFactories = new Set(); -// const seenEmails = new Set(); - -// if (seenEstIds.has(keyEst)) { -// fileDuplicates.add(`Establishment ID: ${keyEst}`); -// continue; -// } -// if (seenFactories.has(keyFactory)) { -// fileDuplicates.add(`Factory Name: ${factory}`); -// continue; -// } -// if (seenEmails.has(keyEmail)) { -// fileDuplicates.add(`Email: ${email}`); -// continue; -// } - -// seenEstIds.add(keyEst); -// seenFactories.add(keyFactory); -// seenEmails.add(keyEmail); - -// if (existingEstIdSet.has(keyEst)) { -// fileDuplicates.add(`Establishment ID already exists: ${keyEst}`); -// continue; -// } -// if (existingFactorySet.has(keyFactory)) { -// fileDuplicates.add(`Factory Name already exists: ${factory}`); -// continue; -// } -// if (existingEmailSet.has(keyEmail)) { -// fileDuplicates.add(`Email already exists: ${email}`); -// continue; -// } - -// // Emirate mapping -// const emirateId = emirateMap[emirateRaw.toLowerCase()]; -// if (!emirateId) { -// errors.push({ -// row: rowNumber, -// error: `Invalid Emirate: '${emirateRaw}'`, -// }); -// continue; -// } - -// // Clean HS codes: remove non-digits and leading zeros -// const cleanedHs = hsRawCodes -// .map((hc) => hc.replace(/[^0-9]/g, "").replace(/^0+/, "")) -// .filter(Boolean); // remove empty after cleaning - -// // Map to product IDs -// const mappedProducts = cleanedHs -// .map((c) => productMap[c]) -// .filter(Boolean); - -// // Validate mapping -// if (mappedProducts.length === 0) { -// errors.push({ -// row: rowNumber, -// error: "None of the provided HS Codes match existing products.", -// hs_codes: cleanedHs -// }); -// continue; -// } - -// if (mappedProducts.length === 0) { -// errors.push({ row: rowNum, error: "No HS codes matched product list" }); -// continue; -// } -// // Validation: at least one HS must match existing product -// if (mappedProducts.length === 0) { -// errors.push({ -// row: rowNumber, -// error: "None of the provided HS Codes match existing products.", -// hs_codes: cleanedHs, -// }); -// continue; -// } - -// // parse employment -// const employment = parseInt(employmentRaw) || 0; - -// toInsert.push({ -// estCode: estId, -// factory, -// email, -// emirateId, -// employment, -// productIds: [...new Set(mappedProducts)], -// rowIndex: rowNumber, -// }); -// } - -// // If file duplicates found (either in file or in DB), return error - consistent with product controller -// if (fileDuplicates.size > 0) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(400).send({ -// status: "failed", -// message: "Duplicate Establishment IDs found (in file or already exist in system). Resolve and re-upload.", -// duplicates: [...fileDuplicates], -// }); -// } - -// // Insert prepared establishments and associated products -// let inserted = 0; -// for (const rec of toInsert) { -// try { -// const createdEst = await Establishment.create({ -// establishment_code: rec.estCode, -// factory_name: rec.factory, -// establishment_contact_email: rec.email, -// establishment_emirate_id: rec.emirateId, -// total_employees: rec.employment, -// created_by: req.user.id, -// created_at: new Date(), -// }); - -// // prepare bulk create payload for establishment_products -// const payload = rec.productIds.map((pid) => ({ -// establishment_id: createdEst.id, -// product_id: pid, -// created_by: req.user.id, -// created_at: new Date(), -// is_active: true, -// })); - -// await EstablishmentProduct.bulkCreate(payload); -// inserted++; -// } catch (errInner) { -// // Collect row-level DB insert errors for partial success reporting -// errors.push({ -// row: rec.rowIndex, -// error: `DB insert error: ${errInner.message}`, -// }); -// } -// } - -// // cleanup uploaded file -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); - -// // Build final response to match product import style -// let finalStatus = "success"; -// let message = `${inserted} establishments inserted successfully.`; - -// if (errors.length > 0) { -// finalStatus = inserted > 0 ? "partial_success" : "failed"; -// if (finalStatus === "partial_success") { -// message = `${inserted} establishments inserted, ${errors.length} rows failed.`; -// } else { -// message = `No establishments imported. ${errors.length} validation errors found.`; -// } -// } - -// return res.status(200).send({ -// status: finalStatus, -// message, -// summary: { -// total_records: rows.length, -// imported: inserted, -// errors, -// }, -// }); -// } catch (err) { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(500).send({ status: "failed", message: err.message }); -// } -// }) -// .on("error", (err) => { -// if (fs.existsSync(filePath)) fs.unlinkSync(filePath); -// return res.status(500).send({ status: "failed", message: err.message }); -// }); -// } catch (error) { -// return res.status(500).send({ status: "failed", message: error.message }); -// } -// }; - - - diff --git a/app/uploads/company_profile_upload_sample.csv b/app/uploads/company_profile_upload_sample.csv index 70bb0e2..ccb63ac 100644 --- a/app/uploads/company_profile_upload_sample.csv +++ b/app/uploads/company_profile_upload_sample.csv @@ -1 +1 @@ -Establishment Code,Factory Name,User Name,Email,Emirate,HS Code 1,HS Code 2,HS Code 3,HS Code 4,HS Code 5,Permanent Factory Code,Industry Code Business Register,Industry Code Current Production,Description,Industry Code Mismatch Remarks,Establishment Address,City/Town,Postal Code,PO Box,Makani Number,Contact Person Name,Contact Person Designation,Mobile Number,Website,Number of Emirati Male,Number of Emirati Female,Number of Non-Emirati Male,Number of Non-Emirati Female +Establishment Code * (Mandatory),Factory Name * (Mandatory),User Name * (Mandatory),Email * (Mandatory),Emirate * (Mandatory),HS Code 1 * (Mandatory),HS Code 2,HS Code 3,HS Code 4,HS Code 5,Permanent Factory Code,Industry Code Business Register,Industry Code Current Production,Description,Industry Code Mismatch Remarks,Establishment Address,City/Town,Postal Code,PO Box,Makani Number,Contact Person Name,Contact Person Designation,Mobile Number,Website,Number of Emirati Male,Number of Emirati Female,Number of Non-Emirati Male,Number of Non-Emirati Female