Files
MinglarBackendNestJS/src/modules/host/services/host.service.ts

509 lines
17 KiB
TypeScript
Raw Normal View History

2025-11-10 15:05:01 +05:30
// src/modules/host/services/host.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../../common/database/prisma.service';
2025-11-12 19:59:54 +05:30
import { AddPaymentDetailsDTO, CreateHostDto, UpdateHostDto } from '../dto/host.dto';
2025-11-12 16:03:57 +05:30
import * as bcrypt from 'bcryptjs';
import ApiError from '../../../common/utils/helper/ApiError';
import { User } from '@prisma/client';
import { z } from 'zod';
import { hostCompanyDetailsSchema } from '@/common/utils/validation/host/hostCompanyDetails.validation';
import { HOST_STATUS_DISPLAY, HOST_STATUS_INTERNAL, STEPPER } from '@/common/utils/constants/host.constant';
import { MINGLAR_STATUS_DISPLAY, MINGLAR_STATUS_INTERNAL } from '@/common/utils/constants/minglar.constant';
import { ROLE } from '@/common/utils/constants/common.constant';
type HostCompanyDetailsInput = z.infer<typeof hostCompanyDetailsSchema>;
// Document input after S3 upload (with S3 URL as filePath)
interface HostDocumentInput {
documentTypeXid: number;
documentName: string;
filePath: string; // S3 URL
}
2025-11-10 15:05:01 +05:30
@Injectable()
export class HostService {
2025-11-12 16:03:57 +05:30
constructor(private prisma: PrismaService) { }
2025-11-10 15:05:01 +05:30
async createHost(data: CreateHostDto) {
return this.prisma.user.create({ data });
}
async getAllHosts() {
return this.prisma.user.findMany({ where: { roleXid: 3 } });
}
2025-11-14 14:08:47 +05:30
async getHostIdByUserXid(user_xid: number) {
const host = await this.prisma.hostHeader.findFirst({
where: { userXid: user_xid },
2025-11-14 15:15:13 +05:30
select: { id: true, companyName: true, countryXid: true, stepper: true },
2025-11-14 14:08:47 +05:30
});
return host;
}
2025-11-10 15:05:01 +05:30
async getHostById(id: number) {
2025-11-14 15:04:01 +05:30
const host = await this.prisma.hostHeader.findFirst({
where: { userXid: id },
include: {
2025-11-14 15:04:01 +05:30
hostParent: true,
HostBankDetails: true,
HostDocuments: true,
HostSuggestion: true,
HostTrack: true,
}
});
2025-11-14 15:04:01 +05:30
if (!host) {
throw new ApiError(404, 'Host record not found.');
2025-11-12 16:03:57 +05:30
}
2025-11-10 15:05:01 +05:30
return host;
}
async updateHost(id: number, data: UpdateHostDto) {
return this.prisma.user.update({
where: { id },
data,
});
}
async deleteHost(id: number) {
return this.prisma.user.delete({ where: { id } });
}
2025-11-12 16:03:57 +05:30
async getHostByEmail(email: string): Promise<User> {
return this.prisma.user.findUnique({ where: { emailAddress: email } });
}
async verifyHostOtp(email: string, otp: string): Promise<boolean> {
const user = await this.prisma.user.findUnique({
where: { emailAddress: email },
select: {
id: true,
emailAddress: true,
UserOtp: {
where: { isActive: true, isVerified: false },
orderBy: { createdAt: 'desc' },
take: 1,
},
},
});
if (!user) {
throw new ApiError(404, 'User not found.');
}
const userOtp = user.UserOtp[0];
if (!userOtp) {
throw new ApiError(400, 'No OTP found.');
}
if (new Date() > userOtp.expiresOn) {
throw new ApiError(400, 'OTP has expired.');
}
const isMatch = await bcrypt.compare(otp, userOtp.otpCode);
if (!isMatch) {
throw new ApiError(400, 'Invalid OTP.');
}
await this.prisma.userOtp.update({
where: { id: userOtp.id },
data: {
isVerified: true,
verifiedOn: new Date(),
isActive: false,
},
});
return true;
}
async loginForHost(emailAddress: string, userPassword: string) {
const existingUser = await this.prisma.user.findUnique({
where: { emailAddress: emailAddress },
});
if (!existingUser) {
throw new ApiError(404, 'User not found');
}
if (existingUser.roleXid !== 4) {
throw new ApiError(403, 'Access denied. Not a host user.');
}
const matchPassword = await bcrypt.compare(userPassword, existingUser.userPassword);
if (!matchPassword) {
throw new ApiError(401, 'Invalid credentials');
}
return existingUser;
}
async createMinglarUser(email: string) {
2025-11-12 16:03:57 +05:30
const newUser = await this.prisma.user.create({
data: { emailAddress: email, roleXid: ROLE.HOST },
2025-11-12 16:03:57 +05:30
});
return newUser;
}
async createPassword(user_xid: number, password: string): Promise<boolean> {
// Find user by id
const user = await this.prisma.user.findUnique({
where: { id: user_xid },
select: { id: true, emailAddress: true, userPassword: true },
});
if (!user) {
throw new ApiError(404, 'User not found');
}
// Check if password already exists
if (user.userPassword) {
throw new ApiError(400, 'Password already exists. Use update password instead.');
}
// Hash the password
const saltRounds = parseInt(process.env.SALT_ROUNDS || '10', 10);
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Update user with hashed password
await this.prisma.user.update({
where: { id: user.id },
data: { userPassword: hashedPassword },
});
return true;
}
2025-11-12 19:59:54 +05:30
2025-11-14 14:08:47 +05:30
async addPaymentDetails(data: AddPaymentDetailsDTO): Promise<AddPaymentDetailsDTO> {
2025-11-12 19:59:54 +05:30
const addedPaymentDetails = await this.prisma.hostBankDetails.create({
data,
});
2025-11-14 14:08:47 +05:30
2025-11-12 19:59:54 +05:30
if (!addedPaymentDetails) {
throw new ApiError(400, 'Failed to add payment details');
}
2025-11-14 14:08:47 +05:30
2025-11-12 19:59:54 +05:30
return addedPaymentDetails;
}
2025-11-17 19:05:26 +05:30
async addOrUpdateCompanyDetails(
2025-11-14 14:08:47 +05:30
user_xid: number,
companyData: HostCompanyDetailsInput,
2025-11-17 19:05:26 +05:30
documents: HostDocumentInput[],
parentCompanyData?: any | null,
parentDocuments?: HostDocumentInput[]
) {
return await this.prisma.$transaction(async (tx) => {
2025-11-17 19:05:26 +05:30
// Check if host already has a company
const existingHostCompany = await tx.hostHeader.findFirst({
where: { userXid: user_xid },
include: { hostParent: true },
});
2025-11-17 19:05:26 +05:30
// CREATE
if (!existingHostCompany) {
// Optionally check unique registration number
const existingByReg = await tx.hostHeader.findFirst({
where: { registrationNumber: companyData.registrationNumber },
});
if (existingByReg) throw new ApiError(400, 'Company already exists with this registration number');
const refNumber = await this.generateHostRefNumber(tx);
const createdHost = await tx.hostHeader.create({
data: {
userXid: user_xid,
companyName: companyData.companyName,
hostRefNumber: refNumber,
address1: companyData.address1,
address2: companyData.address2,
cityXid: companyData.cityXid,
stateXid: companyData.stateXid,
countryXid: companyData.countryXid,
pinCode: companyData.pinCode,
logoPath: companyData.logoPath || null,
isSubsidairy: companyData.isSubsidairy,
registrationNumber: companyData.registrationNumber,
panNumber: companyData.panNumber,
gstNumber: companyData.gstNumber || null,
formationDate: new Date(companyData.formationDate),
companyType: companyData.companyType,
websiteUrl: companyData.websiteUrl || null,
instagramUrl: companyData.instagramUrl || null,
facebookUrl: companyData.facebookUrl || null,
linkedinUrl: companyData.linkedinUrl || null,
twitterUrl: companyData.twitterUrl || null,
currencyXid: companyData.currencyXid,
stepper: STEPPER.UNDER_REVIEW,
hostStatusInternal: HOST_STATUS_INTERNAL.HOST_SUBMITTED,
hostStatusDisplay: HOST_STATUS_DISPLAY.UNDER_REVIEW,
adminStatusInternal: MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW,
adminStatusDisplay: MINGLAR_STATUS_DISPLAY.NEW,
},
});
// Create host documents
if (documents?.length) {
const docsData = documents.map((doc) => ({
hostXid: createdHost.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostDocuments.createMany({ data: docsData });
}
// Parent company and its docs (if present)
if (companyData.isSubsidairy && parentCompanyData) {
const createdParent = await tx.hostParent.create({
data: {
hostXid: createdHost.id,
companyName: parentCompanyData.companyName,
address1: parentCompanyData.address1,
address2: parentCompanyData.address2 || null,
cityXid: parentCompanyData.cityXid,
stateXid: parentCompanyData.stateXid,
countryXid: parentCompanyData.countryXid,
pinCode: parentCompanyData.pinCode,
logoPath: parentCompanyData.logoPath || null,
isSubsidairy: false,
registrationNumber: parentCompanyData.registrationNumber,
panNumber: parentCompanyData.panNumber,
gstNumber: parentCompanyData.gstNumber || null,
formationDate: new Date(parentCompanyData.formationDate),
companyType: parentCompanyData.companyType,
websiteUrl: parentCompanyData.websiteUrl || null,
instagramUrl: parentCompanyData.instagramUrl || null,
facebookUrl: parentCompanyData.facebookUrl || null,
linkedinUrl: parentCompanyData.linkedinUrl || null,
twitterUrl: parentCompanyData.twitterUrl || null,
},
});
if (parentDocuments?.length) {
const parentDocsData = parentDocuments.map((doc) => ({
hostParentXid: createdParent.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostParenetDocuments.createMany({ data: parentDocsData });
}
}
return createdHost;
}
2025-11-14 14:08:47 +05:30
2025-11-17 19:05:26 +05:30
// UPDATE existing
// Prevent changing hostRefNumber
const updatedHost = await tx.hostHeader.update({
where: { id: existingHostCompany.id },
data: {
companyName: companyData.companyName,
address1: companyData.address1,
address2: companyData.address2,
cityXid: companyData.cityXid,
stateXid: companyData.stateXid,
countryXid: companyData.countryXid,
pinCode: companyData.pinCode,
2025-11-17 19:05:26 +05:30
logoPath: companyData.logoPath || null,
isSubsidairy: companyData.isSubsidairy,
registrationNumber: companyData.registrationNumber,
panNumber: companyData.panNumber,
2025-11-17 19:05:26 +05:30
gstNumber: companyData.gstNumber || null,
formationDate: new Date(companyData.formationDate),
companyType: companyData.companyType,
2025-11-17 19:05:26 +05:30
websiteUrl: companyData.websiteUrl || null,
instagramUrl: companyData.instagramUrl || null,
facebookUrl: companyData.facebookUrl || null,
linkedinUrl: companyData.linkedinUrl || null,
twitterUrl: companyData.twitterUrl || null,
currencyXid: companyData.currencyXid,
2025-11-17 19:05:26 +05:30
// hostRefNumber: DO NOT UPDATE
},
});
2025-11-14 14:08:47 +05:30
2025-11-17 19:05:26 +05:30
// Replace host documents (delete old, insert new)
await tx.hostDocuments.deleteMany({ where: { hostXid: updatedHost.id } });
if (documents?.length) {
const docsData = documents.map((doc) => ({
2025-11-17 19:05:26 +05:30
hostXid: updatedHost.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostDocuments.createMany({ data: docsData });
}
2025-11-14 14:08:47 +05:30
2025-11-17 19:05:26 +05:30
// Parent company create/update and replace parent docs
if (companyData.isSubsidairy) {
// existingHostCompany.hostParent may be array or single object depending on Prisma schema
let parentRecord = (existingHostCompany as any).hostParent;
if (Array.isArray(parentRecord)) parentRecord = parentRecord[0];
if (!parentRecord) {
// create
const createdParent = await tx.hostParent.create({
data: {
hostXid: updatedHost.id,
companyName: parentCompanyData.companyName,
address1: parentCompanyData.address1,
address2: parentCompanyData.address2 || null,
cityXid: parentCompanyData.cityXid,
stateXid: parentCompanyData.stateXid,
countryXid: parentCompanyData.countryXid,
pinCode: parentCompanyData.pinCode,
logoPath: parentCompanyData.logoPath || null,
isSubsidairy: false,
registrationNumber: parentCompanyData.registrationNumber,
panNumber: parentCompanyData.panNumber,
gstNumber: parentCompanyData.gstNumber || null,
formationDate: new Date(parentCompanyData.formationDate),
companyType: parentCompanyData.companyType,
websiteUrl: parentCompanyData.websiteUrl || null,
instagramUrl: parentCompanyData.instagramUrl || null,
facebookUrl: parentCompanyData.facebookUrl || null,
linkedinUrl: parentCompanyData.linkedinUrl || null,
twitterUrl: parentCompanyData.twitterUrl || null,
},
});
if (parentDocuments?.length) {
const parentDocsData = parentDocuments.map((doc) => ({
hostParentXid: createdParent.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostParenetDocuments.createMany({ data: parentDocsData });
}
} else {
// update
await tx.hostParent.update({
where: { id: parentRecord.id },
data: {
companyName: parentCompanyData.companyName,
address1: parentCompanyData.address1,
address2: parentCompanyData.address2 || null,
cityXid: parentCompanyData.cityXid,
stateXid: parentCompanyData.stateXid,
countryXid: parentCompanyData.countryXid,
pinCode: parentCompanyData.pinCode,
logoPath: parentCompanyData.logoPath || null,
registrationNumber: parentCompanyData.registrationNumber,
panNumber: parentCompanyData.panNumber,
gstNumber: parentCompanyData.gstNumber || null,
formationDate: new Date(parentCompanyData.formationDate),
companyType: parentCompanyData.companyType,
websiteUrl: parentCompanyData.websiteUrl || null,
instagramUrl: parentCompanyData.instagramUrl || null,
facebookUrl: parentCompanyData.facebookUrl || null,
linkedinUrl: parentCompanyData.linkedinUrl || null,
twitterUrl: parentCompanyData.twitterUrl || null,
},
});
// replace parent docs
await tx.hostParenetDocuments.deleteMany({ where: { hostParentXid: parentRecord.id } });
if (parentDocuments?.length) {
const parentDocsData = parentDocuments.map((doc) => ({
hostParentXid: parentRecord.id,
documentTypeXid: doc.documentTypeXid,
documentName: doc.documentName,
filePath: doc.filePath,
}));
await tx.hostParenetDocuments.createMany({ data: parentDocsData });
}
}
} else {
// If previously had a parent and now isSubsidairy=false -> optionally delete parent and its docs
const previousParent = (existingHostCompany as any).hostParent;
let prevParentId = null;
if (Array.isArray(previousParent) && previousParent.length) prevParentId = previousParent[0].id;
else if (previousParent && previousParent.id) prevParentId = previousParent.id;
if (prevParentId) {
await tx.hostParenetDocuments.deleteMany({ where: { hostParentXid: prevParentId } });
await tx.hostParent.delete({ where: { id: prevParentId } });
}
}
2025-11-14 14:08:47 +05:30
2025-11-17 19:05:26 +05:30
return updatedHost;
});
}
2025-11-14 14:08:47 +05:30
2025-11-17 19:05:26 +05:30
2025-11-17 15:28:22 +05:30
async generateHostRefNumber(tx: any) {
const lastHost = await tx.hostHeader.findFirst({
orderBy: {
id: 'desc',
},
select: {
id: true,
}
});
const nextId = lastHost ? lastHost.id + 1 : 1;
return `HOSTREFNO-${String(nextId).padStart(6, '0')}`;
}
2025-11-19 16:55:54 +05:30
async createOrUpdateHeader(
activityXid: number,
pqqQuestionXid: number,
pqqAnswerXid: number,
comments: string | null
) {
// find existing header
const existing = await this.prisma.activityPQQheader.findFirst({
where: { activityXid, pqqQuestionXid, deletedAt: null }
});
if (!existing) {
return await this.prisma.activityPQQheader.create({
data: {
activityXid,
pqqQuestionXid,
pqqAnswerXid,
comments
}
});
}
// mark old supportings deleted
await this.prisma.activityPQQSupportings.updateMany({
where: { activityPqqHeaderXid: existing.id },
data: {
isActive: false,
deletedAt: new Date()
}
});
// update header
return await this.prisma.activityPQQheader.update({
where: { id: existing.id },
data: {
pqqAnswerXid,
comments
}
});
}
async addSupportingFile(
headerId: number,
mimeType: string,
fileUrl: string
) {
return await this.prisma.activityPQQSupportings.create({
data: {
activityPqqHeaderXid: headerId,
mediaType: mimeType,
mediaFileName: fileUrl
}
});
}
2025-11-10 15:05:01 +05:30
}