import logger from "../config/logger.js"; import SalesforceService from "./salesforce.service.js"; import { VAModel, ProjectModel, LoggedModel, WorkTrackModel } from "../models/vaModel.js"; import bcrypt from "bcrypt"; class VaClientService { constructor(vaClientRepository) { this.clientRepository = vaClientRepository; this.salesforceService = new SalesforceService(); } async registerClient(data) { try { // Check if user already exists const existingUser = await this.clientRepository.findByEmail(data.userDetails.email); if (existingUser) { throw new Error("User already exists with this email"); } // Sanitize and prepare data const clientData = await this.sanitizeClientData(data); const response = await this.clientRepository.create(clientData); if(response) { await this.salesforceService.registerClientInSalesforce(data, response._id); } // Send data to Salesforce first return response; } catch (error) { logger.error("Client Service - registerClient error: " + error.message); throw error; } } async getClientByEmail(email) { try { const response = await this.clientRepository.findByEmail(email); return response; } catch (error) { logger.error("Client Service - getClientByEmail error: " + error.message); throw error; } } async getClientById(id) { try { const response = await this.clientRepository.findById(id); if (!response) throw new Error("Client not found"); return response; } catch (error) { logger.error("Client Service - getClientById error: " + error.message); throw error; } } async updateClient(id, data) { try { // Check if client exists const existingClient = await this.clientRepository.findById(id); if (!existingClient) { throw new Error("Client not found"); } const { userDetails } = data; if (!userDetails) { throw new Error("No user details provided for update"); } // Prepare update data const updateData = { userDetails: {} }; // Define allowed fields for update (excluding password) const allowedFields = [ "name", "firstName", "lastName", "address", "city", "state", "zip", "billingName", "billingEmail", "billingAddress", "billingCity", "billingState", "billingZip", "position", "signatoryName", "signatoryEmail", "paymentMethod", "bankName", "accountNumber", "routingNumber", "accountType", "accountHolderName", "cardNumber", "expiryDate", "securityCode", "cardZip" ]; let hasUpdates = false; const updatedFields = []; // Process allowed fields allowedFields.forEach(field => { if (userDetails[field] !== undefined && userDetails[field] !== null && userDetails[field] !== '') { updateData.userDetails[field] = userDetails[field]; hasUpdates = true; updatedFields.push(field); } }); // Handle email update separately (needs uniqueness check) if (userDetails.email && userDetails.email !== existingClient.userDetails.email) { // Check if new email already exists const emailExists = await this.clientRepository.findByEmail(userDetails.email); if (emailExists && emailExists._id.toString() !== id) { throw new Error("Email already exists"); } updateData.userDetails.email = userDetails.email.toLowerCase(); hasUpdates = true; updatedFields.push('email'); } // Check if any fields were actually updated if (!hasUpdates) { throw new Error("No valid fields to update"); } // Update the client const updatedClient = await this.clientRepository.update(id, updateData); // Notify Salesforce about the update try { await this.salesforceService.updateClientInSalesforce(id, data); logger.info(`Salesforce notified about client update: ${id}`); } catch (sfError) { logger.warn(`Salesforce notification failed for client update: ${sfError.message}`); // Don't throw error - client update should proceed even if Salesforce fails } logger.info(`Client updated successfully: ${id}, Fields: ${updatedFields.join(', ')}`); return { ...updatedClient.toObject(), updateSummary: { updatedFields } }; } catch (error) { logger.error("Client Service - updateClient error: " + error.message); throw error; } } async deleteClient(clientId) { try { // Check if client exists const existingClient = await this.clientRepository.findById(clientId); if (!existingClient) { throw new Error("Client not found"); } const clientName = existingClient.userDetails?.name; const vaIds = existingClient.vaIds || []; const projectIds = existingClient.projectIds || []; logger.info(`Deleting client: ${clientName} (${clientId})`); const deletionSummary = {}; // 1. Remove client from all VAs if (vaIds.length > 0) { const vaResult = await VAModel.updateMany( { _id: { $in: vaIds } }, { $pull: { clientIds: clientId } } ); deletionSummary.vasUpdated = vaResult.modifiedCount; } // 2. Delete all projects and associated data if (projectIds.length > 0) { // Remove project references from VAs await VAModel.updateMany( { projectIds: { $in: projectIds } }, { $pull: { projectIds: { $in: projectIds } } } ); // Delete work tracks and logs await WorkTrackModel.deleteMany({ projectId: { $in: projectIds } }); await LoggedModel.deleteMany({ projectId: { $in: projectIds } }); // Delete projects const projectResult = await ProjectModel.deleteMany({ _id: { $in: projectIds } }); deletionSummary.projectsDeleted = projectResult.deletedCount; } // 3. Delete the client const deletedClient = await this.clientRepository.delete(clientId); logger.info(`✅ Successfully deleted client: ${clientName} (${clientId})`); return { ...deletedClient.toObject(), deletionSummary }; } catch (error) { logger.error("Client Service - deleteClient error: " + error.message); throw error; } } async getClientProfileByEmail(email) { try { const response = await this.clientRepository.findProfileByEmail(email); return response; } catch (error) { logger.error("Client Service - getClientProfileByEmail error: " + error.message); throw error; } } async updateClientPassword(id, passwordData) { try { // Check if client exists const existingClient = await this.clientRepository.findById(id); if (!existingClient) { throw new Error("Client not found"); } const { currentPassword, newPassword, confirmPassword } = passwordData; // Validate required fields if (!currentPassword || !newPassword || !confirmPassword) { throw new Error("All password fields are required"); } // Validate password match if (newPassword !== confirmPassword) { throw new Error("New passwords do not match"); } // Verify current password const isMatch = await bcrypt.compare(currentPassword, existingClient.userDetails.password); if (!isMatch) { throw new Error("Current password is incorrect"); } // Hash new password const saltRounds = 10; const hashedPassword = await bcrypt.hash(newPassword, saltRounds); // Prepare update data const updateData = { userDetails: { password: hashedPassword } }; // Update the client password const updatedClient = await this.clientRepository.update(id, updateData); logger.info(`Client password updated successfully: ${id}`); return { success: true, message: "Password updated successfully", clientId: id }; } catch (error) { logger.error("Client Service - updateClientPassword error: " + error.message); throw error; } } // Helper method to sanitize data async sanitizeClientData(payload) { const { userDetails } = payload; const saltRounds = 10; const hashedPassword = await bcrypt.hash(userDetails.password, saltRounds); return { userDetails: { firstName: userDetails.firstName, lastName: userDetails.lastName, name: userDetails.name, email: userDetails.email.toLowerCase(), password: hashedPassword, address: userDetails.address || null, city: userDetails.city || null, state: userDetails.state || null, zip: userDetails.zip || null, billingName: userDetails.billingName || null, billingAddress: userDetails.billingAddress || null, billingCity: userDetails.billingCity || null, billingState: userDetails.billingState || null, billingZip: userDetails.billingZip || null, billingEmail: userDetails.billingEmail || null, position: userDetails.position || null, signatoryName: userDetails.signatoryName || null, signatoryEmail: userDetails.signatoryEmail || null, paymentMethod: null, bankName: null, accountNumber: null, routingNumber: null, accountType: null, accountHolderName: null, cardNumber: null, expiryDate: null, securityCode: null, cardZip: null, } }; } } export default VaClientService;