From 8ac686c13ee2c8a54d59ea22ee09a07b1118404c Mon Sep 17 00:00:00 2001 From: yashwin-foxy Date: Mon, 15 Sep 2025 20:00:28 +0530 Subject: [PATCH] invoices purchase order sales order added for crm --- src/api/controllers/integrationController.js | 77 ++++++++++++++++++- src/api/routes/integrationRoutes.js | 32 +++++++- src/integrations/zoho/client.js | 17 +++- src/integrations/zoho/mapper.js | 74 ++++++++++++++++++ .../integration/integrationService.js | 33 ++++++++ 5 files changed, 230 insertions(+), 3 deletions(-) diff --git a/src/api/controllers/integrationController.js b/src/api/controllers/integrationController.js index a0ce64b..f9c741b 100644 --- a/src/api/controllers/integrationController.js +++ b/src/api/controllers/integrationController.js @@ -203,4 +203,79 @@ async function getAllProjectPhases(req, res) { } } -module.exports = { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks, getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases }; +async function getSalesOrders(req, res) { + try { + const { provider, page, limit, filters } = req.query; + + if (provider !== 'zoho') { + return res.status(400).json(failure('Sales Orders are only available for Zoho provider', 'INVALID_PROVIDER')); + } + + const integrationService = new IntegrationService(req.user.id); + 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.getSalesOrders(provider, params); + res.json(success('Zoho Sales Orders retrieved successfully', salesOrders)); + } catch (error) { + res.status(400).json(failure(error.message, 'INTEGRATION_ERROR')); + } +} + +async function getPurchaseOrders(req, res) { + try { + const { provider, page, limit, filters } = req.query; + + if (provider !== 'zoho') { + return res.status(400).json(failure('Purchase Orders are only available for Zoho provider', 'INVALID_PROVIDER')); + } + + const integrationService = new IntegrationService(req.user.id); + 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.getPurchaseOrders(provider, params); + res.json(success('Zoho Purchase Orders retrieved successfully', purchaseOrders)); + } catch (error) { + res.status(400).json(failure(error.message, 'INTEGRATION_ERROR')); + } +} + +async function getInvoices(req, res) { + try { + const { provider, page, limit, filters } = req.query; + + if (provider !== 'zoho') { + return res.status(400).json(failure('Invoices are only available for Zoho provider', 'INVALID_PROVIDER')); + } + + const integrationService = new IntegrationService(req.user.id); + 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 invoices = await integrationService.getInvoices(provider, params); + res.json(success('Zoho Invoices retrieved successfully', invoices)); + } 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 }; diff --git a/src/api/routes/integrationRoutes.js b/src/api/routes/integrationRoutes.js index 426df6e..28a268f 100644 --- a/src/api/routes/integrationRoutes.js +++ b/src/api/routes/integrationRoutes.js @@ -1,6 +1,6 @@ const express = require('express'); const Joi = require('joi'); -const { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks, getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases } = require('../controllers/integrationController'); +const { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks, getAllProjectTaskLists, getAllProjectIssues, getAllProjectPhases, getSalesOrders, getPurchaseOrders, getInvoices } = require('../controllers/integrationController'); const auth = require('../middlewares/auth'); const ZohoHandler = require('../../integrations/zoho/handler'); @@ -116,6 +116,36 @@ const allProjectPhasesSchema = Joi.object({ router.get('/all-project-phases', auth, validate(allProjectPhasesSchema), getAllProjectPhases); +// Get Zoho Sales Orders +const salesOrdersSchema = 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() +}); + +router.get('/sales-orders', auth, validate(salesOrdersSchema), getSalesOrders); + +// Get Zoho Purchase Orders +const purchaseOrdersSchema = 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() +}); + +router.get('/purchase-orders', auth, validate(purchaseOrdersSchema), getPurchaseOrders); + +// Get Zoho Invoices +const invoicesSchema = 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() +}); + +router.get('/invoices', auth, validate(invoicesSchema), getInvoices); + // Webhook endpoints (no auth required - uses signature verification) const zohoHandler = new ZohoHandler(); router.post('/webhooks/zoho/crm', zohoHandler.handleCrmWebhook.bind(zohoHandler)); diff --git a/src/integrations/zoho/client.js b/src/integrations/zoho/client.js index 5751cd1..10ef870 100644 --- a/src/integrations/zoho/client.js +++ b/src/integrations/zoho/client.js @@ -112,6 +112,21 @@ class ZohoClient { return ZohoMapper.mapApiResponse(response, 'tasks'); } + async getSalesOrders(params = {}) { + const response = await this.makeRequest('/crm/v2/Sales_Orders', { params }); + return ZohoMapper.mapApiResponse(response, 'sales_orders'); + } + + async getPurchaseOrders(params = {}) { + const response = await this.makeRequest('/crm/v2/Purchase_Orders', { params }); + return ZohoMapper.mapApiResponse(response, 'purchase_orders'); + } + + async getInvoices(params = {}) { + const response = await this.makeRequest('/crm/v2/Invoices', { params }); + return ZohoMapper.mapApiResponse(response, 'invoices'); + } + // Zoho People methods async getEmployees(params = {}) { const response = await this.makeRequest('/people/api/v1/employees', { params }, 'people'); @@ -201,7 +216,7 @@ class ZohoClient { getAvailableResources(service) { const resources = { - 'crm': ['leads', 'contacts', 'deals', 'tasks'], + 'crm': ['leads', 'contacts', 'deals', 'tasks', 'sales_orders', 'purchase_orders', 'invoices'], 'people': ['employees', 'departments'], 'projects': ['projects', 'tasks', 'timesheets'] }; diff --git a/src/integrations/zoho/mapper.js b/src/integrations/zoho/mapper.js index 6371d4d..f842852 100644 --- a/src/integrations/zoho/mapper.js +++ b/src/integrations/zoho/mapper.js @@ -54,6 +54,65 @@ class ZohoMapper { }; } + // Map Zoho CRM Sales Order to standardized format + static mapSalesOrder(zohoSalesOrder) { + return { + id: zohoSalesOrder.id, + subject: zohoSalesOrder.Subject, + orderNumber: zohoSalesOrder.SO_Number, + account: zohoSalesOrder.Account_Name, + contact: 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, + customFields: this.mapCustomFields(zohoSalesOrder) + }; + } + + // Map Zoho CRM Purchase Order to standardized format + 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, + customFields: this.mapCustomFields(zohoPurchaseOrder) + }; + } + + // Map Zoho CRM Invoice to standardized format + static mapInvoice(zohoInvoice) { + return { + id: zohoInvoice.id, + subject: zohoInvoice.Subject, + invoiceNumber: zohoInvoice.Invoice_Number, + account: zohoInvoice.Account_Name, + contact: 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, + customFields: this.mapCustomFields(zohoInvoice) + }; + } + // Map Zoho People Employee to standardized format static mapEmployee(zohoEmployee) { return { @@ -120,6 +179,9 @@ class ZohoMapper { 'leads': this.mapLead, 'contacts': this.mapContact, 'deals': this.mapDeal, + 'sales_orders': this.mapSalesOrder, + 'purchase_orders': this.mapPurchaseOrder, + 'invoices': this.mapInvoice, 'employees': this.mapEmployee, 'projects': this.mapProject, 'tasks': this.mapTask @@ -226,6 +288,18 @@ class ZohoMapper { }; break; + case 'sales_orders': + case 'purchase_orders': + case 'invoices': + // CRM response structure for sales orders, purchase orders, and invoices + records = zohoResponse.data || []; + pageInfo = { + count: zohoResponse.info?.count || records.length, + moreRecords: zohoResponse.info?.more_records || false, + page: zohoResponse.info?.page || 1 + }; + break; + default: // Default CRM response structure (leads, contacts, deals, employees) records = zohoResponse.data || []; diff --git a/src/services/integration/integrationService.js b/src/services/integration/integrationService.js index 9e43aab..40f8503 100644 --- a/src/services/integration/integrationService.js +++ b/src/services/integration/integrationService.js @@ -134,6 +134,39 @@ class IntegrationService { } return await client.getAllProjectPhases(portalId, params); } + + async getSalesOrders(provider, params = {}) { + const client = this.clients[provider]; + if (!client) { + throw new Error(`Provider ${provider} not supported`); + } + if (provider !== 'zoho') { + throw new Error('Sales Orders are only available for Zoho provider'); + } + return await client.getSalesOrders(params); + } + + async getPurchaseOrders(provider, params = {}) { + const client = this.clients[provider]; + if (!client) { + throw new Error(`Provider ${provider} not supported`); + } + if (provider !== 'zoho') { + throw new Error('Purchase Orders are only available for Zoho provider'); + } + return await client.getPurchaseOrders(params); + } + + async getInvoices(provider, params = {}) { + const client = this.clients[provider]; + if (!client) { + throw new Error(`Provider ${provider} not supported`); + } + if (provider !== 'zoho') { + throw new Error('Invoices are only available for Zoho provider'); + } + return await client.getInvoices(params); + } } module.exports = IntegrationService;