Spaces:
Runtime error
Runtime error
| const bcrypt = require('bcryptjs'); | |
| const { body } = require('express-validator'); | |
| const { User, Vendor } = require('../models'); | |
| const validate = require('../utils/validation'); | |
| const { parseVendorCode } = require('../utils/vendorCode'); | |
| const registerVendorValidators = [ | |
| body('company_name').notEmpty().withMessage('company_name is required'), | |
| body('vendor_code') | |
| .notEmpty() | |
| .withMessage('vendor_code is required') | |
| .custom((value) => { | |
| if (!parseVendorCode(value)) { | |
| throw new Error('vendor_code must be in format NAME4-DSS-YY-SEQ (e.g., YESH-DSS-26-001)'); | |
| } | |
| return true; | |
| }), | |
| body('contact_name').notEmpty().withMessage('contact_name is required'), | |
| body('contact_email').isEmail().withMessage('Valid contact_email is required'), | |
| body('contact_phone').notEmpty().withMessage('contact_phone is required'), | |
| validate | |
| ]; | |
| const completeVendorProfileValidators = [ | |
| body('pan').notEmpty().withMessage('pan is required'), | |
| body('gstin').notEmpty().withMessage('gstin is required'), | |
| body('epf_code').optional({ nullable: true }).isString(), | |
| body('esi_code').optional({ nullable: true }).isString(), | |
| body().custom((value) => { | |
| const epf = String(value?.epf_code || '').trim(); | |
| const esi = String(value?.esi_code || '').trim(); | |
| if (!epf && !esi) { | |
| throw new Error('Either epf_code or esi_code is required'); | |
| } | |
| return true; | |
| }), | |
| body('work_order_no').notEmpty().withMessage('work_order_no is required'), | |
| body('contract_start').isDate().withMessage('contract_start must be a valid date'), | |
| body('contract_end').isDate().withMessage('contract_end must be a valid date'), | |
| body('max_workers').isInt({ min: 1 }).withMessage('max_workers must be at least 1'), | |
| validate | |
| ]; | |
| function generateTempPassword(length = 12) { | |
| const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789@#$!'; | |
| let value = ''; | |
| for (let i = 0; i < length; i += 1) { | |
| value += chars[Math.floor(Math.random() * chars.length)]; | |
| } | |
| return value; | |
| } | |
| function isVendorProfileComplete(vendor) { | |
| return Boolean( | |
| vendor?.pan | |
| && vendor?.gstin | |
| && (vendor?.epf_code || vendor?.esi_code) | |
| && vendor?.work_order_no | |
| && vendor?.contract_start | |
| && vendor?.contract_end | |
| && vendor?.max_workers | |
| && vendor?.form1_path | |
| && vendor?.form2_path | |
| ); | |
| } | |
| async function registerVendor(req, res, next) { | |
| try { | |
| const { | |
| company_name, | |
| vendor_code, | |
| contact_name, | |
| contact_email, | |
| contact_phone | |
| } = req.body; | |
| const normalizedEmail = String(contact_email).toLowerCase(); | |
| const existingUser = await User.findOne({ email: normalizedEmail }).lean(); | |
| if (existingUser) { | |
| return res.status(409).json({ message: 'Vendor email is already registered' }); | |
| } | |
| const parsedVendorCode = parseVendorCode(vendor_code); | |
| if (!parsedVendorCode) { | |
| return res.status(400).json({ message: 'vendor_code must be in format NAME4-DSS-YY-SEQ (e.g., YESH-DSS-26-001)' }); | |
| } | |
| const existingVendorCode = await Vendor.findOne({ vendor_code: parsedVendorCode.normalized }).lean(); | |
| if (existingVendorCode) { | |
| return res.status(409).json({ message: 'Vendor code is already in use' }); | |
| } | |
| const existingName4 = await Vendor.findOne({ | |
| vendor_code: { $regex: `^${parsedVendorCode.name4}-DSS-`, $options: 'i' } | |
| }).lean(); | |
| if (existingName4) { | |
| return res.status(409).json({ message: `NAME4 prefix '${parsedVendorCode.name4}' is already assigned to another vendor` }); | |
| } | |
| const vendor = await Vendor.create({ | |
| name: company_name, | |
| vendor_code: parsedVendorCode.normalized, | |
| is_active: true | |
| }); | |
| const plainPassword = generateTempPassword(); | |
| const passwordHash = await bcrypt.hash(plainPassword, 10); | |
| await User.create({ | |
| full_name: contact_name, | |
| email: normalizedEmail, | |
| password_hash: passwordHash, | |
| role: 'Vendor', | |
| vendor_id: vendor._id, | |
| phone: contact_phone, | |
| must_reset_password: true, | |
| is_active: true, | |
| password_changed_at: new Date() | |
| }); | |
| return res.status(201).json({ | |
| message: 'Vendor account created. Share temporary credentials and request immediate password reset.', | |
| vendor_credentials: { | |
| email: normalizedEmail, | |
| temporary_password: plainPassword | |
| } | |
| }); | |
| } catch (error) { | |
| return next(error); | |
| } | |
| } | |
| async function getVendorProfile(req, res, next) { | |
| try { | |
| const vendorId = req.user.vendor_id; | |
| if (!vendorId) { | |
| return res.status(400).json({ message: 'Vendor context not found' }); | |
| } | |
| const vendor = await Vendor.findById(vendorId).lean(); | |
| if (!vendor) { | |
| return res.status(404).json({ message: 'Vendor not found' }); | |
| } | |
| return res.json({ | |
| id: String(vendor._id), | |
| name: vendor.name, | |
| pan: vendor.pan, | |
| gstin: vendor.gstin, | |
| vendor_code: vendor.vendor_code, | |
| epf_code: vendor.epf_code, | |
| esi_code: vendor.esi_code, | |
| work_order_no: vendor.work_order_no, | |
| contract_start: vendor.contract_start, | |
| contract_end: vendor.contract_end, | |
| max_workers: vendor.max_workers, | |
| form1_path: vendor.form1_path, | |
| form2_path: vendor.form2_path, | |
| profile_completed: isVendorProfileComplete(vendor) | |
| }); | |
| } catch (error) { | |
| return next(error); | |
| } | |
| } | |
| async function completeVendorProfile(req, res, next) { | |
| try { | |
| const vendorId = req.user.vendor_id; | |
| if (!vendorId) { | |
| return res.status(400).json({ message: 'Vendor context not found' }); | |
| } | |
| const vendor = await Vendor.findById(vendorId); | |
| if (!vendor) { | |
| return res.status(404).json({ message: 'Vendor not found' }); | |
| } | |
| const form1 = req.files?.form1?.[0]; | |
| const form2 = req.files?.form2?.[0]; | |
| const form1Path = form1 ? `uploads/${form1.filename}` : vendor.form1_path; | |
| const form2Path = form2 ? `uploads/${form2.filename}` : vendor.form2_path; | |
| if (!form1Path || !form2Path) { | |
| return res.status(400).json({ message: 'Form 1 and Form 2 are mandatory for profile completion' }); | |
| } | |
| if (!String(req.body.epf_code || '').trim() && !String(req.body.esi_code || '').trim()) { | |
| return res.status(400).json({ message: 'Either EPF Code or ESI Code is required' }); | |
| } | |
| if (new Date(req.body.contract_end) < new Date(req.body.contract_start)) { | |
| return res.status(400).json({ message: 'contract_end must be on or after contract_start' }); | |
| } | |
| vendor.pan = req.body.pan; | |
| vendor.gstin = req.body.gstin; | |
| vendor.epf_code = req.body.epf_code || null; | |
| vendor.esi_code = req.body.esi_code || null; | |
| vendor.work_order_no = req.body.work_order_no; | |
| vendor.contract_start = new Date(req.body.contract_start); | |
| vendor.contract_end = new Date(req.body.contract_end); | |
| vendor.max_workers = Number(req.body.max_workers); | |
| vendor.form1_path = form1Path; | |
| vendor.form2_path = form2Path; | |
| await vendor.save(); | |
| return res.json({ message: 'Vendor profile completed successfully' }); | |
| } catch (error) { | |
| return next(error); | |
| } | |
| } | |
| module.exports = { | |
| registerVendorValidators, | |
| completeVendorProfileValidators, | |
| registerVendor, | |
| getVendorProfile, | |
| completeVendorProfile, | |
| isVendorProfileComplete | |
| }; | |