From e36857b7bf6272245ba0424985e13b420f5cdb33 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Mon, 10 Nov 2025 11:12:14 +0530 Subject: [PATCH] Establishment bulkupload --- app/controllers/establishment.controller.js | 126 ++++++++++++++++++++ app/models/UnitMaster.model.js | 4 + app/routes/routes.js | 34 ++++++ 3 files changed, 164 insertions(+) diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index d639cb2..03f8c2b 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -13,6 +13,9 @@ 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 = { @@ -817,4 +820,127 @@ exports.forgotPasswordVerifyOTP = async (req, res) => { }; +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 }); + } + }; + + diff --git a/app/models/UnitMaster.model.js b/app/models/UnitMaster.model.js index 6e73408..fb29c12 100644 --- a/app/models/UnitMaster.model.js +++ b/app/models/UnitMaster.model.js @@ -28,6 +28,10 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.STRING, allowNull: true, }, + description: { + type: DataTypes.STRING, + allowNull: true, + }, is_active: { type: DataTypes.BOOLEAN, defaultValue: true, diff --git a/app/routes/routes.js b/app/routes/routes.js index bbab8ff..931b269 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -666,6 +666,38 @@ router.put("/establishments/:id",[verifySignature, verifyToken], establishmentCo router.delete("/establishments/:id",[verifySignature, verifyToken], establishmentController.deleteEstablishment); +/** + * @swagger + * /api/establishments/uploadCSV: + * post: + * summary: Upload Establishment data in bulk using CSV file + * description: This API accepts CSV file and inserts multiple Establishment in bulk. CSV header columns must match Establishment table columns. + * tags: [Establishments] + * security: + * - appSignature: [] + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * multipart/form-data: + * schema: + * type: object + * properties: + * file: + * type: string + * format: binary + * description: CSV file to upload + * responses: + * 200: + * description: Customer Profiles inserted successfully. + * 400: + * description: No file uploaded + * 500: + * description: Server error + */ +router.post("/establishments/uploadCSV",[verifySignature, verifyToken, upload.single("file")], establishmentController.establishmentBulkUpload); + + @@ -1675,6 +1707,7 @@ router.get("/unit_master/:id",[verifySignature, verifyToken], unitMasterControll * is_base_unit: { type : boolean } * base_unit_id: { type : integer } * factor: { type : string } + * description: { type : string } * is_active: { type : boolean } * responses: * 201: @@ -1709,6 +1742,7 @@ router.post("/unit_master",[verifySignature, verifyToken], unitMasterController. * is_base_unit: { type : boolean } * base_unit_id: { type : integer } * factor: { type : string } + * description: { type : string } * is_active: { type : boolean } * responses: * 200: