Centralized_Reporting_Backend/src/services/bulkReadService.js
2025-09-25 19:02:08 +05:30

342 lines
10 KiB
JavaScript

const axios = require('axios');
const ZohoBulkReadRepository = require('../data/repositories/zohoBulkReadRepository');
const userAuthTokenRepo = require('../data/repositories/userAuthTokenRepository');
const { decrypt } = require('../utils/crypto');
const logger = require('../utils/logger');
class BulkReadService {
constructor() {
this.baseUrl = 'https://www.zohoapis.com';
}
/**
* Initiate a bulk read job for a specific module
* @param {string} userId - User UUID
* @param {string} module - Zoho module name
* @param {Array} fields - Fields to fetch
* @param {Object} options - Additional options
* @param {string} accessToken - Frontend access token for callback URL
* @returns {Promise<Object>} Job details
*/
async initiateBulkRead(userId, module, fields, options = {}, accessToken = null) {
try {
console.log(`🚀 Initiating bulk read for user ${userId}, module ${module}`);
// Get access token
const tokenRecord = await userAuthTokenRepo.findByUserAndService(userId, 'zoho');
if (!tokenRecord) {
throw new Error('No Zoho access token found for user');
}
const zohoAccessToken = decrypt(tokenRecord.accessToken);
// Prepare bulk read request
const baseUrl = process.env.API_BASE_URL || process.env.MY_BASE_URL || 'http://localhost:3000';
let callbackUrl = `${baseUrl}/api/v1/integrations/webhooks/zoho/bulkread`;
// Add access token to callback URL if provided
if (accessToken) {
callbackUrl += `?access_token=${accessToken}`;
}
const bulkReadData = {
callback: {
url: callbackUrl,
method: 'post'
},
query: {
module: module,
fields: fields,
page: options.page || 1,
limit: options.limit || 200000
}
};
console.log('📋 Bulk read request:', JSON.stringify(bulkReadData, null, 2));
// Make API call to Zoho
const response = await axios.post(
`${this.baseUrl}/crm/bulk/v2/read`,
bulkReadData,
{
headers: {
'Authorization': `Zoho-oauthtoken ${zohoAccessToken}`,
'Content-Type': 'application/json'
}
}
);
const jobData = response.data;
console.log('✅ Bulk read job response from Zoho:', JSON.stringify(jobData, null, 2));
// Extract job ID from nested response structure
const jobDetails = jobData.data && jobData.data[0] && jobData.data[0].details;
const jobId = jobDetails?.id;
if (!jobId) {
console.log('⚠️ No job ID found in response structure');
console.log('Response structure:', JSON.stringify(jobData, null, 2));
throw new Error('Invalid response structure from Zoho API - no job ID found');
}
console.log('📋 Job ID received from Zoho:', jobId);
console.log('📋 Job details:', jobDetails);
// Store job in database with real job ID
const jobRecord = await ZohoBulkReadRepository.createBulkReadJob({
id: jobId,
user_uuid: userId,
module: module,
operation: jobDetails.operation || 'read',
state: jobDetails.state || 'ADDED',
file_type: 'csv',
records_count: 0,
status: 'pending'
});
logger.info('Bulk read job created and stored', {
userId,
module,
jobId: jobId,
zohoState: jobDetails.state || 'ADDED'
});
return {
jobId: jobId,
status: 'created',
message: `Bulk read job created for ${module}. Processing will be handled asynchronously via webhook.`,
zohoState: jobDetails.state || 'ADDED',
operation: jobDetails.operation || 'read',
createdBy: jobDetails.created_by,
createdTime: jobDetails.created_time,
estimatedTime: this.getEstimatedTime(module, options.limit)
};
} catch (error) {
console.error('❌ Error initiating bulk read:', error.message);
logger.error('Bulk read initiation failed', {
userId,
module,
error: error.message
});
throw error;
}
}
/**
* Get bulk read job status
* @param {string} userId - User UUID
* @param {string} jobId - Job ID
* @returns {Promise<Object>} Job status
*/
async getBulkReadJobStatus(userId, jobId) {
try {
const job = await ZohoBulkReadRepository.getBulkReadJob(jobId);
if (!job) {
throw new Error('Job not found');
}
if (job.user_uuid !== userId) {
throw new Error('Unauthorized access to job');
}
return {
jobId: job.id,
module: job.module,
status: job.status,
state: job.state,
recordsCount: job.records_count,
processedCount: job.processed_count,
createdAt: job.created_at,
updatedAt: job.updated_at,
errorMessage: job.error_message
};
} catch (error) {
console.error('❌ Error getting job status:', error.message);
throw error;
}
}
/**
* Get user's bulk read jobs
* @param {string} userId - User UUID
* @param {Object} options - Query options
* @returns {Promise<Array>} Job list
*/
async getUserBulkReadJobs(userId, options = {}) {
try {
const jobs = await ZohoBulkReadRepository.getUserBulkReadJobs(userId, options);
return jobs.map(job => ({
jobId: job.id,
module: job.module,
status: job.status,
state: job.state,
recordsCount: job.records_count,
processedCount: job.processed_count,
createdAt: job.created_at,
updatedAt: job.updated_at
}));
} catch (error) {
console.error('❌ Error getting user jobs:', error.message);
throw error;
}
}
/**
* Get bulk read data for a module
* @param {string} userId - User UUID
* @param {string} module - Module name
* @param {Object} options - Query options
* @returns {Promise<Object>} Data and pagination info
*/
async getBulkReadData(userId, module, options = {}) {
try {
const { page = 1, limit = 100, orderBy = 'created_time', orderDirection = 'DESC' } = options;
const data = await ZohoBulkReadRepository.getUserData(userId, module, {
limit: parseInt(limit),
offset: (parseInt(page) - 1) * parseInt(limit),
orderBy,
orderDirection
});
const totalCount = await ZohoBulkReadRepository.getUserDataCount(userId, module);
return {
data: data,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: totalCount,
pages: Math.ceil(totalCount / parseInt(limit))
}
};
} catch (error) {
console.error('❌ Error getting bulk read data:', error.message);
throw error;
}
}
/**
* Get estimated processing time for a module
* @param {string} module - Module name
* @param {number} limit - Record limit
* @returns {string} Estimated time
*/
getEstimatedTime(module, limit = 200000) {
const baseTime = {
'contacts': 2,
'leads': 2,
'accounts': 1,
'tasks': 3,
'deals': 2,
'vendors': 1,
'invoices': 2,
'sales_orders': 2,
'purchase_orders': 2
};
const minutes = baseTime[module.toLowerCase()] || 2;
const adjustedMinutes = Math.ceil(minutes * (limit / 100000));
return `${adjustedMinutes} minutes`;
}
/**
* Get available modules for bulk read
* @returns {Array} Available modules
*/
getAvailableModules() {
return [
{
name: 'contacts',
displayName: 'Contacts',
description: 'Customer contact information',
fields: [
'id', 'First_Name', 'Last_Name', 'Email', 'Phone', 'Mobile',
'Lead_Source', 'Account_Name.Account_Name', 'Owner', 'Created_Time', 'Modified_Time'
]
},
{
name: 'leads',
displayName: 'Leads',
description: 'Sales lead information',
fields: [
'id', 'First_Name', 'Last_Name', 'Company', 'Lead_Source',
'Lead_Status', 'Owner', 'Email', 'Phone', 'Created_Time'
]
},
{
name: 'accounts',
displayName: 'Accounts',
description: 'Account information',
fields: [
'id', 'Account_Name', 'Phone', 'Website', 'Industry',
'Ownership', 'Annual_Revenue', 'Owner', 'Created_Time'
]
},
{
name: 'tasks',
displayName: 'Tasks',
description: 'Task information',
fields: [
'id', 'Subject', 'Owner', 'Status', 'Priority',
'Due_Date', 'What_Id', 'Created_Time'
]
},
{
name: 'deals',
displayName: 'Deals',
description: 'Sales deal information',
fields: [
'id', 'Deal_Name', 'Stage', 'Amount', 'Closing_Date',
'Account_Name', 'Contact_Name', 'Pipeline', 'Probability',
'Lead_Source', 'Owner', 'Created_Time', 'Modified_Time'
]
},
{
name: 'vendors',
displayName: 'Vendors',
description: 'Vendor information',
fields: [
'id', 'Vendor_Name', 'Email', 'Phone', 'Website', 'Owner', 'Created_Time'
]
},
{
name: 'invoices',
displayName: 'Invoices',
description: 'Invoice information',
fields: [
'id', 'Invoice_Number', 'Invoice_Date', 'Due_Date', 'Status',
'Grand_Total', 'Account_Name.Account_Name', 'Owner', 'Created_Time'
]
},
{
name: 'sales_orders',
displayName: 'Sales Orders',
description: 'Sales order information',
fields: [
'id', 'Subject', 'Status', 'Due_Date', 'Grand_Total',
'Account_Name.Account_Name', 'Owner', 'Created_Time'
]
},
{
name: 'purchase_orders',
displayName: 'Purchase Orders',
description: 'Purchase order information',
fields: [
'id', 'Subject', 'Vendor_Name.Vendor_Name', 'Status',
'Due_Date', 'Grand_Total', 'Owner', 'Created_Time'
]
}
];
}
}
module.exports = BulkReadService;