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';
|
2025-11-13 15:53:35 +05:30
|
|
|
import { z } from 'zod';
|
|
|
|
|
import { hostCompanyDetailsSchema } from '@/common/utils/validation/host/hostCompanyDetails.validation';
|
2025-11-13 17:48:09 +05:30
|
|
|
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';
|
2025-11-21 14:53:53 +05:30
|
|
|
import { getPresignedUrl } from '@/common/middlewares/aws/getPreSignedUrl';
|
|
|
|
|
import config from '@/config/config';
|
2025-11-13 15:53:35 +05:30
|
|
|
|
|
|
|
|
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 },
|
2025-11-13 14:59:50 +05:30
|
|
|
include: {
|
2025-11-14 15:04:01 +05:30
|
|
|
hostParent: true,
|
|
|
|
|
HostBankDetails: true,
|
|
|
|
|
HostDocuments: true,
|
|
|
|
|
HostSuggestion: true,
|
|
|
|
|
HostTrack: true,
|
|
|
|
|
}
|
2025-11-13 14:59:50 +05:30
|
|
|
});
|
|
|
|
|
|
2025-11-14 15:04:01 +05:30
|
|
|
if (!host) {
|
2025-11-20 18:37:26 +05:30
|
|
|
// If host record doesn't exist yet, return stepper 1 (NOT_SUBMITTED)
|
|
|
|
|
// so callers (like the stepper endpoint) can show initial step.
|
|
|
|
|
return { stepper: STEPPER.NOT_SUBMITTED } as any;
|
2025-11-12 16:03:57 +05:30
|
|
|
}
|
2025-11-13 14:59:50 +05:30
|
|
|
|
2025-11-21 14:53:53 +05:30
|
|
|
if (host.HostDocuments?.length) {
|
|
|
|
|
const bucket = config.aws.bucketName;
|
|
|
|
|
|
|
|
|
|
for (const doc of host.HostDocuments) {
|
|
|
|
|
if (doc.filePath) {
|
|
|
|
|
const filePath = doc.filePath;
|
|
|
|
|
|
|
|
|
|
// If full URL is saved, extract only key
|
|
|
|
|
const key = filePath.startsWith("http")
|
|
|
|
|
? filePath.split(".com/")[1]
|
|
|
|
|
: filePath;
|
|
|
|
|
|
|
|
|
|
(doc as any).presignedUrl = await getPresignedUrl(bucket, key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 14:59:50 +05:30
|
|
|
async createMinglarUser(email: string) {
|
2025-11-12 16:03:57 +05:30
|
|
|
const newUser = await this.prisma.user.create({
|
2025-11-13 17:48:09 +05:30
|
|
|
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-21 13:31:41 +05:30
|
|
|
async addPaymentDetails(data: AddPaymentDetailsDTO) {
|
|
|
|
|
return await this.prisma.$transaction(async (tx) => {
|
|
|
|
|
const addedPaymentDetails = await tx.hostBankDetails.create({
|
|
|
|
|
data,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!addedPaymentDetails) {
|
|
|
|
|
throw new ApiError(400, 'Failed to add payment details');
|
|
|
|
|
}
|
2025-11-14 14:08:47 +05:30
|
|
|
|
2025-11-21 13:31:41 +05:30
|
|
|
await tx.hostHeader.update({
|
|
|
|
|
where: { id: data.hostXid },
|
|
|
|
|
data: {
|
|
|
|
|
stepper: STEPPER.BANK_DETAILS_UPDATED
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-11-14 14:08:47 +05:30
|
|
|
|
2025-11-21 13:31:41 +05:30
|
|
|
async acceptMinglarAgreement(user_xid: number) {
|
|
|
|
|
const hostDetails = await this.prisma.hostHeader.findFirst({
|
|
|
|
|
where: { userXid: user_xid },
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
userXid: true,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
await this.prisma.hostHeader.update({
|
|
|
|
|
where: { id: hostDetails.id },
|
|
|
|
|
data: {
|
2025-11-22 19:22:34 +05:30
|
|
|
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
|
|
|
|
agreementAccepted: true,
|
|
|
|
|
isApproved: true
|
2025-11-21 13:31:41 +05:30
|
|
|
}
|
|
|
|
|
})
|
2025-11-12 19:59:54 +05:30
|
|
|
}
|
2025-11-13 15:53:35 +05:30
|
|
|
|
2025-11-22 19:30:16 +05:30
|
|
|
async getPQQQuestionDetail(question_xid: number, activity_xid: number) {
|
|
|
|
|
return await this.prisma.activityPQQheader.findFirst({
|
2025-11-22 19:47:04 +05:30
|
|
|
where: { activityXid: activity_xid, pqqQuestionXid: question_xid, isActive: true },
|
2025-11-22 19:30:16 +05:30
|
|
|
select: {
|
|
|
|
|
pqqQuestionXid: true,
|
|
|
|
|
pqqAnswerXid: true,
|
|
|
|
|
ActivityPQQSupportings: true
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-22 19:47:04 +05:30
|
|
|
async getLatestQuestionDetailsPQQ(activity_xid: number) {
|
|
|
|
|
return await this.prisma.activityPQQheader.findFirst({
|
|
|
|
|
where: { activityXid: activity_xid, isActive: true },
|
|
|
|
|
select: { pqqQuestionXid: true, pqqAnswerXid: true },
|
|
|
|
|
orderBy: { id: 'desc' }
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-17 19:05:26 +05:30
|
|
|
async addOrUpdateCompanyDetails(
|
2025-11-14 14:08:47 +05:30
|
|
|
user_xid: number,
|
2025-11-13 15:53:35 +05:30
|
|
|
companyData: HostCompanyDetailsInput,
|
2025-11-17 19:05:26 +05:30
|
|
|
documents: HostDocumentInput[],
|
|
|
|
|
parentCompanyData?: any | null,
|
|
|
|
|
parentDocuments?: HostDocumentInput[]
|
2025-11-13 15:53:35 +05:30
|
|
|
) {
|
|
|
|
|
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-13 15:53:35 +05:30
|
|
|
});
|
2025-11-17 19:05:26 +05:30
|
|
|
|
|
|
|
|
// CREATE
|
|
|
|
|
if (!existingHostCompany) {
|
|
|
|
|
// Optionally check unique registration number
|
2025-11-22 08:00:59 +05:30
|
|
|
const existingByPan = await tx.hostHeader.findFirst({
|
|
|
|
|
where: { panNumber: companyData.panNumber },
|
2025-11-17 19:05:26 +05:30
|
|
|
});
|
2025-11-22 08:00:59 +05:30
|
|
|
if (existingByPan) throw new ApiError(400, 'Company already exists with this pan/bin number');
|
2025-11-17 19:05:26 +05:30
|
|
|
|
|
|
|
|
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-13 15:53:35 +05:30
|
|
|
}
|
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 },
|
2025-11-13 15:53:35 +05:30
|
|
|
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,
|
2025-11-13 15:53:35 +05:30
|
|
|
isSubsidairy: companyData.isSubsidairy,
|
|
|
|
|
registrationNumber: companyData.registrationNumber,
|
|
|
|
|
panNumber: companyData.panNumber,
|
2025-11-17 19:05:26 +05:30
|
|
|
gstNumber: companyData.gstNumber || null,
|
2025-11-13 15:53:35 +05:30
|
|
|
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,
|
2025-11-13 15:53:35 +05:30
|
|
|
currencyXid: companyData.currencyXid,
|
2025-11-21 11:47:29 +05:30
|
|
|
stepper: STEPPER.UNDER_REVIEW
|
2025-11-17 19:05:26 +05:30
|
|
|
// hostRefNumber: DO NOT UPDATE
|
2025-11-13 15:53:35 +05:30
|
|
|
},
|
|
|
|
|
});
|
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) {
|
2025-11-13 15:53:35 +05:30
|
|
|
const docsData = documents.map((doc) => ({
|
2025-11-17 19:05:26 +05:30
|
|
|
hostXid: updatedHost.id,
|
2025-11-13 15:53:35 +05:30
|
|
|
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-13 17:48:09 +05:30
|
|
|
}
|
|
|
|
|
}
|
2025-11-14 14:08:47 +05:30
|
|
|
|
2025-11-17 19:05:26 +05:30
|
|
|
return updatedHost;
|
2025-11-13 15:53:35 +05:30
|
|
|
});
|
|
|
|
|
}
|
2025-11-14 14:08:47 +05:30
|
|
|
|
2025-11-22 11:59:48 +05:30
|
|
|
async getSuggestionDetails(user_xid: number) {
|
|
|
|
|
const hostDetails = await this.prisma.hostHeader.findFirst({
|
|
|
|
|
where: { userXid: user_xid, isActive: true },
|
|
|
|
|
include: {
|
|
|
|
|
user: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
emailAddress: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
accountManager: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
emailAddress: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!hostDetails) {
|
|
|
|
|
return { hostSuggestionDetails: [], hostDetails: null };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hostSuggestionDetails = await this.prisma.hostSuggestion.findMany({
|
|
|
|
|
where: { hostXid: hostDetails.id, isActive: true, isreviewed: false }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (hostSuggestionDetails) {
|
|
|
|
|
await this.prisma.hostSuggestion.updateMany({
|
|
|
|
|
where: { hostXid: hostDetails.id, isActive: true, isreviewed: false },
|
|
|
|
|
data: {
|
|
|
|
|
isreviewed: true,
|
|
|
|
|
reviewedByXid: hostDetails.id,
|
|
|
|
|
reviewOn: new Date(),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { hostSuggestionDetails, hostDetails };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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-22 20:05:43 +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
|
|
|
|
|
// }
|
|
|
|
|
// });
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
async createHeader(
|
2025-11-19 16:55:54 +05:30
|
|
|
activityXid: number,
|
|
|
|
|
pqqQuestionXid: number,
|
|
|
|
|
pqqAnswerXid: number,
|
2025-11-22 20:05:43 +05:30
|
|
|
comments?: string | null
|
2025-11-19 16:55:54 +05:30
|
|
|
) {
|
2025-11-22 20:05:43 +05:30
|
|
|
return await this.prisma.activityPQQheader.create({
|
|
|
|
|
data: {
|
|
|
|
|
activityXid,
|
|
|
|
|
pqqQuestionXid,
|
|
|
|
|
pqqAnswerXid,
|
|
|
|
|
comments: comments || null // Handle null comments
|
|
|
|
|
}
|
2025-11-19 16:55:54 +05:30
|
|
|
});
|
2025-11-22 20:05:43 +05:30
|
|
|
}
|
2025-11-19 16:55:54 +05:30
|
|
|
|
2025-11-22 20:05:43 +05:30
|
|
|
async findHeaderByCompositeKey(
|
|
|
|
|
activityXid: number,
|
|
|
|
|
pqqQuestionXid: number,
|
|
|
|
|
pqqAnswerXid: number
|
|
|
|
|
) {
|
|
|
|
|
return await this.prisma.activityPQQheader.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
activityXid,
|
|
|
|
|
pqqQuestionXid,
|
|
|
|
|
pqqAnswerXid
|
2025-11-19 16:55:54 +05:30
|
|
|
}
|
|
|
|
|
});
|
2025-11-22 20:05:43 +05:30
|
|
|
}
|
2025-11-19 16:55:54 +05:30
|
|
|
|
2025-11-22 20:05:43 +05:30
|
|
|
async updateHeader(
|
|
|
|
|
headerId: number,
|
|
|
|
|
comments?: string | null
|
|
|
|
|
) {
|
2025-11-19 16:55:54 +05:30
|
|
|
return await this.prisma.activityPQQheader.update({
|
2025-11-22 20:05:43 +05:30
|
|
|
where: {
|
|
|
|
|
id: headerId
|
|
|
|
|
},
|
2025-11-19 16:55:54 +05:30
|
|
|
data: {
|
2025-11-22 20:05:43 +05:30
|
|
|
comments: comments || null, // Handle null comments
|
|
|
|
|
updatedAt: new Date()
|
2025-11-19 16:55:54 +05:30
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async addSupportingFile(
|
|
|
|
|
headerId: number,
|
|
|
|
|
mimeType: string,
|
|
|
|
|
fileUrl: string
|
|
|
|
|
) {
|
|
|
|
|
return await this.prisma.activityPQQSupportings.create({
|
|
|
|
|
data: {
|
|
|
|
|
activityPqqHeaderXid: headerId,
|
|
|
|
|
mediaType: mimeType,
|
|
|
|
|
mediaFileName: fileUrl
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-22 20:05:43 +05:30
|
|
|
async getSupportingFilesByHeaderId(headerId: number) {
|
|
|
|
|
return await this.prisma.activityPQQSupportings.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
activityPqqHeaderXid: headerId
|
|
|
|
|
},
|
|
|
|
|
orderBy: {
|
|
|
|
|
id: 'asc' // Maintain consistent order
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updateSupportingFile(
|
|
|
|
|
supportingFileId: number,
|
|
|
|
|
mimeType: string,
|
|
|
|
|
fileUrl: string
|
|
|
|
|
) {
|
|
|
|
|
return await this.prisma.activityPQQSupportings.update({
|
|
|
|
|
where: {
|
|
|
|
|
id: supportingFileId
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
mediaType: mimeType,
|
|
|
|
|
mediaFileName: fileUrl,
|
|
|
|
|
updatedAt: new Date()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async deleteSupportingFile(supportingFileId: number) {
|
|
|
|
|
return await this.prisma.activityPQQSupportings.delete({
|
|
|
|
|
where: {
|
|
|
|
|
id: supportingFileId
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-22 19:25:07 +05:30
|
|
|
async getAllActivityTypesWithInterest(search?: string) {
|
|
|
|
|
const where: any = {
|
|
|
|
|
isActive: true,
|
|
|
|
|
deletedAt: null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (search && search.trim() !== '') {
|
|
|
|
|
const q = search.trim();
|
|
|
|
|
where.OR = [
|
|
|
|
|
{ activityTypeName: { contains: q, mode: 'insensitive' } },
|
|
|
|
|
{ interests: { interestName: { contains: q, mode: 'insensitive' } } },
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await this.prisma.activityTypes.findMany({
|
|
|
|
|
where,
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
activityTypeName: true,
|
|
|
|
|
interestXid: true,
|
|
|
|
|
interests: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
interestName: true,
|
|
|
|
|
displayOrder: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: { activityTypeName: 'asc' },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-22 20:48:45 +05:30
|
|
|
async createActivity(
|
|
|
|
|
userId: number,
|
|
|
|
|
activityTypeXid: number,
|
|
|
|
|
frequenciesXid?: number,
|
|
|
|
|
) {
|
|
|
|
|
// Find host header for this user
|
|
|
|
|
const host = await this.prisma.hostHeader.findFirst({ where: { userXid: userId, isActive: true } });
|
|
|
|
|
if (!host) {
|
|
|
|
|
throw new ApiError(404, 'Host not found for the user');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate activityType exists
|
|
|
|
|
const activityType = await this.prisma.activityTypes.findUnique({ where: { id: activityTypeXid } });
|
|
|
|
|
if (!activityType) {
|
|
|
|
|
throw new ApiError(404, 'Activity type not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optionally validate frequency
|
|
|
|
|
if (frequenciesXid) {
|
|
|
|
|
const freq = await this.prisma.frequencies.findUnique({ where: { id: frequenciesXid } });
|
|
|
|
|
if (!freq) throw new ApiError(404, 'Frequency not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const created = await this.prisma.activities.create({
|
|
|
|
|
data: {
|
|
|
|
|
hostXid: host.id,
|
|
|
|
|
activityTypeXid: activityTypeXid,
|
|
|
|
|
frequenciesXid: frequenciesXid || null,
|
|
|
|
|
isActive: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return created;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 15:05:01 +05:30
|
|
|
}
|