dss-server / src /controllers /vendorController.js
yeshwanth-kr's picture
Upload 43 files
8c7b7ca verified
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
};