va-tracker-middleware/src/services/vaClient.service.js

307 lines
9.6 KiB
JavaScript

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;