From af72fa4c98f9838a42a0e3a0f5721dd4ebd0bba4 Mon Sep 17 00:00:00 2001 From: yashwin-foxy Date: Mon, 22 Sep 2025 19:23:19 +0530 Subject: [PATCH] api's created for zoho people --- src/api/controllers/integrationController.js | 52 ++++- src/api/routes/integrationRoutes.js | 8 +- src/integrations/zoho/client.js | 65 ++++++- src/integrations/zoho/mapper.js | 193 +++++++++++++++++-- 4 files changed, 302 insertions(+), 16 deletions(-) diff --git a/src/api/controllers/integrationController.js b/src/api/controllers/integrationController.js index c124136..1911e1d 100644 --- a/src/api/controllers/integrationController.js +++ b/src/api/controllers/integrationController.js @@ -1,5 +1,6 @@ const { success, failure } = require('../../utils/response'); const IntegrationService = require('../../services/integration/integrationService'); +const ZohoClient = require('../../integrations/zoho/client'); async function getData(req, res) { try { @@ -903,6 +904,54 @@ async function getPerformanceData(req, res) { } } +// Get attendance report for users +const getUserReport = async (req, res) => { + try { + const { sdate, edate, dateFormat, startIndex } = req.query; + const userId = req.user.id; + + const zohoClient = new ZohoClient(userId); + const attendanceReport = await zohoClient.getUserReport({ + sdate, + edate, + dateFormat, + startIndex + }); + + res.json(success('Attendance report retrieved successfully', attendanceReport)); + } catch (error) { + res.status(400).json(failure(error.message, 'INTEGRATION_ERROR')); + } +} + +// Get leave tracker report +const getLeaveTrackerReport = async (req, res) => { + try { + const userId = req.user.id; + + const zohoClient = new ZohoClient(userId); + const leaveTrackerReport = await zohoClient.getLeaveTrackerReport(); + + res.json(success('Leave tracker report retrieved successfully', leaveTrackerReport)); + } catch (error) { + res.status(400).json(failure(error.message, 'INTEGRATION_ERROR')); + } +} + +// Get holidays +const getHolidays = async (req, res) => { + try { + const userId = req.user.id; + + const zohoClient = new ZohoClient(userId); + const holidays = await zohoClient.getHolidays(); + + res.json(success('Holidays retrieved successfully', holidays)); + } catch (error) { + res.status(400).json(failure(error.message, 'INTEGRATION_ERROR')); + } +} + module.exports = { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks, getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases, getSalesOrders, @@ -910,5 +959,6 @@ module.exports = { getOrganizations, getCustomers, getVendors, getItems, getEstimates, getBills, getExpenses, getBankAccounts, getBankTransactions, getReports, getBooksSalesOrders, getBooksPurchaseOrders, getContacts, getBooksContacts, getBooksInvoices, getEmployeeForms, getEmployeeById, - getAttendanceEntries, getShiftConfiguration, getLeaveData, getGoalsData, getPerformanceData + getAttendanceEntries, getShiftConfiguration, getLeaveData, getGoalsData, getPerformanceData, + getUserReport, getLeaveTrackerReport, getHolidays }; diff --git a/src/api/routes/integrationRoutes.js b/src/api/routes/integrationRoutes.js index f3fe38a..d137cb0 100644 --- a/src/api/routes/integrationRoutes.js +++ b/src/api/routes/integrationRoutes.js @@ -7,7 +7,8 @@ const { getOrganizations, getCustomers, getVendors, getItems, getEstimates, getBills, getExpenses, getBankAccounts, getBankTransactions, getReports, getBooksSalesOrders, getBooksPurchaseOrders, getContacts, getBooksContacts, getBooksInvoices, getEmployeeForms, getEmployeeById, - getAttendanceEntries, getShiftConfiguration, getLeaveData, getGoalsData, getPerformanceData + getAttendanceEntries, getShiftConfiguration, getLeaveData, getGoalsData, getPerformanceData, + getUserReport, getLeaveTrackerReport, getHolidays } = require('../controllers/integrationController'); const auth = require('../middlewares/auth'); const ZohoHandler = require('../../integrations/zoho/handler'); @@ -245,6 +246,11 @@ router.get('/zoho/people/leave-data', auth, validate(leaveDataSchema), getLeaveD router.get('/zoho/people/goals-data', auth, validate(goalsDataSchema), getGoalsData); router.get('/zoho/people/performance-data', auth, validate(performanceDataSchema), getPerformanceData); +// Zoho People Reports API routes +router.get('/zoho/people/attendance-report', auth, getUserReport); +router.get('/zoho/people/leave-tracker-report', auth, getLeaveTrackerReport); +router.get('/zoho/people/holidays', auth, getHolidays); + // Zoho Books specific routes const organizationsSchema = Joi.object({ provider: Joi.string().valid('zoho').required(), diff --git a/src/integrations/zoho/client.js b/src/integrations/zoho/client.js index dca7b78..91600c6 100644 --- a/src/integrations/zoho/client.js +++ b/src/integrations/zoho/client.js @@ -54,6 +54,7 @@ class ZohoClient { return response.data; } catch (error) { + // console.log('error in makeRequest',JSON.stringify(error)) if (error.response?.status === 401) { await this.refreshToken(); const newTokens = await this.getTokens(); @@ -190,9 +191,71 @@ class ZohoClient { async getPerformanceData(formLinkName = 'performance', params = {}) { const response = await this.makeRequest(`/people/api/forms/${formLinkName}/getRecords`, { params }, 'people'); + console.log('performance response i got',response) return ZohoMapper.mapApiResponse(response, 'performance_data'); } + async getUserReport(params = {}) { + // Set default to last 7 days if no dates provided + if (!params.sdate || !params.edate) { + const today = new Date(); + const sevenDaysAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000); + params.sdate = sevenDaysAgo.toISOString().split('T')[0]; + params.edate = today.toISOString().split('T')[0]; + } + + // Set default date format if not provided + if (!params.dateFormat) { + params.dateFormat = 'yyyy-MM-dd'; + } + + // Set default start index if not provided + if (!params.startIndex) { + params.startIndex = 1; + } + + const response = await this.makeRequest('/people/api/attendance/getUserReport', { params }, 'people'); + return ZohoMapper.mapApiResponse(response, 'attendance_report'); + } + + async getLeaveTrackerReport(params = {}) { + // Set default parameters if not provided + if (!params.from) { + // Current leave year from - start of current year + const currentYear = new Date().getFullYear(); + const startDate = new Date(currentYear, 0, 1); // January 1st + params.from = this.formatDate(startDate); + } + + if (!params.to) { + // Current date + params.to = this.formatDate(new Date()); + } + + if (!params.unit) { + // Default unit is Day + params.unit = 'Day'; + } + + const response = await this.makeRequest('/people/api/v2/leavetracker/reports/bookedAndBalance', { params }, 'people'); + return ZohoMapper.mapApiResponse(response, 'leave_tracker_report'); + } + + // Helper method to format date as DD-MMM-YYYY + formatDate(date) { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + const day = date.getDate().toString().padStart(2, '0'); + const month = months[date.getMonth()]; + const year = date.getFullYear(); + return `${day}-${month}-${year}`; + } + + async getHolidays(params = {}) { + const response = await this.makeRequest('/people/api/leave/v2/holidays/get', { params }, 'people'); + return ZohoMapper.mapApiResponse(response, 'holidays'); + } + // Zoho Books methods async getOrganizations(params = {}) { const response = await this.makeRequest('/books/v3/organizations', { params }, 'books'); @@ -347,7 +410,7 @@ class ZohoClient { getAvailableResources(service) { const resources = { 'crm': ['leads', 'contacts', 'deals', 'tasks', 'sales_orders', 'purchase_orders', 'invoices'], - 'people': ['employees', 'departments', 'timesheets', 'leave_requests', 'attendance', 'employee_forms', 'attendance_entries', 'shift_configuration', 'leave_data', 'goals_data', 'performance_data'], + 'people': ['employees', 'departments', 'timesheets', 'leave_requests', 'attendance', 'employee_forms', 'attendance_entries', 'shift_configuration', 'leave_data', 'goals_data', 'performance_data', 'attendance_report', 'leave_tracker_report', 'holidays'], 'books': ['organizations', 'customers', 'vendors', 'items', 'invoices', 'estimates', 'bills', 'expenses', 'bank_accounts', 'bank_transactions', 'reports', 'sales_orders', 'purchase_orders'], 'projects': ['projects', 'tasks', 'timesheets'] }; diff --git a/src/integrations/zoho/mapper.js b/src/integrations/zoho/mapper.js index 7987907..9e0bf0d 100644 --- a/src/integrations/zoho/mapper.js +++ b/src/integrations/zoho/mapper.js @@ -211,10 +211,37 @@ class ZohoMapper { employeeId: zohoAttendanceEntry.empId, emailId: zohoAttendanceEntry.emailId, date: zohoAttendanceEntry.date, - checkIn: zohoAttendanceEntry.checkIn, - checkOut: zohoAttendanceEntry.checkOut, - hoursWorked: zohoAttendanceEntry.hoursWorked, + // First In details + firstIn: zohoAttendanceEntry.firstIn, + firstIn_Location: zohoAttendanceEntry.firstIn_Location, + firstIn_Building: zohoAttendanceEntry.firstIn_Building, + firstIn_latitude: zohoAttendanceEntry.firstIn_latitude, + firstIn_longitude: zohoAttendanceEntry.firstIn_longitude, + // Last Out details + lastOut: zohoAttendanceEntry.lastOut, + lastOut_Location: zohoAttendanceEntry.lastOut_Location, + lastOut_Building: zohoAttendanceEntry.lastOut_Building, + lastOut_latitude: zohoAttendanceEntry.lastOut_latitude, + lastOut_longitude: zohoAttendanceEntry.lastOut_longitude, + // Summary + totalHrs: zohoAttendanceEntry.totalHrs, + allowedToCheckIn: zohoAttendanceEntry.allowedToCheckIn, status: zohoAttendanceEntry.status, + // Detailed entries array + entries: zohoAttendanceEntry.entries ? zohoAttendanceEntry.entries.map(entry => ({ + checkIn: entry.checkIn, + checkOut: entry.checkOut, + checkIn_Location: entry.checkIn_Location, + checkOut_Location: entry.checkOut_Location, + checkIn_Building: entry.checkIn_Building, + checkOut_Building: entry.checkOut_Building, + checkIn_Latitude: entry.checkIn_Latitude, + checkIn_Longitude: entry.checkIn_Longitude, + checkOut_Latitude: entry.checkOut_Latitude, + checkOut_Longitude: entry.checkOut_Longitude, + sourceOfPunchIn: entry.sourceOfPunchIn, + sourceOfPunchOut: entry.sourceOfPunchOut + })) : [], customFields: this.mapCustomFields(zohoAttendanceEntry) }; } @@ -236,14 +263,20 @@ class ZohoMapper { // Map Zoho People Leave Data to standardized format static mapLeaveData(zohoLeaveData) { return { - id: zohoLeaveData.id, - employeeId: zohoLeaveData.employeeId, - leaveType: zohoLeaveData.leaveType, - startDate: zohoLeaveData.startDate, - endDate: zohoLeaveData.endDate, - days: zohoLeaveData.days, - reason: zohoLeaveData.reason, - status: zohoLeaveData.status, + id: zohoLeaveData.Id || zohoLeaveData.id, + employeeId: zohoLeaveData.empId || zohoLeaveData.employeeId, + employeeName: zohoLeaveData.employeeName || zohoLeaveData.employee_name, + leaveType: zohoLeaveData.leaveType || zohoLeaveData.leave_type, + leaveTypeName: zohoLeaveData.leaveTypeName || zohoLeaveData.leave_type_name, + startDate: zohoLeaveData.startDate || zohoLeaveData.start_date, + endDate: zohoLeaveData.endDate || zohoLeaveData.end_date, + days: zohoLeaveData.days || zohoLeaveData.total_days, + reason: zohoLeaveData.reason || zohoLeaveData.leave_reason, + status: zohoLeaveData.status || zohoLeaveData.leave_status, + appliedDate: zohoLeaveData.appliedDate || zohoLeaveData.applied_date, + approvedBy: zohoLeaveData.approvedBy || zohoLeaveData.approved_by, + approvedDate: zohoLeaveData.approvedDate || zohoLeaveData.approved_date, + comments: zohoLeaveData.comments || zohoLeaveData.leave_comments, customFields: this.mapCustomFields(zohoLeaveData) }; } @@ -279,6 +312,83 @@ class ZohoMapper { }; } + // Map Zoho People Attendance Report to standardized format + static mapAttendanceReport(zohoAttendanceReport) { + return { + employeeId: zohoAttendanceReport.employeeDetails?.id, + employeeName: `${zohoAttendanceReport.employeeDetails?.['first name']} ${zohoAttendanceReport.employeeDetails?.['last name']}`, + email: zohoAttendanceReport.employeeDetails?.['mail id'], + employeeNumber: zohoAttendanceReport.employeeDetails?.erecno, + attendanceDetails: zohoAttendanceReport.attendanceDetails ? + Object.entries(zohoAttendanceReport.attendanceDetails).map(([date, details]) => ({ + date, + shiftStartTime: details.ShiftStartTime, + shiftEndTime: details.ShiftEndTime, + shiftName: details.ShiftName, + status: details.Status, + firstIn: details.FirstIn, + lastOut: details.LastOut, + firstInLocation: details.FirstIn_Location, + lastOutLocation: details.LastOut_Location, + totalHours: details.TotalHours, + workingHours: details.WorkingHours, + paidBreakHours: details.paidBreakHours, + unPaidBreakHours: details.unPaidBreakHours, + deviationTime: details.DeviationTime + })) : [] + }; + } + + // Map Zoho People Leave Tracker Report to standardized format + static mapLeaveTrackerReport(zohoLeaveTrackerReport) { + return { + leaveTypes: zohoLeaveTrackerReport.leavetypes ? + Object.entries(zohoLeaveTrackerReport.leavetypes).map(([id, type]) => ({ + id, + name: type.name, + code: type.code, + unit: type.unit, + type: type.type + })) : [], + employees: zohoLeaveTrackerReport.employees || [], + reports: zohoLeaveTrackerReport.report ? + Object.entries(zohoLeaveTrackerReport.report).map(([employeeId, report]) => ({ + employeeId, + employeeName: report.employee?.name, + leaveBalances: Object.entries(report).filter(([key]) => key !== 'totals' && key !== 'employee').map(([leaveTypeId, balance]) => ({ + leaveTypeId, + booked: balance.booked || 0, + balance: balance.balance || 0 + })), + totals: { + paidBooked: report.totals?.paidBooked || 0, + paidBalance: report.totals?.paidBalance || 0, + unpaidBooked: report.totals?.unpaidBooked || 0, + unpaidBalance: report.totals?.unpaidBalance || 0, + ondutyBooked: report.totals?.ondutyBooked || 0, + ondutyBalance: report.totals?.ondutyBalance || 0 + } + })) : [] + }; + } + + // Map Zoho People Holidays to standardized format + static mapHoliday(zohoHoliday) { + return { + id: zohoHoliday.Id, + name: zohoHoliday.Name, + date: zohoHoliday.Date, + remarks: zohoHoliday.Remarks, + locationId: zohoHoliday.LocationId, + locationName: zohoHoliday.LocationName, + shiftId: zohoHoliday.ShiftId, + shiftName: zohoHoliday.ShiftName, + isHalfday: zohoHoliday.isHalfday, + isRestrictedHoliday: zohoHoliday.isRestrictedHoliday, + session: zohoHoliday.Session + }; + } + // Map Zoho Books Organization to standardized format static mapOrganization(zohoOrganization) { return { @@ -541,6 +651,9 @@ class ZohoMapper { 'leave_data': this.mapLeaveData, 'goals_data': this.mapGoalsData, 'performance_data': this.mapPerformanceData, + 'attendance_report': this.mapAttendanceReport, + 'leave_tracker_report': this.mapLeaveTrackerReport, + 'holidays': this.mapHoliday, // Books 'organizations': this.mapOrganization, 'customers': this.mapCustomer, @@ -781,9 +894,7 @@ class ZohoMapper { break; case 'employee_forms': - case 'attendance_entries': case 'shift_configuration': - case 'leave_data': case 'goals_data': case 'performance_data': // People Forms API response structure @@ -795,6 +906,62 @@ class ZohoMapper { }; break; + case 'leave_data': + // Leave data specific response structure + records = zohoResponse.response?.result || []; + pageInfo = { + count: records.length, + moreRecords: false, + page: 1, + message: zohoResponse.response?.message, + status: zohoResponse.response?.status + }; + break; + + case 'attendance_entries': + // Attendance entries specific response structure + records = zohoResponse.entries || []; + pageInfo = { + count: zohoResponse.info?.count || records.length, + moreRecords: zohoResponse.info?.more_records || false, + page: zohoResponse.info?.page || 1 + }; + break; + + case 'attendance_report': + // Attendance report response structure + records = zohoResponse.result || []; + pageInfo = { + count: records.length, + moreRecords: false, + page: 1, + message: zohoResponse.message, + status: zohoResponse.status + }; + break; + + case 'leave_tracker_report': + // Leave tracker report response structure + records = [zohoResponse]; // Wrap in array for consistency + pageInfo = { + count: 1, + moreRecords: false, + page: 1 + }; + break; + + case 'holidays': + // Holidays response structure + records = zohoResponse.data || []; + pageInfo = { + count: records.length, + moreRecords: false, + page: 1, + message: zohoResponse.message, + status: zohoResponse.status + }; + break; + case 'employee_detail': // Single employee detail response records = zohoResponse.data ? [zohoResponse.data] : [];