Add ActivityTrack model to schema and update User and Activities models to include activityTracks relation. Modify seed data to reflect new interest names and activity types. Implement activity reference number generation in HostService for activity creation.

This commit is contained in:
2025-12-02 13:42:14 +05:30
parent 3f921febe0
commit e72c260b18
5 changed files with 199 additions and 73 deletions

View File

@@ -66,6 +66,7 @@ model User {
friendOf Friends[] @relation("FriendUser")
userAddressDetails UserAddressDetails[]
userDocuments UserDocuments[]
activityTracks ActivityTrack[]
@@map("users")
@@schema("usr")
@@ -908,6 +909,7 @@ model Activities {
ActivityEquipmentTaxes ActivityEquipmentTaxes[]
ScheduleHeader ScheduleHeader[]
ItineraryActivities ItineraryActivities[]
activityTracks ActivityTrack[]
@@map("activities")
@@schema("act")
@@ -932,6 +934,25 @@ model ActivityOtherDetails {
@@schema("act")
}
model ActivityTrack {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
trackType String? @default("PQQ") @map("track_type")
updatedByRole String? @map("updated_by_role")
trackStatus String? @map("track_status")
updatedByXid Int? @map("updated_by_xid")
user User? @relation(fields: [updatedByXid], references: [id], onDelete: Cascade)
updatedOn DateTime? @map("updated_on")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("activity_track")
@@schema("act")
}
model ActivitiesMedia {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")

View File

@@ -78,35 +78,51 @@ async function main() {
create: { interestName: 'Chill and Zen', displayOrder: 1 },
});
const sweatmode = await prisma.interests.upsert({
where: { interestName: 'Sweat Mode' },
where: { interestName: 'Sweat Mode On' },
update: {},
create: { interestName: 'Sweat Mode', displayOrder: 2 },
create: { interestName: 'Sweat Mode On', displayOrder: 2 },
});
const gameon = await prisma.interests.upsert({
where: { interestName: 'Game On' },
const trackracer = await prisma.interests.upsert({
where: { interestName: 'Track Racer' },
update: {},
create: { interestName: 'Game On', displayOrder: 3 },
create: { interestName: 'Track Racer', displayOrder: 3 },
});
const circuitracer = await prisma.interests.upsert({
where: { interestName: 'Circuit Racer' },
update: {},
create: { interestName: 'Circuit Racer', displayOrder: 4 },
});
const thermalGliding = await prisma.interests.upsert({
where: { interestName: 'Thermal Gliding' },
update: {},
create: { interestName: 'Thermal Gliding', displayOrder: 5 },
});
const partycentral = await prisma.interests.upsert({
where: { interestName: 'Party Central' },
update: {},
create: { interestName: 'Party Central', displayOrder: 4 },
create: { interestName: 'Party Central', displayOrder: 6 },
});
const artsy = await prisma.interests.upsert({
where: { interestName: 'Artsy' },
const aqua = await prisma.interests.upsert({
where: { interestName: 'Aqua' },
update: {},
create: { interestName: 'Artsy', displayOrder: 5 },
create: { interestName: 'Aqua', displayOrder: 7 },
});
const foodiediaries = await prisma.interests.upsert({
where: { interestName: 'Foodie Diaries' },
const foodie = await prisma.interests.upsert({
where: { interestName: 'Foodie' },
update: {},
create: { interestName: 'Foodie Diaries', displayOrder: 6 },
create: { interestName: 'Foodie', displayOrder: 8 },
});
await prisma.activityTypes.createMany({
data: [
{ interestXid: chillandzen.id, activityTypeName: 'Cricket' },
{ interestXid: chillandzen.id, activityTypeName: 'Football' },
{ interestXid: aqua.id, activityTypeName: 'Scuba-Diving' },
{ interestXid: sweatmode.id, activityTypeName: 'Cloudboarding' },
{ interestXid: partycentral.id, activityTypeName: 'Soaring Glider' },
{ interestXid: sweatmode.id, activityTypeName: 'Speedway Racer' },
{ interestXid: aqua.id, activityTypeName: 'Aerial Surfing' },
{ interestXid: foodie.id, activityTypeName: 'Wine Tasting' },
{ interestXid: trackracer.id, activityTypeName: 'Track Racer' },
{ interestXid: thermalGliding.id, activityTypeName: 'Thermal Gliding' },
],
skipDuplicates: true,
});

View File

@@ -38,6 +38,20 @@ interface HostDocumentInput {
documentName: string;
filePath: string; // S3 URL
}
export async function generateActivityRefNumber(tx: any) {
const lastrecord = await tx.activities.findFirst({
orderBy: {
id: 'desc',
},
select: {
id: true,
},
});
const nextId = lastrecord ? lastrecord.id + 1 : 1;
return `ACT-${String(nextId).padStart(6, '0')}`;;
}
@Injectable()
export class HostService {
@@ -330,6 +344,7 @@ export class HostService {
where: { id: data.hostXid },
data: {
stepper: STEPPER.BANK_DETAILS_UPDATED,
currencyXid: data.currencyXid
},
});
});
@@ -1206,42 +1221,48 @@ export class HostService {
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');
}
return await this.prisma.$transaction(async (tx) => {
// Optionally validate frequency
if (frequenciesXid) {
const freq = await this.prisma.frequencies.findUnique({
where: { id: frequenciesXid },
// Fetch host
const host = await tx.hostHeader.findFirst({
where: { userXid: userId, isActive: true },
});
if (!freq) throw new ApiError(404, 'Frequency not found');
}
if (!host) throw new ApiError(404, 'Host not found for the user');
const created = await this.prisma.activities.create({
data: {
hostXid: host.id,
activityTypeXid: activityTypeXid,
frequenciesXid: frequenciesXid || null,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.DRAFT_PQ,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.DRAFT_PQ,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.DRAFT_PQ,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.DRAFT_PQ,
},
// Validate activityType
const activityType = await tx.activityTypes.findUnique({
where: { id: activityTypeXid },
});
if (!activityType) throw new ApiError(404, 'Activity type not found');
// Validate frequency
if (frequenciesXid) {
const freq = await tx.frequencies.findUnique({
where: { id: frequenciesXid },
});
if (!freq) throw new ApiError(404, 'Frequency not found');
}
// Generate reference number
const referenceNumber = await generateActivityRefNumber(tx);
// Create activity
const created = await tx.activities.create({
data: {
hostXid: host.id,
activityTypeXid,
frequenciesXid: frequenciesXid || null,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.DRAFT_PQ,
activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.DRAFT_PQ,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.DRAFT_PQ,
amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.DRAFT_PQ,
activityRefNumber: referenceNumber,
},
});
return created;
});
return created;
}
}

View File

@@ -192,6 +192,18 @@ export class MinglarService {
isActive: true,
userStatus: USER_STATUS.ACTIVE,
},
select: {
id: true,
firstName: true,
lastName: true,
emailAddress: true,
mobileNumber: true,
roleXid: true,
isProfileUpdated: true,
userStatus: true,
profileImage: true,
userPassword: true,
}
});
if (!existingUser) {
@@ -214,6 +226,16 @@ export class MinglarService {
throw new ApiError(401, 'Invalid credentials');
}
if (existingUser?.profileImage) {
const key = existingUser.profileImage.startsWith("http")
? existingUser.profileImage.split(".com/")[1]
: existingUser.profileImage;
existingUser.profileImage = await getPresignedUrl(bucket, key);
}
delete existingUser.userPassword;
return existingUser;
}
@@ -742,19 +764,36 @@ export class MinglarService {
}
/** APPLICATION STATUS FILTER (NEW) **/
const APPLICATION_STATUS_MAP: Record<string, string> = {
"New": MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW && MINGLAR_STATUS_DISPLAY.NEW,
"To_Review": MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW && MINGLAR_STATUS_DISPLAY.TO_REVIEW,
"Enchancing": MINGLAR_STATUS_INTERNAL.AM_REJECTED,
const APPLICATION_STATUS_MAP: Record<
string,
{ internal: string; display: string }
> = {
New: {
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
display: MINGLAR_STATUS_DISPLAY.NEW,
},
To_Review: {
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
display: MINGLAR_STATUS_DISPLAY.TO_REVIEW,
},
Enhancing: {
internal: MINGLAR_STATUS_INTERNAL.AM_REJECTED,
display: MINGLAR_STATUS_DISPLAY.ENHANCING,
},
};
if (applicationStatus?.trim()) {
const key = applicationStatus.trim();
if (APPLICATION_STATUS_MAP[key]) {
filters.adminStatusInternal = APPLICATION_STATUS_MAP[key];
const statusObj = APPLICATION_STATUS_MAP[key];
if (statusObj) {
filters.adminStatusInternal = statusObj.internal;
filters.adminStatusDisplay = statusObj.display;
}
}
/** ROLE-BASED FILTER **/
if (userRoleXid === ROLE.CO_ADMIN || userRoleXid === ROLE.ACCOUNT_MANAGER) {
filters.accountManagerXid = userId;
@@ -1513,7 +1552,21 @@ export class MinglarService {
async getAMdetailById(id: number) {
const user = await this.prisma.user.findUnique({
where: { id: id, isActive: true, userStatus: USER_STATUS.ACTIVE },
include: {
select: {
id: true,
firstName: true,
lastName: true,
emailAddress: true,
isdCode: true,
mobileNumber: true,
roleXid: true,
userStatus: true,
isProfileUpdated: true,
dateOfBirth: true,
profileImage: true,
isEmailVerfied: true,
isMobileVerfied: true,
isBiometric: true,
userAddressDetails: {
select: {
id: true,

View File

@@ -11,15 +11,11 @@ export class PrePopulateService {
isActive: true,
deletedAt: null,
},
include: {
BankBranches: {
select: {
id: true,
branchAddress: true,
ifscCode: true,
},
},
select: {
id: true,
bankName: true,
},
orderBy: { bankName: 'asc' }
});
}
@@ -28,7 +24,6 @@ export class PrePopulateService {
where: {
bankXid,
isActive: true,
deletedAt: null
},
select: {
id: true,
@@ -60,6 +55,12 @@ export class PrePopulateService {
isActive: true,
deletedAt: null,
},
select: {
id: true,
currencyName: true,
currencySymbol: true,
},
orderBy: { currencyName: 'asc' }
});
}
@@ -68,27 +69,41 @@ export class PrePopulateService {
where: { isActive: true },
include: {
pqqsubCategories: {
include: {
where: { isActive: true },
select: {
id: true,
subCategoryName: true,
categoryXid: true,
displayOrder: true,
questions: {
include: {
where: { isActive: true },
select: {
id: true,
questionName: true,
maxPoints: true,
displayOrder: true,
PQQAnswers: {
orderBy: {
displayOrder: 'asc'
where: { isActive: true },
orderBy: { displayOrder: 'asc' },
select: {
id: true,
answerName: true,
answerPoints: true,
displayOrder: true
}
}
},
orderBy: {
displayOrder: 'asc'
}
}
orderBy: { displayOrder: 'asc' }
},
},
orderBy: { displayOrder: 'asc' }
}
orderBy: { displayOrder: 'asc' },
},
},
orderBy: { displayOrder: 'asc' }
orderBy: { displayOrder: 'asc' },
});
}
async getAllDocumentTypeWithCountryStateCity() {
const [documentDetails, countryDetails, stateDetails, companyTypeDetails] =
await this.prisma.$transaction([