From ce45de6a27dcfbfaecdde3b03d5f48afc25ee523 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 21 Nov 2025 16:11:51 +0530 Subject: [PATCH] send email for bulk upload --- app/controllers/establishment.controller.js | 379 +++++++++++++++++++- app/models/notificationTemplate.model.js | 4 +- 2 files changed, 366 insertions(+), 17 deletions(-) diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index 51f9321..17bdf6b 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -95,7 +95,6 @@ exports.createEstablishment = async (req, res) => { for (const item of establishment_products) { const id = Number(item.product_id); - if (!id || isNaN(id) || id <= 1) { return res.status(400).send({ status: "failed", @@ -178,8 +177,6 @@ exports.createEstablishment = async (req, res) => { } await sendEmailService(establishment_user.email, 'establishment_user_creation_to_user', placeHolderData); - - // insert establishment_products if (Array.isArray(establishment_products) && establishment_products.length > 0) { @@ -211,7 +208,6 @@ exports.createEstablishment = async (req, res) => { } } - // 🔹 Return success response return res.status(201).send({ status: "success", @@ -226,7 +222,6 @@ exports.createEstablishment = async (req, res) => { }, }); - } catch (err) { if (err.name === "SequelizeUniqueConstraintError") { @@ -448,7 +443,6 @@ exports.getAllEstablishments = async (req, res) => { } }; - // Get one establishment exports.getEstablishmentById = async (req, res) => { try { @@ -510,8 +504,6 @@ exports.getEstablishmentById = async (req, res) => { }; // Update establishment - - exports.updateEstablishment = async (req, res) => { try { const { id } = req.params; @@ -585,7 +577,6 @@ exports.updateEstablishment = async (req, res) => { } }; - // exports.updateEstablishment = async (req, res) => { // try { @@ -1213,17 +1204,35 @@ exports.establishmentBulkUpload = async (req, res) => { establishment_contact_email: rec.email, establishment_emirate_id: rec.emirateId, total_employees: rec.employment, - created_by: req.user.id, - created_at: new Date() + 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, - created_at: new Date(), - is_active: true + created_by: req.user.id })) ); } @@ -1250,6 +1259,348 @@ exports.establishmentBulkUpload = 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, +// 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 diff --git a/app/models/notificationTemplate.model.js b/app/models/notificationTemplate.model.js index 61cde3a..3aecc45 100644 --- a/app/models/notificationTemplate.model.js +++ b/app/models/notificationTemplate.model.js @@ -39,9 +39,7 @@ module.exports = (sequelize, DataTypes) => { updated_at: { type: DataTypes.DATE, allowNull: true, - }, - created_by: {type: DataTypes.INTEGER, allowNull: false }, - updated_by: { type: DataTypes.INTEGER, allowNull: true }, + } }, { tableName: "notification_templates",