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 };