const db = require("../models"); const bcrypt = require("bcryptjs"); const Establishment = db.Establishment; const EstablishmentUser = db.EstablishmentUser; const EstablishmentPasswordResetRequest = db.EstablishmentPasswordResetRequest; const EstablishmentProduct = db.EstablishmentProduct; const CityTown = db.CityTown; const Emirate = db.Emirate; const user = db.user; const Product = db.Product; const ExcelJS = require("exceljs"); const { Op } = require("sequelize"); const { sendEmail } = require("../services/emailHelper"); const { sendEmailService } = require("../services/email.service"); const logger = require("../services/logger"); const fs = require("fs"); const csv = require("csv-parser"); const path = require("path"); exports.testEmail = async (req, res) => { placeHolderData = { contact_name : 'Gowtham', portal_url : process.env.BASE_URL, username : '--', password : '--', support_email : '--', support_phone : '--', } await sendEmailService('gowthamceline46@gmail.com', 'establishment_user_creation_to_user', placeHolderData); }; exports.createEstablishment = async (req, res) => { try { const { establishment_code, factory_name, permanent_factory_code, industry_code, industry_code_production, license_number, isic_code, description, // Establishment Contact Details establishment_address, establishment_city_town_id, establishment_emirate_id, establishment_postal_code, establishment_po_box, establishment_makani_number, establishment_contact_person_name, establishment_contact_person_designation, establishment_mobile_number, establishment_contact_email, establishment_website, // Corporate Details corporate_same_as_establishment, corporate_name, corporate_address, corporate_city_town_id, corporate_emirate_id, corporate_postal_code, corporate_po_box, corporate_makani_number, corporate_contact_person_name, corporate_contact_person_designation, corporate_mobile_number, corporate_email, corporate_website, emirati_male, emirati_female, non_emirati_male, non_emirati_female, total_emirati, total_employees, created_by, establishment_user, establishment_products } = req.body; // Basic Validation if (!establishment_code || !factory_name || !establishment_user) { return res.status(400).send({ status: "failed", message: "Missing required fields: establishment_code, factory_name, email, establishment_user", }); } // Check if establishment already exists const existingEstablishment = await Establishment.findOne({where: { establishment_code }, }); if (existingEstablishment) { return res.status(400).send({status: "failed",message: "Establishment code already exists", }); } // Create establishment record const establishment = await Establishment.create({ establishment_code, factory_name, permanent_factory_code, industry_code, industry_code_production, license_number, isic_code, description, establishment_address, establishment_city_town_id, establishment_emirate_id, establishment_postal_code, establishment_po_box, establishment_makani_number, establishment_contact_person_name, establishment_contact_person_designation, establishment_mobile_number, establishment_contact_email, establishment_website, corporate_same_as_establishment, corporate_name, corporate_address, corporate_city_town_id, corporate_emirate_id, corporate_postal_code, corporate_po_box, corporate_makani_number, corporate_contact_person_name, corporate_contact_person_designation, corporate_mobile_number, corporate_email, corporate_website, emirati_male, emirati_female, non_emirati_male, non_emirati_female, total_emirati, total_employees, created_by, created_at: new Date(), }); // Hash password const hashedPassword = await bcrypt.hash(establishment_user.password, 10); // Create linked Establishment User const user = await EstablishmentUser.create({ establishment_id: establishment.id, name: establishment_user.name, email: establishment_user.email, password: hashedPassword, }); //send email to user placeHolderData = { contact_name : establishment_user.name, portal_url : process.env.FE_BASE_URL, username : establishment_user.email, password : establishment_user.password, support_email : process.env.SUPPORT_EMAIL, support_phone : process.env.SUPPORT_PHONE, } await sendEmailService(establishment_user.email, 'establishment_user_creation_to_user', placeHolderData); // insert establishment_products if (Array.isArray(establishment_products) && establishment_products.length > 0) { // remove duplicates from request itself let uniqueProducts = [...new Set(establishment_products.map(x => x.product_id))]; // now check which already exist for this establishment const existing = await EstablishmentProduct.findAll({ where: { establishment_id: establishment.id, product_id: uniqueProducts }, attributes: ['product_id'] }); const existingIds = existing.map(x => x.product_id); // filter only new ones (not existing) const newProducts = uniqueProducts .filter(pid => !existingIds.includes(pid)) .map(pid => ({ establishment_id: establishment.id, product_id: pid, created_at: new Date(), })); // only insert if have new ones if (newProducts.length) { await EstablishmentProduct.bulkCreate(newProducts); } } // 🔹 Return success response return res.status(201).send({ status: "success", message: "Establishment and linked user created successfully", data: { establishment, user: { id: user.id, name: user.name, email: user.email, }, }, }); } catch (err) { if (err.name === "SequelizeUniqueConstraintError") { // extract exact field const field = err.errors[0].path; // <-- this gives the column name return res.status(400).json({ status: "failed", message: `${field} already exists` }); } return res.status(500).send({status: "failed",message: err.message || "Internal server error",}); } }; // Get all establishments // exports.getAllEstablishments = async (req, res) => { // try { // const data = await Establishment.findAll({ // include: [ // // { // // model: EstablishmentUser, // // as: "users", // // attributes: ["id", "name", "email", "is_active"], // // }, // { // model: CityTown, // as: "establishment_city", // attributes: ["name"], // }, // { // model: Emirate, // as: "establishment_emirate", // attributes: ["name"], // },{ // model: CityTown, // as: "corporate_city", // attributes: ["name"], // }, // { // model: Emirate, // as: "corporate_emirate", // attributes: ["name"], // }, // { // model: user, // as: "created_user", // attributes: ["name"], // } // ], // }); // return res.status(200).send({'status':"success",'message':"Fetched successfully",'data': data }); // } catch (err) { // return res.status(500).send({'status':"failed",'message':err.message }); // } // }; exports.getAllEstablishments = async (req, res) => { try { let { page = 1, limit = 10, search = "", emirate_id, isic_code, status, sort_by = "created_at", sort_order = "DESC", export: exportType, // detect ?export=excel } = req.query; page = parseInt(page); limit = parseInt(limit); const offset = (page - 1) * limit; // Build dynamic where clause const whereClause = {}; // Filters if (status) whereClause.is_active = status === "active" ? 1 : 0; if (isic_code) whereClause.isic_code = isic_code; if (emirate_id) whereClause.establishment_emirate_id = emirate_id; // Search by name, establishment_code, or contact emails if (search) { whereClause[Op.or] = [ { factory_name: { [Op.like]: `%${search}%` } }, { establishment_code: { [Op.like]: `%${search}%` } }, { establishment_contact_email: { [Op.like]: `%${search}%` } }, { corporate_email: { [Op.like]: `%${search}%` } }, ]; } // Fetch Data const data = await Establishment.findAll({ where: whereClause, include: [ { model: CityTown, as: "establishment_city", attributes: ["name"] }, { model: Emirate, as: "establishment_emirate", attributes: ["name"] }, { model: CityTown, as: "corporate_city", attributes: ["name"] }, { model: Emirate, as: "corporate_emirate", attributes: ["name"] }, { model: user, as: "created_user", attributes: ["name"] }, ], order: [[sort_by, sort_order]], ...(exportType ? {} : { limit, offset }), // pagination only when not exporting }); // If export = excel → generate file if (exportType && exportType.toLowerCase() === "excel") { const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet("Establishments"); // 🧩 Define headers worksheet.columns = [ { header: "ID", key: "id", width: 10 }, { header: "Establishment Name", key: "factory_name", width: 30 }, { header: "Contact Name", key: "establishment_contact_person_name", width: 30 }, { header: "Emirate", key: "emirate_name", width: 20 }, { header: "ISIC Code", key: "isic_code", width: 20 }, { header: "Establishment ID", key: "establishment_code", width: 20 }, { header: "Total Employees", key: "total_employees", width: 20 }, { header: "Created By", key: "created_user", width: 20 }, { header: "Created On", key: "created_at", width: 20 }, { header: "Last Updated", key: "updated_at", width: 20 }, { header: "Status", key: "status", width: 15 }, // { header: "City/Town", key: "city_name", width: 20 }, // { header: "Contact Email", key: "contact_email", width: 30 }, // { header: "Corporate Email", key: "corporate_email", width: 30 }, ]; // 🧠 Format data data.forEach((item) => { worksheet.addRow({ id: item.id, factory_name: item.factory_name, establishment_contact_person_name: item.establishment_contact_person_name, emirate_name: item.establishment_emirate?.name || "-", isic_code: item.isic_code || "-", establishment_code: item.establishment_code, total_employees: item.total_employees, created_user: item.created_user?.name || "-", created_at: new Date(item.created_at).toLocaleDateString(), updated_at: new Date(item.updated_at).toLocaleDateString(), status: item.is_active ? "Active" : "Inactive", // city_name: item.establishment_city?.name || "-", // contact_email: item.establishment_contact_email || "-", // corporate_email: item.corporate_email || "-", }); }); // 🖋️ Styling header worksheet.getRow(1).eachCell((cell) => { cell.font = { bold: true }; cell.alignment = { horizontal: "center" }; cell.border = { top: { style: "thin" }, left: { style: "thin" }, bottom: { style: "thin" }, right: { style: "thin" }, }; }); // 📤 Send as Excel file res.setHeader( "Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ); res.setHeader( "Content-Disposition", `attachment; filename=Establishments_${Date.now()}.xlsx` ); await workbook.xlsx.write(res); return res.end(); } // Otherwise return JSON (paginated) const totalCount = await Establishment.count({ where: whereClause }); return res.status(200).json({ status: "success", message: "Fetched establishments successfully", data, pagination: { total_records: totalCount, current_page: page, total_pages: Math.ceil(totalCount / limit), limit, }, }); // return res.status(200).json({ // status: "success", // message: "Fetched establishments successfully", // data: rows, // pagination: { // total_records: count, // current_page: page, // total_pages: Math.ceil(count / limit), // limit, // }, // }); } catch (err) { console.error(err); return res.status(500).json({ status: "failed", message: err.message || "Internal server error", }); } }; // Get one establishment exports.getEstablishmentById = async (req, res) => { try { const data = await Establishment.findByPk(req.params.id, { include: [ { model: EstablishmentUser, as: "users", attributes: ["id", "name", "email", "gender", "is_active"], }, { model: CityTown, as: "establishment_city", attributes: ["name"], }, { model: Emirate, as: "establishment_emirate", attributes: ["name"], },{ model: CityTown, as: "corporate_city", attributes: ["name"], }, { model: Emirate, as: "corporate_emirate", attributes: ["name"], }, { model: user, as: "created_user", attributes: ["name"], }, { model: EstablishmentProduct, as: "establishment_products", attributes: ["id", "establishment_id", "product_id"], include: [ { model: Product, as: "product", attributes: ["product_name", "hs_code", "hs_description"], }, ], }, ], }); if (!data) return res.status(404).send({'status':"failed",'message':"Record Not found",'data': "" }); return res.status(200).send({'status':"success",'message':"Fetched successfully",'data': data }); } catch (err) { return res.status(500).send({'status':"failed",'message':err.message }); } }; // Update establishment exports.updateEstablishment = async (req, res) => { try { const { id } = req.params; const { establishment_products, establishment_user, ...estData } = req.body; const establishment = await Establishment.findByPk(id); if (!establishment) { return res.status(404).json({ status: "failed", message: "Record not found" }); } // check duplicate establishment_code if (estData.establishment_code) { const exists = await Establishment.findOne({ where: { establishment_code: estData.establishment_code, id: { [Op.ne]: id } } }); if (exists) { return res.status(400).json({ status:"failed", message:"establishment_code already exists" }); } } // update establishment await establishment.update(estData); // update user if needed if (establishment_user) { const user = await EstablishmentUser.findOne({where:{establishment_id:id}}); if (user) { let updateUser = { name: establishment_user.name, email: establishment_user.email }; if (establishment_user.password) { updateUser.password = await bcrypt.hash(establishment_user.password,10); } await user.update(updateUser); } } // update establishment products if (Array.isArray(establishment_products)) { let incomingIds = establishment_products.map(e => e.product_id); const oldProducts = await EstablishmentProduct.findAll({ where:{ establishment_id:id } }); const oldIds = oldProducts.map(e => e.product_id); // remove products not in new list const removeIds = oldIds.filter(v => !incomingIds.includes(v)); if (removeIds.length) { await EstablishmentProduct.destroy({ where:{ establishment_id:id, product_id:removeIds } }); } // add only new ones const newIds = incomingIds.filter(v => !oldIds.includes(v)); for (let pid of newIds) { await EstablishmentProduct.create({ establishment_id:id, product_id:pid }); } } return res.json({ status:"success", message:"Updated successfully" , data:"" }); } catch(err) { if (err.name === "SequelizeUniqueConstraintError") { const field = err.errors[0].path; return res.status(400).json({ status:"failed", message:`${field} already exists` }); } return res.status(500).json({ status:"failed", message: err.message }); } }; // exports.updateEstablishment = async (req, res) => { // try { // const [updated] = await Establishment.update(req.body, { // where: { id: req.params.id }, // }); // if (!updated){ return res.status(404).send({'status':"failed",'message':"Record Not found",'data': "" }); } // return res.status(200).send({'status':"success",'message':"Updated successfully",'data': "" }); // } catch (err) { // return res.status(500).send({'status':"failed",'message':err.message }); // } // }; // Delete establishment exports.deleteEstablishment = async (req, res) => { try { const deleted = await Establishment.destroy({ where: { id: req.params.id } }); if (!deleted) return res.status(404).send({'status':"failed",'message':"Record Not found",'data': "" }); return res.status(200).send({'status':"success",'message':"Deleted successfully",'data': "" }); } catch (err) { return res.status(500).send({'status':"failed",'message':err.message }); } }; exports.getAllEmirates = async (req, res) => { try { const emirates = await Emirate.findAll({ attributes: ["id", "name", "code"], order: [["name", "ASC"]], }); return res.status(200).send({status: "success", message: "Emirates fetched successfully", data: emirates,}); } catch (err) { return res.status(500).send({status: "failed",message: err.message || "Internal server error", }); } }; exports.getAllCityTowns = async (req, res) => { try { const { emirate_id } = req.query; const whereClause = {}; if (emirate_id) { whereClause.emirate_id = emirate_id; } const cities = await CityTown.findAll({ where: whereClause, attributes: ["id", "name", "emirate_id"], order: [["name", "ASC"]], }); return res.status(200).send({status: "success", message: "City/Town fetched successfully", data: cities, }); } catch (err) { return res.status(500).send({ status: "failed", message: err.message || "Internal server error", }); } }; exports.getAllRequests = async (req, res) => { try { const requests = await EstablishmentPasswordResetRequest.findAll({ order: [["created_at", "DESC"]], }); res.status(200).json({ status: "success", data: requests, }); } catch (err) { res.status(500).json({ status: "failed", message: err.message }); } }; exports.createRequest = async (req, res) => { try { const { establishment_name, establishment_code, registered_email, contact_person_name, contact_phone, additional_notes, } = req.body; // Validate input if (!establishment_name || !establishment_code || !registered_email) { return res.status(400).json({ status: "failed", message: "establishment_name, establishment_code, and registered_email are required", }); } // Find user by email const user = await EstablishmentUser.findOne({ where: { email: registered_email }, }); if (!user) return res.status(404).json({ status: "failed", message: "Registered email not found" }); // Find establishment by code const establishment = await Establishment.findOne({ where: { code: establishment_code }, }); if (!establishment) return res.status(404).json({ status: "failed", message: "Invalid establishment code" }); // Verify both match if (user.establishment_id !== establishment.id) return res.status(400).json({ status: "failed", message: "Establishment mismatch between code and email", }); // Create reset request const newRequest = await EstablishmentPasswordResetRequest.create({ establishment_name, establishment_code, registered_email, contact_person_name, contact_phone, additional_notes, establishment_user_id: user.id, establishment_id: establishment.id, }); // Trigger Email const subject = "Password Reset Request Received"; const body = ` Dear ${contact_person_name || establishment_name}, We have received your password reset request for establishment "${establishment_name}". Our team will verify and get back to you shortly. Regards, Support Team `; await sendEmail(registered_email, subject, body); res.status(201).json({ status: "success", message: "Password reset request created and email sent successfully", data: newRequest, }); } catch (err) { console.error(err); res.status(500).json({ status: "failed", message: err.message }); } }; exports.forgotPasswordRequestOTP = async (req, res) => { try { const { establishment_name, establishment_code, registered_email } = req.body; // Validate inputs if (!establishment_name || !establishment_code || !registered_email) return res.status(400).json({ status: "failed", message: "All fields are required" }); // Find establishment and user const establishment = await Establishment.findOne({ where: { establishment_code } }); if (!establishment) return res.status(404).json({ status: "failed", message: "Establishment not found" }); const user = await EstablishmentUser.findOne({ where: { email: registered_email, establishment_id: establishment.id }, }); if (!user) return res.status(404).json({ status: "failed", message: "User not found for this establishment" }); // Generate 6-digit OTP const otp = Math.floor(100000 + Math.random() * 900000).toString(); // Hash OTP before saving (security) const hashedOtp = await bcrypt.hash(otp, 10); // Store OTP in user record await user.update({ reset_otp: hashedOtp, reset_otp_expires_at: new Date(Date.now() + 10 * 60 * 1000), // valid for 10 mins }); // Send OTP email await sendEmail( registered_email, "Password Reset OTP", `

Dear ${user.name},

Your OTP for password reset is ${otp}. It is valid for 10 minutes.

` ); logger.info(`OTP sent to ${registered_email} for establishment_id=${establishment.id}`); return res.status(200).json({ status: "success", message: "OTP sent successfully to registered email", }); } catch (err) { logger.error(`Error in forgotPasswordRequestOTP: ${err.message}`); return res.status(500).json({ status: "failed", message: err.message }); } }; exports.forgotPasswordVerifyOTP = async (req, res) => { try { const { registered_email, otp, password, confirm_password } = req.body; if (!registered_email || !otp || !password || !confirm_password) return res.status(400).json({ status: "failed", message: "All fields are required" }); if (password !== confirm_password) return res.status(400).json({ status: "failed", message: "Passwords do not match" }); const user = await EstablishmentUser.findOne({ where: { email: registered_email } }); if (!user || !user.reset_otp) return res.status(404).json({ status: "failed", message: "verification code not found or invalid user" }); // Check OTP expiry if (new Date() > new Date(user.reset_otp_expires_at)) return res.status(400).json({ status: "failed", message: "Verification code has expired. Please request a new one" }); // Compare OTP const isOtpValid = await bcrypt.compare(otp, user.reset_otp); if (!isOtpValid) return res.status(400).json({ status: "failed", message: "Invalid verification code" }); // Update password const hashedPassword = await bcrypt.hash(password, 10); await user.update({ password: hashedPassword, reset_otp: null, reset_otp_expires_at: null, }); logger.info(`Password reset successful for user=${registered_email}`); return res.status(200).json({ status: "success", message: "Password reset successfully", }); } catch (err) { logger.error(`Error in forgotPasswordVerifyOTP: ${err.message}`); return res.status(500).json({ status: "failed", message: err.message }); } }; exports.establishmentBulkUpload = async (req, res) => { try { // Check if file exists if (!req.file) { return res.status(400).send({ status: "failed", message: "No file uploaded" }); } const filePath = req.file.path; // Validate file type if (!req.file.originalname.endsWith(".csv")) { fs.unlinkSync(filePath); return res.status(400).send({ status: "failed", message: "Invalid file type. Only CSV files are allowed.", }); } // Validate userId if (!req.body.userId || isNaN(req.body.userId)) { fs.unlinkSync(filePath); return res.status(400).send({ status: "failed", message: "Invalid or missing userId. Must be an integer.", }); } const results = []; const userId = parseInt(req.body.userId); // Read CSV and clean headers/values fs.createReadStream(filePath) .pipe(csv()) .on("data", (row) => { // Trim all keys and values to handle spaces in header names or values const cleanRow = {}; for (const key in row) { cleanRow[key.trim()] = row[key] ? row[key].trim() : null; } results.push(cleanRow); }) .on("end", async () => { try { // Check for empty CSV if (results.length === 0) { fs.unlinkSync(filePath); return res.status(400).send({ status: "failed", message: "CSV file is empty or invalid.", }); } const estCode = results.map((r) => r.EstablishmentId); const existingEstablishment = await Establishment.findAll({ where: { establishment_code: estCode }, }); const existingEstcode = existingEstablishment.map((p) => p.establishment_code); const toInsert = []; const duplicates = []; // Process CSV rows for (const row of results) { const establishmentId = row.EstablishmentId?.trim(); const factoryName = row.FactoryName?.trim(); const email = row.Email?.trim(); const emirateName = row.Emirate?.trim(); const totalEmployment = row.TotalEmployment; if (!establishmentId || !factoryName) continue; // skip invalid rows const emirateRecord = await Emirate.findOne({ where: { name: emirateName }, attributes: ['id'] }); const emirateId = emirateRecord ? emirateRecord.id : null; if (existingEstcode.includes(establishmentId)) { duplicates.push(establishmentId); } else { toInsert.push({ establishment_code: establishmentId, factory_name: factoryName, establishment_contact_email: email, establishment_emirate_id: emirateRecord ? emirateRecord.id : null, total_employees:totalEmployment, created_by: userId, created_at: new Date(), }); } } // Bulk insert new records let inserted = []; if (toInsert.length > 0) { inserted = await Establishment.bulkCreate(toInsert, { validate: true }); } // Delete file after processing fs.unlinkSync(filePath); // Send success response return res.status(200).send({ status: "success", message: '${inserted.length}Customer Profiles inserted.', inserted_count: inserted.length, duplicate_establishment: duplicates, }); } catch (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 }); } };