zoho books api added along with zoho people (employee api tested)

This commit is contained in:
yashwin-foxy 2025-09-19 17:42:25 +05:30
parent 9e785012fe
commit efce4cc90d
6 changed files with 1825 additions and 61 deletions

View File

@ -278,4 +278,587 @@ async function getInvoices(req, res) {
}
}
module.exports = { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks, getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases, getSalesOrders, getPurchaseOrders, getInvoices };
// Zoho People specific controllers
async function getDepartments(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Departments are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const departments = await integrationService.getDepartments(provider, params);
res.json(success('Zoho Departments retrieved successfully', departments));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getLeaveRequests(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Leave Requests are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const leaveRequests = await integrationService.getLeaveRequests(provider, params);
res.json(success('Zoho Leave Requests retrieved successfully', leaveRequests));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getAttendance(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Attendance is only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const attendance = await integrationService.getAttendance(provider, params);
res.json(success('Zoho Attendance retrieved successfully', attendance));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
// Zoho Books specific controllers
async function getOrganizations(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Organizations are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const organizations = await integrationService.getOrganizations(provider, params);
res.json(success('Zoho Organizations retrieved successfully', organizations));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getCustomers(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Customers are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const customers = await integrationService.getCustomers(provider, params);
res.json(success('Zoho Customers retrieved successfully', customers));
} catch (error) {
console.log('customer response error i got', JSON.stringify(error))
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getVendors(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Vendors are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const vendors = await integrationService.getVendors(provider, params);
res.json(success('Zoho Vendors retrieved successfully', vendors));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getItems(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Items are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const items = await integrationService.getItems(provider, params);
res.json(success('Zoho Items retrieved successfully', items));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getEstimates(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Estimates are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const estimates = await integrationService.getEstimates(provider, params);
res.json(success('Zoho Estimates retrieved successfully', estimates));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getBills(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Bills are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const bills = await integrationService.getBills(provider, params);
res.json(success('Zoho Bills retrieved successfully', bills));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getExpenses(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Expenses are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const expenses = await integrationService.getExpenses(provider, params);
res.json(success('Zoho Expenses retrieved successfully', expenses));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getBankAccounts(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Bank Accounts are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const bankAccounts = await integrationService.getBankAccounts(provider, params);
res.json(success('Zoho Bank Accounts retrieved successfully', bankAccounts));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getBankTransactions(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Bank Transactions are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const bankTransactions = await integrationService.getBankTransactions(provider, params);
res.json(success('Zoho Bank Transactions retrieved successfully', bankTransactions));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getReports(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Reports are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const reports = await integrationService.getReports(provider, params);
res.json(success('Zoho Reports retrieved successfully', reports));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getBooksSalesOrders(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Books Sales Orders are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const salesOrders = await integrationService.getBooksSalesOrders(provider, params);
res.json(success('Zoho Books Sales Orders retrieved successfully', salesOrders));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getBooksPurchaseOrders(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Books Purchase Orders are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const purchaseOrders = await integrationService.getBooksPurchaseOrders(provider, params);
res.json(success('Zoho Books Purchase Orders retrieved successfully', purchaseOrders));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getContacts(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Contacts are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const contacts = await integrationService.getContacts(provider, params);
res.json(success('Zoho Books Contacts retrieved successfully', contacts));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
// Zoho People Forms API controllers
async function getEmployeeForms(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Employee Forms are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const employeeForms = await integrationService.getEmployeeForms(provider, params);
res.json(success('Zoho People Employee Forms retrieved successfully', employeeForms));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getEmployeeById(req, res) {
try {
const { provider, recordId, formLinkName } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Employee Details are only available for Zoho provider', 'INVALID_PROVIDER'));
}
if (!recordId) {
return res.status(400).json(failure('recordId is required', 'MISSING_RECORD_ID'));
}
const integrationService = new IntegrationService(req.user.uuid);
const employeeDetail = await integrationService.getEmployeeById(provider, recordId, formLinkName);
res.json(success('Zoho People Employee Details retrieved successfully', employeeDetail));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getAttendanceEntries(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Attendance Entries are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const attendanceEntries = await integrationService.getAttendanceEntries(provider, params);
res.json(success('Zoho People Attendance Entries retrieved successfully', attendanceEntries));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getShiftConfiguration(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Shift Configuration is only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const shiftConfig = await integrationService.getShiftConfiguration(provider, params);
res.json(success('Zoho People Shift Configuration retrieved successfully', shiftConfig));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getLeaveData(req, res) {
try {
const { provider, formLinkName, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Leave Data is only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const leaveData = await integrationService.getLeaveData(provider, formLinkName, params);
res.json(success('Zoho People Leave Data retrieved successfully', leaveData));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getGoalsData(req, res) {
try {
const { provider, formLinkName, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Goals Data is only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const goalsData = await integrationService.getGoalsData(provider, formLinkName, params);
res.json(success('Zoho People Goals Data retrieved successfully', goalsData));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getPerformanceData(req, res) {
try {
const { provider, formLinkName, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Performance Data is only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.uuid);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const performanceData = await integrationService.getPerformanceData(provider, formLinkName, params);
res.json(success('Zoho People Performance Data retrieved successfully', performanceData));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
module.exports = {
getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks,
getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases, getSalesOrders,
getPurchaseOrders, getInvoices, getDepartments, getLeaveRequests, getAttendance,
getOrganizations, getCustomers, getVendors, getItems, getEstimates, getBills,
getExpenses, getBankAccounts, getBankTransactions, getReports, getBooksSalesOrders,
getBooksPurchaseOrders, getContacts, getEmployeeForms, getEmployeeById,
getAttendanceEntries, getShiftConfiguration, getLeaveData, getGoalsData, getPerformanceData
};

View File

@ -1,6 +1,14 @@
const express = require('express');
const Joi = require('joi');
const { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks, getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases, getSalesOrders, getPurchaseOrders, getInvoices } = require('../controllers/integrationController');
const {
getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks,
getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases, getSalesOrders,
getPurchaseOrders, getInvoices, getDepartments, getLeaveRequests, getAttendance,
getOrganizations, getCustomers, getVendors, getItems, getEstimates, getBills,
getExpenses, getBankAccounts, getBankTransactions, getReports, getBooksSalesOrders,
getBooksPurchaseOrders, getContacts, getEmployeeForms, getEmployeeById,
getAttendanceEntries, getShiftConfiguration, getLeaveData, getGoalsData, getPerformanceData
} = require('../controllers/integrationController');
const auth = require('../middlewares/auth');
const ZohoHandler = require('../../integrations/zoho/handler');
@ -60,7 +68,8 @@ const portalsSchema = Joi.object({
provider: Joi.string().valid('zoho').required()
});
router.get('/portals', auth, validate(portalsSchema), getPortals);
// Zoho Projects specific routes with clear service identification
router.get('/zoho/projects/portals', auth, validate(portalsSchema), getPortals);
// Get all Zoho projects (across all portals)
const allProjectsSchema = Joi.object({
@ -70,7 +79,7 @@ const allProjectsSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/all-projects', auth, validate(allProjectsSchema), getAllProjects);
router.get('/zoho/projects/all-projects', auth, validate(allProjectsSchema), getAllProjects);
// Get all project tasks for a specific portal
const allProjectTasksSchema = Joi.object({
@ -81,7 +90,7 @@ const allProjectTasksSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/all-project-tasks', auth, validate(allProjectTasksSchema), getAllProjectTasks);
router.get('/zoho/projects/all-project-tasks', auth, validate(allProjectTasksSchema), getAllProjectTasks);
// Get all project task lists for a specific portal
const allProjectTaskListsSchema = Joi.object({
@ -92,7 +101,7 @@ const allProjectTaskListsSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/all-project-tasklists', auth, validate(allProjectTaskListsSchema), getAllProjectTaskLists);
router.get('/zoho/projects/all-project-tasklists', auth, validate(allProjectTaskListsSchema), getAllProjectTaskLists);
// Get all project issues for a specific portal
const allProjectIssuesSchema = Joi.object({
@ -103,7 +112,7 @@ const allProjectIssuesSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/all-project-issues', auth, validate(allProjectIssuesSchema), getAllProjectIssues);
router.get('/zoho/projects/all-project-issues', auth, validate(allProjectIssuesSchema), getAllProjectIssues);
// Get all project phases for a specific portal
const allProjectPhasesSchema = Joi.object({
@ -114,7 +123,7 @@ const allProjectPhasesSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/all-project-phases', auth, validate(allProjectPhasesSchema), getAllProjectPhases);
router.get('/zoho/projects/all-project-phases', auth, validate(allProjectPhasesSchema), getAllProjectPhases);
// Get Zoho Sales Orders
const salesOrdersSchema = Joi.object({
@ -124,7 +133,8 @@ const salesOrdersSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/sales-orders', auth, validate(salesOrdersSchema), getSalesOrders);
// Zoho CRM specific routes with clear service identification
router.get('/zoho/crm/sales-orders', auth, validate(salesOrdersSchema), getSalesOrders);
// Get Zoho Purchase Orders
const purchaseOrdersSchema = Joi.object({
@ -134,7 +144,7 @@ const purchaseOrdersSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/purchase-orders', auth, validate(purchaseOrdersSchema), getPurchaseOrders);
router.get('/zoho/crm/purchase-orders', auth, validate(purchaseOrdersSchema), getPurchaseOrders);
// Get Zoho Invoices
const invoicesSchema = Joi.object({
@ -144,12 +154,202 @@ const invoicesSchema = Joi.object({
filters: Joi.string().optional()
});
router.get('/invoices', auth, validate(invoicesSchema), getInvoices);
router.get('/zoho/crm/invoices', auth, validate(invoicesSchema), getInvoices);
// Zoho People specific routes
const departmentsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const leaveRequestsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const attendanceSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
// Zoho People Forms API schemas
const employeeFormsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const employeeByIdSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
recordId: Joi.string().required(),
formLinkName: Joi.string().default('employee')
});
const attendanceEntriesSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const shiftConfigurationSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const leaveDataSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
formLinkName: Joi.string().default('leave'),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const goalsDataSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
formLinkName: Joi.string().default('goals'),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const performanceDataSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
formLinkName: Joi.string().default('performance'),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
// Zoho People specific routes with clear service identification
router.get('/zoho/people/departments', auth, validate(departmentsSchema), getDepartments);
router.get('/zoho/people/leave-requests', auth, validate(leaveRequestsSchema), getLeaveRequests);
router.get('/zoho/people/attendance', auth, validate(attendanceSchema), getAttendance);
// Zoho People Forms API routes
router.get('/zoho/people/employee-forms', auth, validate(employeeFormsSchema), getEmployeeForms);
router.get('/zoho/people/employee/:recordId', auth, validate(employeeByIdSchema), getEmployeeById);
router.get('/zoho/people/attendance-entries', auth, validate(attendanceEntriesSchema), getAttendanceEntries);
router.get('/zoho/people/shift-configuration', auth, validate(shiftConfigurationSchema), getShiftConfiguration);
router.get('/zoho/people/leave-data', auth, validate(leaveDataSchema), getLeaveData);
router.get('/zoho/people/goals-data', auth, validate(goalsDataSchema), getGoalsData);
router.get('/zoho/people/performance-data', auth, validate(performanceDataSchema), getPerformanceData);
// Zoho Books specific routes
const organizationsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const customersSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const vendorsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const itemsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const estimatesSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const billsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const expensesSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const bankAccountsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const bankTransactionsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const reportsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const booksSalesOrdersSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
const booksPurchaseOrdersSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
// Zoho Books specific routes with clear service identification
router.get('/zoho/books/organizations', auth, validate(organizationsSchema), getOrganizations);
router.get('/zoho/books/contacts', auth, validate(customersSchema), getContacts);
router.get('/zoho/books/vendors', auth, validate(vendorsSchema), getVendors);
router.get('/zoho/books/items', auth, validate(itemsSchema), getItems);
router.get('/zoho/books/customers', auth, validate(customersSchema), getCustomers);
router.get('/zoho/books/invoices', auth, validate(invoicesSchema), getInvoices);
router.get('/zoho/books/estimates', auth, validate(estimatesSchema), getEstimates);
router.get('/zoho/books/bills', auth, validate(billsSchema), getBills);
router.get('/zoho/books/expenses', auth, validate(expensesSchema), getExpenses);
router.get('/zoho/books/bank-accounts', auth, validate(bankAccountsSchema), getBankAccounts);
router.get('/zoho/books/bank-transactions', auth, validate(bankTransactionsSchema), getBankTransactions);
router.get('/zoho/books/reports', auth, validate(reportsSchema), getReports);
router.get('/zoho/books/sales-orders', auth, validate(booksSalesOrdersSchema), getBooksSalesOrders);
router.get('/zoho/books/purchase-orders', auth, validate(booksPurchaseOrdersSchema), getBooksPurchaseOrders);
// Webhook endpoints (no auth required - uses signature verification)
const zohoHandler = new ZohoHandler();
router.post('/webhooks/zoho/crm', zohoHandler.handleCrmWebhook.bind(zohoHandler));
router.post('/webhooks/zoho/people', zohoHandler.handlePeopleWebhook.bind(zohoHandler));
router.post('/webhooks/zoho/books', zohoHandler.handleBooksWebhook.bind(zohoHandler));
router.post('/webhooks/zoho/projects', zohoHandler.handleProjectsWebhook.bind(zohoHandler));
router.post('/webhooks/zoho/bulkread', zohoHandler.handleBulkReadWebhook.bind(zohoHandler));

View File

@ -8,7 +8,8 @@ class ZohoClient {
this.userId = userId;
this.baseUrls = {
crm: 'https://www.zohoapis.com',
people: 'https://www.zohoapis.com',
people: 'https://people.zoho.com',
books: 'https://www.zohoapis.com',
projects: 'https://projectsapi.zoho.com'
};
}
@ -30,12 +31,13 @@ class ZohoClient {
const baseUrl = this.baseUrls[service] || this.baseUrls.crm;
const url = `${baseUrl}${endpoint}`;
// Transform limit to per_page for Zoho APIs
// Transform limit to per_page for Zoho APIs (except People APIs)
let params = options.params || {};
if (params.limit) {
if (params.limit && service !== 'people') {
params.per_page = params.limit;
delete params.limit;
}
// For People APIs, keep limit as is
const config = {
...options,
@ -133,6 +135,134 @@ class ZohoClient {
return ZohoMapper.mapApiResponse(response, 'employees');
}
async getDepartments(params = {}) {
const response = await this.makeRequest('/people/api/v1/departments', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'departments');
}
async getTimesheets(params = {}) {
const response = await this.makeRequest('/people/api/v1/timesheets', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'timesheets');
}
async getLeaveRequests(params = {}) {
const response = await this.makeRequest('/people/api/v1/leave', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'leave_requests');
}
async getAttendance(params = {}) {
const response = await this.makeRequest('/people/api/v1/attendance', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'attendance');
}
// Zoho People Forms API methods
async getEmployeeForms(params = {}) {
const response = await this.makeRequest('/people/api/forms/employee/getRecords', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'employee_forms');
}
async getEmployeeById(recordId, formLinkName = 'employee') {
const response = await this.makeRequest(`/people/api/forms/${formLinkName}/getDataByID`, {
params: { recordId }
}, 'people');
return ZohoMapper.mapApiResponse(response, 'employee_detail');
}
async getAttendanceEntries(params = {}) {
const response = await this.makeRequest('/people/api/attendance/getAttendanceEntries', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'attendance_entries');
}
async getShiftConfiguration(params = {}) {
const response = await this.makeRequest('/people/api/attendance/getShiftConfiguration', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'shift_configuration');
}
async getLeaveData(formLinkName = 'leave', params = {}) {
const response = await this.makeRequest(`/people/api/forms/${formLinkName}/getRecords`, { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'leave_data');
}
async getGoalsData(formLinkName = 'goals', params = {}) {
const response = await this.makeRequest(`/people/api/forms/${formLinkName}/getRecords`, { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'goals_data');
}
async getPerformanceData(formLinkName = 'performance', params = {}) {
const response = await this.makeRequest(`/people/api/forms/${formLinkName}/getRecords`, { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'performance_data');
}
// Zoho Books methods
async getOrganizations(params = {}) {
const response = await this.makeRequest('/books/v3/organizations', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'organizations');
}
async getCustomers(params = {}) {
const response = await this.makeRequest('/books/v3/contacts', { params :{ ...params, contact_type: 'customer' }}, 'books');
return ZohoMapper.mapApiResponse(response, 'contacts');
}
async getContacts(params = {}) {
const response = await this.makeRequest('/books/v3/contacts', { params}, 'books');
return ZohoMapper.mapApiResponse(response, 'contacts');
}
async getVendors(params = {}) {
const response = await this.makeRequest('/books/v3/contacts', { params: { ...params, contact_type: 'vendor' } }, 'books');
return ZohoMapper.mapApiResponse(response, 'vendors');
}
async getItems(params = {}) {
const response = await this.makeRequest('/books/v3/items', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'items');
}
async getInvoices(params = {}) {
const response = await this.makeRequest('/books/v3/invoices', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'invoices');
}
async getEstimates(params = {}) {
const response = await this.makeRequest('/books/v3/estimates', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'estimates');
}
async getBills(params = {}) {
const response = await this.makeRequest('/books/v3/bills', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'bills');
}
async getExpenses(params = {}) {
const response = await this.makeRequest('/books/v3/expenses', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'expenses');
}
async getBankAccounts(params = {}) {
const response = await this.makeRequest('/books/v3/bankaccounts', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'bank_accounts');
}
async getBankTransactions(params = {}) {
const response = await this.makeRequest('/books/v3/banktransactions', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'bank_transactions');
}
async getReports(params = {}) {
const response = await this.makeRequest('/books/v3/reports', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'reports');
}
async getBooksSalesOrders(params = {}) {
const response = await this.makeRequest('/books/v3/salesorders', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'sales_orders');
}
async getBooksPurchaseOrders(params = {}) {
const response = await this.makeRequest('/books/v3/purchaseorders', { params }, 'books');
return ZohoMapper.mapApiResponse(response, 'purchase_orders');
}
// Zoho Projects methods
async getPortals() {
const response = await this.makeRequest('/api/v3/portals', {}, 'projects');
@ -211,13 +341,14 @@ class ZohoClient {
// Service discovery
getAvailableServices() {
return ['crm', 'people', 'projects'];
return ['crm', 'people', 'books', 'projects'];
}
getAvailableResources(service) {
const resources = {
'crm': ['leads', 'contacts', 'deals', 'tasks', 'sales_orders', 'purchase_orders', 'invoices'],
'people': ['employees', 'departments'],
'people': ['employees', 'departments', 'timesheets', 'leave_requests', 'attendance', 'employee_forms', 'attendance_entries', 'shift_configuration', 'leave_data', 'goals_data', 'performance_data'],
'books': ['organizations', 'customers', 'vendors', 'items', 'invoices', 'estimates', 'bills', 'expenses', 'bank_accounts', 'bank_transactions', 'reports', 'sales_orders', 'purchase_orders'],
'projects': ['projects', 'tasks', 'timesheets']
};
return resources[service] || [];

View File

@ -178,6 +178,68 @@ class ZohoHandler {
}
}
// Handle Zoho Books webhook events
async handleBooksWebhook(req, res) {
try {
const signature = req.headers['x-zoho-signature'];
const payload = JSON.stringify(req.body);
if (!this.verifyWebhookSignature(payload, signature)) {
logger.warn('Invalid Zoho Books webhook signature', {
correlationId: logger.getCorrelationId(req),
ip: req.ip
});
return res.status(401).json({ status: 'error', message: 'Invalid signature' });
}
const { event, data } = req.body;
logger.info('Zoho Books webhook received', {
correlationId: logger.getCorrelationId(req),
event,
recordId: data?.id
});
// Process different Books events
switch (event) {
case 'customers.create':
await this.handleCustomerCreated(data);
break;
case 'customers.update':
await this.handleCustomerUpdated(data);
break;
case 'invoices.create':
await this.handleBooksInvoiceCreated(data);
break;
case 'invoices.update':
await this.handleBooksInvoiceUpdated(data);
break;
case 'bills.create':
await this.handleBillCreated(data);
break;
case 'bills.update':
await this.handleBillUpdated(data);
break;
case 'expenses.create':
await this.handleExpenseCreated(data);
break;
case 'expenses.update':
await this.handleExpenseUpdated(data);
break;
default:
logger.warn('Unknown Zoho Books event', { event });
}
res.json({ status: 'success', message: 'Webhook processed' });
} catch (error) {
logger.error('Zoho Books webhook processing failed', {
correlationId: logger.getCorrelationId(req),
error: error.message,
stack: error.stack
});
res.status(500).json({ status: 'error', message: 'Webhook processing failed' });
}
}
// CRM Event Handlers
async handleLeadCreated(data) {
logger.info('Lead created', { leadId: data.id, name: data.Full_Name });
@ -616,6 +678,47 @@ class ZohoHandler {
logger.info('Task updated', { taskId: data.id, name: data.name, projectId: data.project?.id });
// Add your business logic here
}
// Books Event Handlers
async handleCustomerCreated(data) {
logger.info('Customer created', { customerId: data.contact_id, name: data.contact_name });
// Add your business logic here
}
async handleCustomerUpdated(data) {
logger.info('Customer updated', { customerId: data.contact_id, name: data.contact_name });
// Add your business logic here
}
async handleBooksInvoiceCreated(data) {
logger.info('Books Invoice created', { invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, total: data.total });
// Add your business logic here
}
async handleBooksInvoiceUpdated(data) {
logger.info('Books Invoice updated', { invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, total: data.total });
// Add your business logic here
}
async handleBillCreated(data) {
logger.info('Bill created', { billId: data.bill_id, billNumber: data.bill_number, total: data.total });
// Add your business logic here
}
async handleBillUpdated(data) {
logger.info('Bill updated', { billId: data.bill_id, billNumber: data.bill_number, total: data.total });
// Add your business logic here
}
async handleExpenseCreated(data) {
logger.info('Expense created', { expenseId: data.expense_id, amount: data.amount, description: data.description });
// Add your business logic here
}
async handleExpenseUpdated(data) {
logger.info('Expense updated', { expenseId: data.expense_id, amount: data.amount, description: data.description });
// Add your business logic here
}
}
module.exports = ZohoHandler;

View File

@ -54,61 +54,61 @@ class ZohoMapper {
};
}
// Map Zoho CRM Sales Order to standardized format
// Map Zoho Sales Order to standardized format (handles both CRM and Books)
static mapSalesOrder(zohoSalesOrder) {
return {
id: zohoSalesOrder.id,
subject: zohoSalesOrder.Subject,
orderNumber: zohoSalesOrder.SO_Number,
account: zohoSalesOrder.Account_Name,
contact: zohoSalesOrder.Contact_Name,
id: zohoSalesOrder.id || zohoSalesOrder.salesorder_id,
subject: zohoSalesOrder.Subject || zohoSalesOrder.subject,
orderNumber: zohoSalesOrder.SO_Number || zohoSalesOrder.salesorder_number,
account: zohoSalesOrder.Account_Name || zohoSalesOrder.customer_name,
contact: zohoSalesOrder.Contact_Name || zohoSalesOrder.contact_name,
deal: zohoSalesOrder.Deal_Name,
grandTotal: zohoSalesOrder.Grand_Total,
status: zohoSalesOrder.Status,
orderDate: zohoSalesOrder.Order_Date,
dueDate: zohoSalesOrder.Due_Date,
createdTime: zohoSalesOrder.Created_Time,
modifiedTime: zohoSalesOrder.Modified_Time,
owner: zohoSalesOrder.Owner?.name,
grandTotal: zohoSalesOrder.Grand_Total || zohoSalesOrder.total,
status: zohoSalesOrder.Status || zohoSalesOrder.status,
orderDate: zohoSalesOrder.Order_Date || zohoSalesOrder.date,
dueDate: zohoSalesOrder.Due_Date || zohoSalesOrder.due_date,
createdTime: zohoSalesOrder.Created_Time || zohoSalesOrder.created_time,
modifiedTime: zohoSalesOrder.Modified_Time || zohoSalesOrder.last_modified_time,
owner: zohoSalesOrder.Owner?.name || zohoSalesOrder.owner?.name,
customFields: this.mapCustomFields(zohoSalesOrder)
};
}
// Map Zoho CRM Purchase Order to standardized format
// Map Zoho Purchase Order to standardized format (handles both CRM and Books)
static mapPurchaseOrder(zohoPurchaseOrder) {
return {
id: zohoPurchaseOrder.id,
subject: zohoPurchaseOrder.Subject,
orderNumber: zohoPurchaseOrder.PO_Number,
vendor: zohoPurchaseOrder.Vendor_Name,
account: zohoPurchaseOrder.Account_Name,
grandTotal: zohoPurchaseOrder.Grand_Total,
status: zohoPurchaseOrder.Status,
orderDate: zohoPurchaseOrder.PO_Date,
dueDate: zohoPurchaseOrder.Expected_Delivery_Date,
createdTime: zohoPurchaseOrder.Created_Time,
modifiedTime: zohoPurchaseOrder.Modified_Time,
owner: zohoPurchaseOrder.Owner?.name,
id: zohoPurchaseOrder.id || zohoPurchaseOrder.purchaseorder_id,
subject: zohoPurchaseOrder.Subject || zohoPurchaseOrder.subject,
orderNumber: zohoPurchaseOrder.PO_Number || zohoPurchaseOrder.purchaseorder_number,
vendor: zohoPurchaseOrder.Vendor_Name || zohoPurchaseOrder.vendor_name,
account: zohoPurchaseOrder.Account_Name || zohoPurchaseOrder.customer_name,
grandTotal: zohoPurchaseOrder.Grand_Total || zohoPurchaseOrder.total,
status: zohoPurchaseOrder.Status || zohoPurchaseOrder.status,
orderDate: zohoPurchaseOrder.PO_Date || zohoPurchaseOrder.date,
dueDate: zohoPurchaseOrder.Expected_Delivery_Date || zohoPurchaseOrder.due_date,
createdTime: zohoPurchaseOrder.Created_Time || zohoPurchaseOrder.created_time,
modifiedTime: zohoPurchaseOrder.Modified_Time || zohoPurchaseOrder.last_modified_time,
owner: zohoPurchaseOrder.Owner?.name || zohoPurchaseOrder.owner?.name,
customFields: this.mapCustomFields(zohoPurchaseOrder)
};
}
// Map Zoho CRM Invoice to standardized format
// Map Zoho Invoice to standardized format (handles both CRM and Books)
static mapInvoice(zohoInvoice) {
return {
id: zohoInvoice.id,
subject: zohoInvoice.Subject,
invoiceNumber: zohoInvoice.Invoice_Number,
account: zohoInvoice.Account_Name,
contact: zohoInvoice.Contact_Name,
id: zohoInvoice.id || zohoInvoice.invoice_id,
subject: zohoInvoice.Subject || zohoInvoice.subject,
invoiceNumber: zohoInvoice.Invoice_Number || zohoInvoice.invoice_number,
account: zohoInvoice.Account_Name || zohoInvoice.customer_name,
contact: zohoInvoice.Contact_Name || zohoInvoice.contact_name,
deal: zohoInvoice.Deal_Name,
grandTotal: zohoInvoice.Grand_Total,
status: zohoInvoice.Status,
invoiceDate: zohoInvoice.Invoice_Date,
dueDate: zohoInvoice.Due_Date,
createdTime: zohoInvoice.Created_Time,
modifiedTime: zohoInvoice.Modified_Time,
owner: zohoInvoice.Owner?.name,
grandTotal: zohoInvoice.Grand_Total || zohoInvoice.total,
status: zohoInvoice.Status || zohoInvoice.status,
invoiceDate: zohoInvoice.Invoice_Date || zohoInvoice.date,
dueDate: zohoInvoice.Due_Date || zohoInvoice.due_date,
createdTime: zohoInvoice.Created_Time || zohoInvoice.created_time,
modifiedTime: zohoInvoice.Modified_Time || zohoInvoice.last_modified_time,
owner: zohoInvoice.Owner?.name || zohoInvoice.owner?.name,
customFields: this.mapCustomFields(zohoInvoice)
};
}
@ -131,6 +131,310 @@ class ZohoMapper {
};
}
// Map Zoho People Department to standardized format
static mapDepartment(zohoDepartment) {
return {
id: zohoDepartment.id,
name: zohoDepartment.name,
description: zohoDepartment.description,
head: zohoDepartment.head,
employeeCount: zohoDepartment.employeeCount,
customFields: this.mapCustomFields(zohoDepartment)
};
}
// Map Zoho People Timesheet to standardized format
static mapTimesheet(zohoTimesheet) {
return {
id: zohoTimesheet.id,
employeeId: zohoTimesheet.employeeId,
projectId: zohoTimesheet.projectId,
taskId: zohoTimesheet.taskId,
date: zohoTimesheet.date,
hours: zohoTimesheet.hours,
description: zohoTimesheet.description,
status: zohoTimesheet.status,
customFields: this.mapCustomFields(zohoTimesheet)
};
}
// Map Zoho People Leave Request to standardized format
static mapLeaveRequest(zohoLeaveRequest) {
return {
id: zohoLeaveRequest.id,
employeeId: zohoLeaveRequest.employeeId,
leaveType: zohoLeaveRequest.leaveType,
startDate: zohoLeaveRequest.startDate,
endDate: zohoLeaveRequest.endDate,
days: zohoLeaveRequest.days,
reason: zohoLeaveRequest.reason,
status: zohoLeaveRequest.status,
customFields: this.mapCustomFields(zohoLeaveRequest)
};
}
// Map Zoho People Attendance to standardized format
static mapAttendance(zohoAttendance) {
return {
id: zohoAttendance.id,
employeeId: zohoAttendance.employeeId,
date: zohoAttendance.date,
checkIn: zohoAttendance.checkIn,
checkOut: zohoAttendance.checkOut,
hoursWorked: zohoAttendance.hoursWorked,
status: zohoAttendance.status,
customFields: this.mapCustomFields(zohoAttendance)
};
}
// Map Zoho People Employee Forms to standardized format
static mapEmployeeForm(zohoEmployeeForm) {
return {
id: zohoEmployeeForm.id,
employeeId: zohoEmployeeForm.employeeId,
firstName: zohoEmployeeForm.firstName,
lastName: zohoEmployeeForm.lastName,
email: zohoEmployeeForm.email,
phone: zohoEmployeeForm.phone,
department: zohoEmployeeForm.department,
designation: zohoEmployeeForm.designation,
joiningDate: zohoEmployeeForm.joiningDate,
status: zohoEmployeeForm.status,
customFields: this.mapCustomFields(zohoEmployeeForm)
};
}
// Map Zoho People Attendance Entries to standardized format
static mapAttendanceEntry(zohoAttendanceEntry) {
return {
id: zohoAttendanceEntry.id,
employeeId: zohoAttendanceEntry.empId,
emailId: zohoAttendanceEntry.emailId,
date: zohoAttendanceEntry.date,
checkIn: zohoAttendanceEntry.checkIn,
checkOut: zohoAttendanceEntry.checkOut,
hoursWorked: zohoAttendanceEntry.hoursWorked,
status: zohoAttendanceEntry.status,
customFields: this.mapCustomFields(zohoAttendanceEntry)
};
}
// Map Zoho People Shift Configuration to standardized format
static mapShiftConfiguration(zohoShiftConfig) {
return {
id: zohoShiftConfig.id,
employeeId: zohoShiftConfig.empId,
shiftName: zohoShiftConfig.shiftName,
startTime: zohoShiftConfig.startTime,
endTime: zohoShiftConfig.endTime,
breakDuration: zohoShiftConfig.breakDuration,
workingDays: zohoShiftConfig.workingDays,
customFields: this.mapCustomFields(zohoShiftConfig)
};
}
// 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,
customFields: this.mapCustomFields(zohoLeaveData)
};
}
// Map Zoho People Goals Data to standardized format
static mapGoalsData(zohoGoalsData) {
return {
id: zohoGoalsData.id,
employeeId: zohoGoalsData.employeeId,
goalTitle: zohoGoalsData.goalTitle,
description: zohoGoalsData.description,
targetValue: zohoGoalsData.targetValue,
currentValue: zohoGoalsData.currentValue,
startDate: zohoGoalsData.startDate,
endDate: zohoGoalsData.endDate,
status: zohoGoalsData.status,
customFields: this.mapCustomFields(zohoGoalsData)
};
}
// Map Zoho People Performance Data to standardized format
static mapPerformanceData(zohoPerformanceData) {
return {
id: zohoPerformanceData.id,
employeeId: zohoPerformanceData.employeeId,
reviewPeriod: zohoPerformanceData.reviewPeriod,
rating: zohoPerformanceData.rating,
comments: zohoPerformanceData.comments,
reviewer: zohoPerformanceData.reviewer,
reviewDate: zohoPerformanceData.reviewDate,
status: zohoPerformanceData.status,
customFields: this.mapCustomFields(zohoPerformanceData)
};
}
// Map Zoho Books Organization to standardized format
static mapOrganization(zohoOrganization) {
return {
id: zohoOrganization.organization_id,
name: zohoOrganization.organization_name,
currency: zohoOrganization.currency_code,
timezone: zohoOrganization.time_zone,
fiscalYearStart: zohoOrganization.fiscal_year_start_month,
accountCreatedDate: zohoOrganization.account_created_date,
customFields: this.mapCustomFields(zohoOrganization)
};
}
// Map Zoho Books Customer to standardized format
static mapCustomer(zohoCustomer) {
return {
id: zohoCustomer.contact_id,
name: zohoCustomer.contact_name,
email: zohoCustomer.contact_persons?.[0]?.email,
phone: zohoCustomer.contact_persons?.[0]?.phone,
company: zohoCustomer.company_name,
billingAddress: zohoCustomer.billing_address,
shippingAddress: zohoCustomer.shipping_address,
currency: zohoCustomer.currency_code,
paymentTerms: zohoCustomer.payment_terms,
customFields: this.mapCustomFields(zohoCustomer)
};
}
// Map Zoho Books Vendor to standardized format
static mapVendor(zohoVendor) {
return {
id: zohoVendor.contact_id,
name: zohoVendor.contact_name,
email: zohoVendor.contact_persons?.[0]?.email,
phone: zohoVendor.contact_persons?.[0]?.phone,
company: zohoVendor.company_name,
billingAddress: zohoVendor.billing_address,
shippingAddress: zohoVendor.shipping_address,
currency: zohoVendor.currency_code,
paymentTerms: zohoVendor.payment_terms,
customFields: this.mapCustomFields(zohoVendor)
};
}
// Map Zoho Books Item to standardized format
static mapItem(zohoItem) {
return {
id: zohoItem.item_id,
name: zohoItem.name,
description: zohoItem.description,
sku: zohoItem.sku,
unit: zohoItem.unit,
rate: zohoItem.rate,
purchaseRate: zohoItem.purchase_rate,
salesRate: zohoItem.sales_rate,
itemType: zohoItem.item_type,
status: zohoItem.status,
customFields: this.mapCustomFields(zohoItem)
};
}
// Map Zoho Books Invoice to standardized format
static mapBooksInvoice(zohoInvoice) {
return {
id: zohoInvoice.invoice_id,
invoiceNumber: zohoInvoice.invoice_number,
customerId: zohoInvoice.customer_id,
customerName: zohoInvoice.customer_name,
date: zohoInvoice.date,
dueDate: zohoInvoice.due_date,
total: zohoInvoice.total,
balance: zohoInvoice.balance,
status: zohoInvoice.status,
currency: zohoInvoice.currency_code,
customFields: this.mapCustomFields(zohoInvoice)
};
}
// Map Zoho Books Estimate to standardized format
static mapEstimate(zohoEstimate) {
return {
id: zohoEstimate.estimate_id,
estimateNumber: zohoEstimate.estimate_number,
customerId: zohoEstimate.customer_id,
customerName: zohoEstimate.customer_name,
date: zohoEstimate.date,
expiryDate: zohoEstimate.expiry_date,
total: zohoEstimate.total,
status: zohoEstimate.status,
currency: zohoEstimate.currency_code,
customFields: this.mapCustomFields(zohoEstimate)
};
}
// Map Zoho Books Bill to standardized format
static mapBill(zohoBill) {
return {
id: zohoBill.bill_id,
billNumber: zohoBill.bill_number,
vendorId: zohoBill.vendor_id,
vendorName: zohoBill.vendor_name,
date: zohoBill.date,
dueDate: zohoBill.due_date,
total: zohoBill.total,
balance: zohoBill.balance,
status: zohoBill.status,
currency: zohoBill.currency_code,
customFields: this.mapCustomFields(zohoBill)
};
}
// Map Zoho Books Expense to standardized format
static mapExpense(zohoExpense) {
return {
id: zohoExpense.expense_id,
expenseNumber: zohoExpense.expense_number,
accountId: zohoExpense.account_id,
accountName: zohoExpense.account_name,
date: zohoExpense.date,
amount: zohoExpense.amount,
description: zohoExpense.description,
status: zohoExpense.status,
currency: zohoExpense.currency_code,
customFields: this.mapCustomFields(zohoExpense)
};
}
// Map Zoho Books Bank Account to standardized format
static mapBankAccount(zohoBankAccount) {
return {
id: zohoBankAccount.account_id,
accountName: zohoBankAccount.account_name,
accountNumber: zohoBankAccount.account_number,
bankName: zohoBankAccount.bank_name,
accountType: zohoBankAccount.account_type,
balance: zohoBankAccount.balance,
currency: zohoBankAccount.currency_code,
customFields: this.mapCustomFields(zohoBankAccount)
};
}
// Map Zoho Books Bank Transaction to standardized format
static mapBankTransaction(zohoBankTransaction) {
return {
id: zohoBankTransaction.transaction_id,
accountId: zohoBankTransaction.account_id,
date: zohoBankTransaction.date,
amount: zohoBankTransaction.amount,
description: zohoBankTransaction.description,
type: zohoBankTransaction.type,
status: zohoBankTransaction.status,
customFields: this.mapCustomFields(zohoBankTransaction)
};
}
// Map Zoho Projects Project to standardized format
static mapProject(zohoProject) {
return {
@ -176,13 +480,37 @@ class ZohoMapper {
// Map multiple records using appropriate mapper
static mapRecords(records, recordType) {
const mapperMap = {
// CRM
'leads': this.mapLead,
'contacts': this.mapContact,
'deals': this.mapDeal,
'sales_orders': this.mapSalesOrder,
'purchase_orders': this.mapPurchaseOrder,
'invoices': this.mapInvoice,
// People
'employees': this.mapEmployee,
'departments': this.mapDepartment,
'timesheets': this.mapTimesheet,
'leave_requests': this.mapLeaveRequest,
'attendance': this.mapAttendance,
'employee_forms': this.mapEmployeeForm,
'attendance_entries': this.mapAttendanceEntry,
'shift_configuration': this.mapShiftConfiguration,
'leave_data': this.mapLeaveData,
'goals_data': this.mapGoalsData,
'performance_data': this.mapPerformanceData,
// Books
'organizations': this.mapOrganization,
'customers': this.mapCustomer,
'vendors': this.mapVendor,
'items': this.mapItem,
'invoices': this.mapBooksInvoice,
'estimates': this.mapEstimate,
'bills': this.mapBill,
'expenses': this.mapExpense,
'bank_accounts': this.mapBankAccount,
'bank_transactions': this.mapBankTransaction,
// Projects
'projects': this.mapProject,
'tasks': this.mapTask
};
@ -288,10 +616,153 @@ class ZohoMapper {
};
break;
case 'sales_orders':
case 'purchase_orders':
case 'invoices':
// CRM response structure for sales orders, purchase orders, and invoices
// Handle both CRM and Books invoices - check response structure
if (zohoResponse.data && zohoResponse.info) {
// Books response structure
records = zohoResponse.data || [];
pageInfo = {
count: zohoResponse.info?.count || records.length,
moreRecords: zohoResponse.info?.more_records || false,
page: zohoResponse.info?.page || 1
};
} else {
// CRM response structure
records = zohoResponse.invoices || [];
pageInfo = {
count: zohoResponse.page_context?.count || records.length,
moreRecords: zohoResponse.page_context?.more_records || false,
page: zohoResponse.page_context?.page || 1
};
}
break;
case 'contacts':
// Books response structure for contacts
records = zohoResponse.contacts || [];
pageInfo = {
count: zohoResponse.page_context?.count || records.length,
moreRecords: zohoResponse.page_context?.more_records || false,
page: zohoResponse.page_context?.page || 1
};
break;
case 'vendors':
// Books response structure for vendors (filtered contacts)
records = zohoResponse.contacts || [];
pageInfo = {
count: zohoResponse.page_context?.count || records.length,
moreRecords: zohoResponse.page_context?.more_records || false,
page: zohoResponse.page_context?.page || 1
};
break;
case 'sales_orders':
// Handle both CRM and Books sales orders - check response structure
if (zohoResponse.data && zohoResponse.info) {
// Books response structure
records = zohoResponse.data || [];
pageInfo = {
count: zohoResponse.info?.count || records.length,
moreRecords: zohoResponse.info?.more_records || false,
page: zohoResponse.info?.page || 1
};
} else {
// CRM response structure
records = zohoResponse.salesorders || [];
pageInfo = {
count: zohoResponse.page_context?.count || records.length,
moreRecords: zohoResponse.page_context?.more_records || false,
page: zohoResponse.page_context?.page || 1
};
}
break;
case 'purchase_orders':
// Handle both CRM and Books purchase orders - check response structure
if (zohoResponse.data && zohoResponse.info) {
// Books response structure
records = zohoResponse.data || [];
pageInfo = {
count: zohoResponse.info?.count || records.length,
moreRecords: zohoResponse.info?.more_records || false,
page: zohoResponse.info?.page || 1
};
} else {
// CRM response structure
records = zohoResponse.purchaseorders || [];
pageInfo = {
count: zohoResponse.page_context?.count || records.length,
moreRecords: zohoResponse.page_context?.more_records || false,
page: zohoResponse.page_context?.page || 1
};
}
break;
case 'employee_forms':
// Handle both CRM and Books sales orders - check response structure
// Books response structure
records = zohoResponse.response.result || [];
pageInfo = {
count: zohoResponse.response.result?.count || records.length,
moreRecords: zohoResponse.response.result?.more_records || false,
page: zohoResponse.response.result?.page || 1
};
break;
case 'departments':
case 'timesheets':
case 'leave_requests':
case 'attendance':
// People response structure
records = zohoResponse.data || [];
pageInfo = {
count: zohoResponse.info?.count || records.length,
moreRecords: zohoResponse.info?.more_records || false,
page: zohoResponse.info?.page || 1
};
break;
case 'employee_forms':
case 'attendance_entries':
case 'shift_configuration':
case 'leave_data':
case 'goals_data':
case 'performance_data':
// People Forms API response structure
records = zohoResponse.data || [];
pageInfo = {
count: zohoResponse.info?.count || records.length,
moreRecords: zohoResponse.info?.more_records || false,
page: zohoResponse.info?.page || 1
};
break;
case 'employee_detail':
// Single employee detail response
records = zohoResponse.data ? [zohoResponse.data] : [];
pageInfo = {
count: records.length,
moreRecords: false,
page: 1
};
break;
case 'organizations':
case 'customers':
case 'items':
case 'estimates':
case 'bills':
case 'expenses':
case 'bank_accounts':
case 'bank_transactions':
case 'reports':
// Books response structure
records = zohoResponse.data || [];
pageInfo = {
count: zohoResponse.info?.count || records.length,

View File

@ -40,13 +40,33 @@ class IntegrationService {
getMethodName(service, resource) {
const resourceMap = {
// CRM
'leads': 'Leads',
'contacts': 'Contacts',
'deals': 'Deals',
'sales_orders': 'SalesOrders',
'purchase_orders': 'PurchaseOrders',
'invoices': 'Invoices',
// People
'employees': 'Employees',
'departments': 'Departments',
'timesheets': 'Timesheets',
'leave_requests': 'LeaveRequests',
'attendance': 'Attendance',
// Books
'organizations': 'Organizations',
'customers': 'Customers',
'vendors': 'Vendors',
'items': 'Items',
'estimates': 'Estimates',
'bills': 'Bills',
'expenses': 'Expenses',
'bank_accounts': 'BankAccounts',
'bank_transactions': 'BankTransactions',
'reports': 'Reports',
// Projects
'projects': 'Projects',
'tasks': 'Tasks',
'timesheets': 'Timesheets'
'tasks': 'Tasks'
};
const resourceName = resourceMap[resource] || resource;
@ -167,6 +187,262 @@ class IntegrationService {
}
return await client.getInvoices(params);
}
// Zoho People specific methods
async getDepartments(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Departments are only available for Zoho provider');
}
return await client.getDepartments(params);
}
async getLeaveRequests(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Leave Requests are only available for Zoho provider');
}
return await client.getLeaveRequests(params);
}
async getAttendance(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Attendance is only available for Zoho provider');
}
return await client.getAttendance(params);
}
// Zoho Books specific methods
async getOrganizations(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Organizations are only available for Zoho provider');
}
return await client.getOrganizations(params);
}
async getCustomers(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Customers are only available for Zoho provider');
}
return await client.getCustomers(params);
}
async getVendors(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Vendors are only available for Zoho provider');
}
return await client.getVendors(params);
}
async getItems(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Items are only available for Zoho provider');
}
return await client.getItems(params);
}
async getEstimates(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Estimates are only available for Zoho provider');
}
return await client.getEstimates(params);
}
async getBills(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Bills are only available for Zoho provider');
}
return await client.getBills(params);
}
async getExpenses(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Expenses are only available for Zoho provider');
}
return await client.getExpenses(params);
}
async getBankAccounts(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Bank Accounts are only available for Zoho provider');
}
return await client.getBankAccounts(params);
}
async getBankTransactions(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Bank Transactions are only available for Zoho provider');
}
return await client.getBankTransactions(params);
}
async getReports(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Reports are only available for Zoho provider');
}
return await client.getReports(params);
}
async getBooksSalesOrders(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Books Sales Orders are only available for Zoho provider');
}
return await client.getBooksSalesOrders(params);
}
async getBooksPurchaseOrders(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Books Purchase Orders are only available for Zoho provider');
}
return await client.getBooksPurchaseOrders(params);
}
async getContacts(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Contacts are only available for Zoho provider');
}
return await client.getContacts(params);
}
// Zoho People Forms API methods
async getEmployeeForms(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Employee Forms are only available for Zoho provider');
}
return await client.getEmployeeForms(params);
}
async getEmployeeById(provider, recordId, formLinkName = 'employee') {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Employee Details are only available for Zoho provider');
}
return await client.getEmployeeById(recordId, formLinkName);
}
async getAttendanceEntries(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Attendance Entries are only available for Zoho provider');
}
return await client.getAttendanceEntries(params);
}
async getShiftConfiguration(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Shift Configuration is only available for Zoho provider');
}
return await client.getShiftConfiguration(params);
}
async getLeaveData(provider, formLinkName = 'leave', params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Leave Data is only available for Zoho provider');
}
return await client.getLeaveData(formLinkName, params);
}
async getGoalsData(provider, formLinkName = 'goals', params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Goals Data is only available for Zoho provider');
}
return await client.getGoalsData(formLinkName, params);
}
async getPerformanceData(provider, formLinkName = 'performance', params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Performance Data is only available for Zoho provider');
}
return await client.getPerformanceData(formLinkName, params);
}
}
module.exports = IntegrationService;