Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/controllers/claim.controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { claimService } from '../services/claim.service.js';
import { ApiResponse } from '../utils/apiResponse.js';
import fs from 'fs';

export const submitClaim = async (req, res, next) => {
try {
Expand Down Expand Up @@ -55,6 +56,23 @@ export const updateClaimStatus = async (req, res, next) => {
reviewerId,
});

try {
const { Notification } = await import('../models/notification.model.js');
await Notification.create({
userId: updatedClaim.userId,
title: `Claim ${targetStatus}: ${updatedClaim.title}`,
body: `Your reimbursement claim for ₹${updatedClaim.amount} has been ${targetStatus.toLowerCase()}.${adminNotes || rejectionReason ? ` Note: ${adminNotes || rejectionReason}` : ''}`,
type: 'claim',
data: {
claimId: updatedClaim._id?.toString(),
status: targetStatus,
amount: updatedClaim.amount,
},
});
} catch (notifErr) {
console.warn('Claim notification warning:', notifErr.message);
}

return ApiResponse.success(
res,
`Claim status successfully updated to ${targetStatus}`,
Expand Down Expand Up @@ -100,3 +118,43 @@ export const getClaimDetails = async (req, res, next) => {
}
};

export const clearAllClaims = async (req, res, next) => {
try {
const employerId = req.query.employerId || null;
const result = await claimService.clearAllClaims(employerId);
return ApiResponse.success(res, 'All demo claims cleared successfully', result);
} catch (error) {
next(error);
}
};

export const deleteClaim = async (req, res, next) => {
try {
const { claimId } = req.params;
const result = await claimService.deleteClaim(claimId);
return ApiResponse.success(res, 'Claim deleted successfully', result);
} catch (error) {
next(error);
}
};

export const getClaimReceiptImage = async (req, res, next) => {
try {
const { claimId } = req.params;
const { filePath, mimeType, originalName } = await claimService.getClaimReceiptImageFile(claimId);

res.setHeader('Content-Type', mimeType);
res.setHeader('Cache-Control', 'public, max-age=86400');
res.setHeader('Content-Disposition', `inline; filename="${originalName}"`);

const stream = fs.createReadStream(filePath);
stream.pipe(res);
} catch (error) {
if (error.statusCode === 404) {
return ApiResponse.error(res, error.message, 404);
}
next(error);
}
};


157 changes: 150 additions & 7 deletions src/controllers/employer.controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,132 @@
import { employerService } from '../services/employer.service.js';
import { UserModel } from '../models/user.model.js';
import { ClaimModel } from '../models/claim.model.js';
import { EmployerModel } from '../models/employer.model.js';
import { ApiResponse } from '../utils/apiResponse.js';

/**
* Generate a new unique employee invite code (Employer Dashboard)
*/
export const generateInviteCode = async (req, res, next) => {
try {
const {
companyId,
companyName,
department,
monthlyAllowance,
role,
expiryDays,
} = req.body;
const createdBy = req.user?.name || req.user?.email || 'Admin';

const invite = await employerService.generateInviteCode({
companyId,
companyName,
department,
monthlyAllowance,
role,
createdBy,
expiryDays,
});

return ApiResponse.success(res, 'Invite code generated successfully', invite, 201);
} catch (error) {
next(error);
}
};

/**
* List all invite codes generated for a company (Employer Dashboard)
*/
export const getCompanyInviteCodes = async (req, res, next) => {
try {
const { companyId } = req.query;
const invites = await employerService.getCompanyInviteCodes(companyId);
return ApiResponse.success(res, 'Company invite codes retrieved', {
invites,
total: invites.length,
});
} catch (error) {
next(error);
}
};

/**
* Verify an invite code (Mobile App - before joining)
*/
export const verifyInviteCode = async (req, res, next) => {
try {
const { code } = req.body;
const userId = req.user?.id || req.body?.userId;
const verified = await employerService.verifyInviteCode(code, userId);
return ApiResponse.success(res, 'Invite code is valid', verified);
} catch (error) {
if (error.statusCode) {
return ApiResponse.error(res, error.message, error.statusCode, { code: error.code });
}
next(error);
}
};

/**
* Claim an invite code and join the organization (Mobile App)
*/
export const claimInviteCode = async (req, res, next) => {
try {
const userId = req.user?.id || req.body.userId;
if (!userId) {
return ApiResponse.error(res, 'User authentication required to join company', 401);
}

const { code, name, email, phone, employeeId } = req.body;
const result = await employerService.claimInviteCode(userId, code, {
name: name || req.user?.name,
email: email || req.user?.email,
phone: phone || req.user?.phone,
employeeId,
});

return ApiResponse.success(
res,
`Successfully joined ${result.linkedEmployer.employerName}!`,
result,
201
);
} catch (error) {
if (error.statusCode) {
return ApiResponse.error(res, error.message, error.statusCode, { code: error.code });
}
next(error);
}
};

/**
* Get currently linked company for the mobile user
*/
export const getMyCompany = async (req, res, next) => {
try {
const userId = req.user?.id;
const linked = await employerService.getLinkedEmployer(userId);

if (!linked) {
return ApiResponse.success(res, 'No company currently linked', {
isLinked: false,
company: null,
});
}

return ApiResponse.success(res, 'Company details retrieved', {
isLinked: true,
company: linked,
});
} catch (error) {
next(error);
}
};

/**
* Link employer directly
*/
export const linkEmployer = async (req, res, next) => {
try {
const userId = req.user?.id;
Expand All @@ -27,6 +151,9 @@ export const linkEmployer = async (req, res, next) => {
}
};

/**
* Get linked employer
*/
export const getLinkedEmployer = async (req, res, next) => {
try {
const userId = req.user?.id;
Expand All @@ -42,6 +169,9 @@ export const getLinkedEmployer = async (req, res, next) => {
}
};

/**
* Unlink employer
*/
export const unlinkEmployer = async (req, res, next) => {
try {
const userId = req.user?.id;
Expand All @@ -55,12 +185,25 @@ export const unlinkEmployer = async (req, res, next) => {
}
};

/**
* Get company employees for the employer dashboard
*/
export const getCompanyEmployees = async (req, res, next) => {
try {
const users = await UserModel.find().sort({ createdAt: -1 }).lean();
const claims = await ClaimModel.find().lean();
const linkedEmployers = await EmployerModel.find().lean();

const employerMap = new Map();
linkedEmployers.forEach((le) => {
employerMap.set(le.userId, le);
});

// Only include users who have actually linked to this employer
const enrolledUsers = users.filter((u) => employerMap.has(u._id.toString()));

const employees = users.map((user) => {
const employees = enrolledUsers.map((user) => {
const empLink = employerMap.get(user._id.toString());
const userClaims = claims.filter(
(c) => c.userId === user._id.toString() || c.userId === user.email
);
Expand All @@ -75,10 +218,10 @@ export const getCompanyEmployees = async (req, res, next) => {
name,
email: user.email,
phone: user.phone || '+91 98000 00000',
department: user.department || 'Engineering',
role: user.role || 'Software Engineer',
department: empLink?.department || 'Engineering',
role: empLink?.role || 'Team Member',
status: 'Active',
monthlyAllowance: user.salary ? Math.round(user.salary * 0.3) : 35000,
monthlyAllowance: empLink?.monthlyAllowance || 25000,
totalReimbursed,
claimsCount: userClaims.length,
avatar:
Expand All @@ -88,9 +231,9 @@ export const getCompanyEmployees = async (req, res, next) => {
.join('')
.slice(0, 2)
.toUpperCase() || 'EM',
joinedDate: user.createdAt
? new Date(user.createdAt).toISOString().split('T')[0]
: '2024-04-01',
joinedDate: empLink?.linkedAt
? new Date(empLink.linkedAt).toISOString().split('T')[0]
: '2026-09-23',
};
});

Expand Down
105 changes: 105 additions & 0 deletions src/controllers/notification.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Notification } from '../models/notification.model.js';
import { ApiResponse } from '../utils/apiResponse.js';

export const getNotifications = async (req, res, next) => {
try {
const userId = req.user?.id || 'usr_me';
let userPhone = req.query.phone || req.user?.phone || '';
if (!userPhone && userId && userId !== 'usr_me') {
try {
const { UserModel } = await import('../models/user.model.js');
const u = await UserModel.findById(userId).select('phone');
if (u && u.phone) {
userPhone = u.phone;
}
} catch (err) {
console.warn('Phone resolution warning:', err.message);
}
}
const cleanPhone = userPhone ? userPhone.replace(/\D/g, '').slice(-10) : '';

const conditions = [{ userId }];
if (cleanPhone) {
conditions.push({ 'data.targetPhone': { $regex: cleanPhone + '$' } });
}

const notifications = await Notification.find({
$or: conditions,
})
.sort({ createdAt: -1 })
.limit(50);

return ApiResponse.success(res, 'Notifications fetched successfully', notifications);
} catch (error) {
next(error);
}
};

export const markAsRead = async (req, res, next) => {
try {
const { id } = req.params;
const notification = await Notification.findByIdAndUpdate(
id,
{ isRead: true },
{ new: true }
);
if (!notification) {
return ApiResponse.error(res, 'Notification not found', 404);
}
return ApiResponse.success(res, 'Notification marked as read', notification);
} catch (error) {
next(error);
}
};

export const markAllAsRead = async (req, res, next) => {
try {
const userId = req.user?.id || 'usr_me';
await Notification.updateMany({ userId, isRead: false }, { isRead: true });
return ApiResponse.success(res, 'All notifications marked as read');
} catch (error) {
next(error);
}
};

export const updateActionStatus = async (req, res, next) => {
try {
const { id } = req.params;
const { action } = req.body; // 'ACCEPTED' or 'DECLINED'

const notification = await Notification.findByIdAndUpdate(
id,
{ actionStatus: action, isRead: true },
{ new: true }
);
if (!notification) {
return ApiResponse.error(res, 'Notification not found', 404);
}
return ApiResponse.success(res, 'Notification action updated', notification);
} catch (error) {
next(error);
}
};

export const createNotification = async (req, res, next) => {
try {
const { userId, title, body, type, data } = req.body;
const targetUserId = userId || req.user?.id || 'usr_me';

if (!title || !body) {
return ApiResponse.error(res, 'Title and body are required', 400);
}

const notification = await Notification.create({
userId: targetUserId,
title,
body,
type: type || 'system',
data: data || {},
});

return ApiResponse.success(res, 'Notification created successfully', notification, 201);
} catch (error) {
next(error);
}
};
Loading
Loading