diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index 2e8bdca..09906cc 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -10,6 +10,9 @@ const ExcelJS = require("exceljs"); const { Op } = require("sequelize"); const { sendEmail } = require("../services/emailHelper"); // custom helper const logger = require("../services/logger"); +const fs = require("fs"); +const csv = require("csv-parser"); +const path = require("path"); exports.createEstablishment = async (req, res) => { @@ -642,5 +645,124 @@ 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/routes/routes.js b/app/routes/routes.js index 4ba8b8d..49cda50 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -2634,6 +2634,37 @@ router.get("/emirates", [verifySignature, verifyToken], establishmentController. */ router.get("/city-towns", [verifySignature, verifyToken], establishmentController.getAllCityTowns); +/** + * @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: [Establishment] + * 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); +