diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ca8a446..56b18b8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -861,7 +861,7 @@ model Activities { frequency Frequencies? @relation(fields: [frequenciesXid], references: [id], onDelete: Restrict) activityRefNumber String? @map("activity_ref_number") @db.VarChar(30) activityTitle String? @map("activity_title") @db.VarChar(30) - activityDescription String? @map("activity_description") @db.VarChar(80) + activityDescription String? @map("activity_description") @db.VarChar(255) checkInLat Float? @map("check_in_lat") checkInLong Float? @map("check_in_long") checkInAddress String? @map("check_in_address") @db.VarChar(150) @@ -913,7 +913,6 @@ model Activities { ActivityEligibility ActivityEligibility[] ActivitySuggestions ActivitySuggestions[] ActivityAmDetails ActivityAmDetails[] - ActivityVenueArtifacts ActivityVenueArtifacts[] ActivityPQQheader ActivityPQQheader[] ActivityAllowedEntry ActivityAllowedEntry[] ActivityFoodCost ActivityFoodCost[] @@ -936,7 +935,7 @@ model ActivityOtherDetails { id Int @id @default(autoincrement()) activityXid Int @map("activity_xid") activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) - exclusiveNotes String? @map("exclusive_notes") @db.VarChar(50) + exclusiveNotes String? @map("exclusive_notes") @db.VarChar(500) dosNotes String? @map("dos_notes") @db.VarChar(200) dontsNotes String? @map("donts_notes") @db.VarChar(200) tipsNotes String? @map("tips_notes") @db.VarChar(100) @@ -1003,6 +1002,7 @@ model ActivityVenues { ScheduleHeader ScheduleHeader[] ItineraryActivities ItineraryActivities[] ActivityPrices ActivityPrices[] // <-- Added opposite relation + ActivityVenueArtifacts ActivityVenueArtifacts[] // <-- Added opposite relation @@map("activity_venues") @@schema("act") @@ -1143,7 +1143,7 @@ model ActivityPriceTaxes { model ActivityVenueArtifacts { id Int @id @default(autoincrement()) activityVenueXid Int @map("activity_venue_xid") - activityVenue Activities @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade) + activityVenue ActivityVenues @relation(fields: [activityVenueXid], references: [id], onDelete: Cascade) mediaType String @map("media_type") @db.VarChar(30) mediaFileName String @map("media_file_name") @db.VarChar(400) isActive Boolean @default(true) @map("is_active") diff --git a/src/modules/host/dto/createActivity.schema.ts b/src/modules/host/dto/createActivity.schema.ts index 5032530..98d2874 100644 --- a/src/modules/host/dto/createActivity.schema.ts +++ b/src/modules/host/dto/createActivity.schema.ts @@ -2,12 +2,12 @@ import { z } from 'zod'; /* ================= MEDIA ================= */ export const MediaDto = z.object({ - mediaType: z.string().optional(), - mediaFileName: z.string(), + mediaType: z.string().optional(), // "image/jpeg", "video/mp4", etc. + mediaFileName: z.string(), // S3 file URL }); /* ================= PRICE ================= - * ❌ NO TAX HERE (tax is root-level only) + * ❌ No tax info here; root-level only */ export const PriceDto = z.object({ noOfSession: z.number().int().optional().default(1), @@ -15,7 +15,7 @@ export const PriceDto = z.object({ sessionValidity: z.number().int().optional().default(0), sessionValidityFrequency: z.string().optional().default('Days'), basePrice: z.number().int().optional().default(0), - sellPrice: z.number().int(), // REQUIRED + sellPrice: z.number().int(), // required }); /* ================= VENUE ================= */ @@ -27,6 +27,11 @@ export const VenueDto = z.object({ minPeopleRequired: z.number().int().nullable().optional(), minReqfullfilledBeforeMins: z.number().int().nullable().optional(), venueDescription: z.string().optional(), + + // ✅ new: media per venue (for ActivityVenueArtifacts) + media: z.array(MediaDto).optional().default([]), + + // price list per venue prices: z.array(PriceDto).optional().default([]), }); @@ -69,6 +74,7 @@ export const EligibilityDto = z.object({ isHeightRestriction: z.boolean().optional().default(false), heightRestrictionName: z.string().nullable().optional(), heightEntered: z.number().int().nullable().optional(), + heightIn: z.string().nullable().optional(), minHeight: z.number().int().nullable().optional(), maxHeight: z.number().int().nullable().optional(), }); @@ -127,7 +133,7 @@ export const CreateActivityDto = z.object({ cancellationAvailable: z.boolean().optional().default(false), - /* MONEY */ + /* MONEY / CURRENCY */ currencyXid: z.number().int().nullable().optional(), sustainabilityScore: z.number().int().nullable().optional(), safetyScore: z.number().int().nullable().optional(), @@ -136,21 +142,19 @@ export const CreateActivityDto = z.object({ /* 🔥 ROOT-LEVEL TAX (SINGLE SOURCE OF TRUTH) */ taxXids: z.array(z.number().int()).optional().default([]), - /* ARRAYS */ - media: z.array(MediaDto).optional().default([]), - venues: z.array(VenueDto).optional().default([]), + /* 🔥 MEDIA ARRAYS */ + media: z.array(MediaDto).optional().default([]), // Activity-level media + venues: z.array(VenueDto).optional().default([]), // Each venue’s media + prices + /* RELATION ARRAYS */ foodTypeIds: z.array(z.number().int()).optional().default([]), cuisineIds: z.array(z.number().int()).optional().default([]), - pickupTransports: z.array(PickupTransportDto).optional().default([]), - - /* 🔥 NAVIGATION = IDs ONLY */ navigationModes: z.array(z.number().int()).optional().default([]), - equipments: z.array(EquipmentDto).optional().default([]), amenitiesIds: z.array(z.number().int()).optional().default([]), + /* EXTRA OBJECTS */ eligibility: EligibilityDto.optional(), otherDetails: OtherDetailsDto.optional(), }); diff --git a/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts b/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts index e6edb29..cd77d0e 100644 --- a/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts +++ b/src/modules/host/handlers/Activity_Hub/OnBoarding/CreateNewActivity.ts @@ -22,7 +22,6 @@ function getExtensionFromMime(mimeType: string) { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp', - // ✅ Common video formats 'video/mp4': 'mp4', 'video/quicktime': 'mov', 'video/x-msvideo': 'avi', @@ -141,13 +140,21 @@ export const handler = safeHandler( 'currencyXid', 'energyLevelXid', 'activityDurationMins', + 'activityTypeXid', + 'frequenciesXid', 'trainerTotalAmount', 'pickupDropTotalPrice', 'navigationModeTotalPrice', + 'sustainabilityScore', + 'safetyScore', + 'checkInLat', + 'checkInLong', + 'checkOutLat', + 'checkOutLong', ]; for (const key of numberKeys) { - if (activityPayload[key] !== undefined && activityPayload[key] !== null) { + if (activityPayload[key] !== undefined && activityPayload[key] !== null && activityPayload[key] !== '') { activityPayload[key] = Number(activityPayload[key]); } } @@ -175,17 +182,13 @@ export const handler = safeHandler( if (activityPayload[key] === 'false') activityPayload[key] = false; } - /* 8️⃣ UPLOAD MEDIA */ - const uploadedMedia: Array<{ mediaType?: string; mediaFileName: string }> = - []; + /* 8️⃣ UPLOAD ACTIVITY-LEVEL MEDIA (images/videos) */ + const uploadedActivityMedia: Array<{ mediaType?: string; mediaFileName: string }> = []; - // ✅ Accept both images and videos under multipart fields `images` or `videos` for (const file of files.filter( - (f) => f.fieldName === 'images' || f.fieldName === 'videos', + (f) => f.fieldName === 'activityImages' || f.fieldName === 'activityVideos', )) { - const ext = getExtensionFromMime(file.mimeType); - - const s3Key = `ActivityOnboarding/Activity_${activityPayload.activityXid}/Artifacts/${file.fileName}`; + const s3Key = `ActivityOnboarding/Activity_${activityPayload.activityXid}/Media/${Date.now()}_${file.fileName}`; if (s3Key.length > 900) { throw new ApiError(400, 'Generated S3 key too long'); @@ -201,20 +204,72 @@ export const handler = safeHandler( }) .promise(); - uploadedMedia.push({ + uploadedActivityMedia.push({ mediaType: file.mimeType, mediaFileName: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`, }); } - /* 🔥 MERGE MEDIA (DO NOT OVERWRITE) */ + /* 🔥 MERGE ACTIVITY MEDIA */ const existingMedia = Array.isArray(activityPayload.media) ? activityPayload.media : []; + activityPayload.media = [...existingMedia, ...uploadedActivityMedia]; - activityPayload.media = [...existingMedia, ...uploadedMedia]; + /* 9️⃣ PROCESS VENUE MEDIA UPLOADS */ + // Group venue files by index: venueImages[0], venueImages[1], etc. + const venueFilesMap: Map> = new Map(); - /* 9️⃣ VALIDATION */ + for (const file of files) { + // Match patterns like: venueImages[0], venueVideos[1], etc. + const match = file.fieldName.match(/^venue(Images|Videos)\[(\d+)\]$/); + if (match) { + const venueIndex = parseInt(match[2], 10); + if (!venueFilesMap.has(venueIndex)) { + venueFilesMap.set(venueIndex, []); + } + venueFilesMap.get(venueIndex)!.push(file); + } + } + + // Upload venue files and attach to corresponding venues + if (Array.isArray(activityPayload.venues)) { + for (let i = 0; i < activityPayload.venues.length; i++) { + const venue = activityPayload.venues[i]; + const venueFiles = venueFilesMap.get(i) || []; + + const uploadedVenueMedia: Array<{ mediaType?: string; mediaFileName: string }> = []; + + for (const file of venueFiles) { + const s3Key = `ActivityOnboarding/Activity_${activityPayload.activityXid}/Venue_${i}/Media/${Date.now()}_${file.fileName}`; + + if (s3Key.length > 900) { + throw new ApiError(400, 'Generated S3 key too long for venue media'); + } + + await s3 + .upload({ + Bucket: config.aws.bucketName, + Key: s3Key, + Body: file.buffer, + ContentType: file.mimeType, + ACL: 'private', + }) + .promise(); + + uploadedVenueMedia.push({ + mediaType: file.mimeType, + mediaFileName: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`, + }); + } + + // Merge with existing venue media + const existingVenueMedia = Array.isArray(venue.media) ? venue.media : []; + venue.media = [...existingVenueMedia, ...uploadedVenueMedia]; + } + } + + /* 🔟 VALIDATION */ let parsedDto: CreateActivityInput; if (!isDraft) { @@ -230,14 +285,14 @@ export const handler = safeHandler( parsedDto = activityPayload as CreateActivityInput; } - /* 🔟 SAVE ACTIVITY */ + /* 1️⃣1️⃣ SAVE ACTIVITY */ const createdActivity = await hostService.createOrUpdateActivity( userInfo.id, parsedDto, isDraft, ); - /* 1️⃣1️⃣ RESPONSE */ + /* 1️⃣2️⃣ RESPONSE */ return { statusCode: 200, headers: { @@ -253,4 +308,4 @@ export const handler = safeHandler( }), }; }, -); +); \ No newline at end of file diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index acbd213..20a9266 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -14,7 +14,9 @@ import { hostCompanyDetailsSchema } from '../../../common/utils/validation/host/ import { ACTIVITY_AM_DISPLAY_STATUS, ACTIVITY_AM_INTERNAL_STATUS, - ACTIVITY_DISPLAY_STATUS, ACTIVITY_INTERNAL_STATUS, HOST_STATUS_DISPLAY, + ACTIVITY_DISPLAY_STATUS, + ACTIVITY_INTERNAL_STATUS, + HOST_STATUS_DISPLAY, HOST_STATUS_INTERNAL, STEPPER, } from '../../../common/utils/constants/host.constant'; @@ -40,7 +42,6 @@ function sanitizeDocumentName(name?: string) { .substring(0, 100); } - type HostCompanyDetailsInput = z.infer; // Document input after S3 upload (with S3 URL as filePath) @@ -61,7 +62,7 @@ export async function generateActivityRefNumber(tx: any) { const nextId = lastrecord ? lastrecord.id + 1 : 1; - return `ACT-${String(nextId).padStart(6, '0')}`;; + return `ACT-${String(nextId).padStart(6, '0')}`; } function round2(value: number) { @@ -75,13 +76,21 @@ function computeBasePriceAndTaxes( if (!taxes?.length) { return { basePrice: round2(sellPrice), - taxDetails: [] as Array<{ taxXid: number; taxPer: number; taxAmount: number }>, + taxDetails: [] as Array<{ + taxXid: number; + taxPer: number; + taxAmount: number; + }>, }; } - const totalTaxPer = taxes.reduce((sum, t) => sum + (Number(t.taxPer) || 0), 0); + const totalTaxPer = taxes.reduce( + (sum, t) => sum + (Number(t.taxPer) || 0), + 0, + ); const denominator = 1 + totalTaxPer / 100; - const basePrice = denominator > 0 ? round2(sellPrice / denominator) : round2(sellPrice); + const basePrice = + denominator > 0 ? round2(sellPrice / denominator) : round2(sellPrice); const taxDetails = taxes.map((t) => ({ taxXid: t.id, @@ -96,7 +105,7 @@ const bucket = config.aws.bucketName; @Injectable() export class HostService { - constructor(private prisma: PrismaClient) { } + constructor(private prisma: PrismaClient) {} async createHost(data: CreateHostDto) { return this.prisma.user.create({ data }); @@ -115,7 +124,7 @@ export class HostService { const user = await this.prisma.user.findUnique({ where: { id: user_xid }, select: { id: true, emailAddress: true }, - }) + }); return { host, user }; } @@ -131,10 +140,10 @@ export class HostService { filePath: true, documentName: true, documentTypeXid: true, - documentType: true - } - } - } + documentType: true, + }, + }, + }, }, HostBankDetails: true, HostDocuments: { @@ -151,7 +160,7 @@ export class HostService { mobileNumber: true, profileImage: true, userRefNumber: true, - } + }, }, user: { select: { @@ -163,7 +172,7 @@ export class HostService { profileImage: true, userStatus: true, userRefNumber: true, - } + }, }, companyTypes: { select: { @@ -182,7 +191,7 @@ export class HostService { title: true, comments: true, isparent: true, - } + }, }, countries: true, currencies: true, @@ -198,7 +207,6 @@ export class HostService { } if (host.HostDocuments?.length) { - for (const doc of host.HostDocuments) { if (doc.filePath) { const filePath = doc.filePath; @@ -213,8 +221,8 @@ export class HostService { } } if (host.user?.profileImage) { - const key = host.user.profileImage.startsWith("http") - ? host.user.profileImage.split(".com/")[1] + const key = host.user.profileImage.startsWith('http') + ? host.user.profileImage.split('.com/')[1] : host.user.profileImage; host.user.profileImage = await getPresignedUrl(bucket, key); @@ -241,8 +249,8 @@ export class HostService { // Parent company logo if (parent.logoPath) { - const key = parent.logoPath.startsWith("http") - ? parent.logoPath.split(".com/")[1] + const key = parent.logoPath.startsWith('http') + ? parent.logoPath.split('.com/')[1] : parent.logoPath; parent.logoPath = await getPresignedUrl(bucket, key); @@ -252,8 +260,8 @@ export class HostService { if (parent.HostParenetDocuments?.length) { for (const doc of parent.HostParenetDocuments) { if (doc.filePath) { - const key = doc.filePath.startsWith("http") - ? doc.filePath.split(".com/")[1] + const key = doc.filePath.startsWith('http') + ? doc.filePath.split('.com/')[1] : doc.filePath; (doc as any).presignedUrl = await getPresignedUrl(bucket, key); @@ -337,15 +345,18 @@ export class HostService { emailAddress: true, mobileNumber: true, userPassword: true, - userStatus: true - } + userStatus: true, + }, }); if (!existingUser) { throw new ApiError(404, 'User not found'); } if (existingUser.userStatus == USER_STATUS.REJECTED) { - throw new ApiError(403, "You are not allowed to login. Please contact minglar admin.") + throw new ApiError( + 403, + 'You are not allowed to login. Please contact minglar admin.', + ); } if (existingUser.roleXid !== 4) { @@ -429,7 +440,7 @@ export class HostService { if (existingAccount) { throw new ApiError( 400, - 'Host account with this account number already exists.' + 'Host account with this account number already exists.', ); } const addedPaymentDetails = await tx.hostBankDetails.create({ @@ -444,16 +455,20 @@ export class HostService { where: { id: data.hostXid }, data: { stepper: STEPPER.BANK_DETAILS_UPDATED, - currencyXid: data.currencyXid + currencyXid: data.currencyXid, }, }); }); } - async getAllHostActivity(search?: string, user_xid?: number, paginationOptions?: { page: number; limit: number; skip: number }) { + async getAllHostActivity( + search?: string, + user_xid?: number, + paginationOptions?: { page: number; limit: number; skip: number }, + ) { const hostDetails = await this.prisma.hostHeader.findFirst({ - where: { userXid: user_xid, isActive: true } - }) + where: { userXid: user_xid, isActive: true }, + }); const whereClause: any = { isActive: true, @@ -464,7 +479,7 @@ export class HostService { data: [], total: 0, page: paginationOptions?.page || 1, - limit: paginationOptions?.limit || 10 + limit: paginationOptions?.limit || 10, }; } @@ -477,8 +492,8 @@ export class HostService { { activityTitle: { contains: term, mode: 'insensitive' } }, { activityType: { - activityTypeName: { contains: term, mode: 'insensitive' } - } + activityTypeName: { contains: term, mode: 'insensitive' }, + }, }, ]; } @@ -500,8 +515,8 @@ export class HostService { frequency: { select: { id: true, - frequencyName: true - } + frequencyName: true, + }, }, ActivityAmDetails: { select: { @@ -524,10 +539,10 @@ export class HostService { interests: { select: { id: true, - interestName: true - } - } - } + interestName: true, + }, + }, + }, }, }, skip: paginationOptions?.skip || 0, @@ -542,8 +557,8 @@ export class HostService { const am = activity.ActivityAmDetails?.[0]?.accountManager; if (am?.profileImage) { - const key = am.profileImage.startsWith("http") - ? am.profileImage.split(".com/")[1] + const key = am.profileImage.startsWith('http') + ? am.profileImage.split('.com/')[1] : am.profileImage; const presignedUrl = await getPresignedUrl(bucket, key); @@ -555,11 +570,13 @@ export class HostService { } } - const { paginationService } = require('@/common/utils/pagination/pagination.service'); + const { + paginationService, + } = require('@/common/utils/pagination/pagination.service'); return paginationService.createPaginatedResponse( hostAllActivities, totalCount, - paginationOptions || { page: 1, limit: 10, skip: 0 } + paginationOptions || { page: 1, limit: 10, skip: 0 }, ); } @@ -596,8 +613,8 @@ export class HostService { id: true, activityPqqHeaderXid: true, mediaFileName: true, - mediaType: true - } + mediaType: true, + }, }, ActivityPQQSuggestions: { where: { isActive: true, isReviewed: false }, @@ -605,13 +622,12 @@ export class HostService { id: true, title: true, comments: true, - } + }, }, }, }); if (detailsOfQuestion.ActivityPQQSupportings?.length) { - for (const doc of detailsOfQuestion.ActivityPQQSupportings) { if (doc.mediaFileName) { const filePath = doc.mediaFileName; @@ -635,8 +651,8 @@ export class HostService { activityXid: activity_xid, isActive: true, pqqAnswerXid: { - not: null - } + not: null, + }, }, select: { pqqQuestionXid: true, @@ -669,10 +685,9 @@ export class HostService { include: { HostParenetDocuments: true }, }); - return parents.flatMap(p => p.HostParenetDocuments); + return parents.flatMap((p) => p.HostParenetDocuments); } - async deleteExistingParentRecords(userId: number) { const host = await this.prisma.hostHeader.findFirst({ where: { userXid: userId }, @@ -688,7 +703,7 @@ export class HostService { if (!parents.length) return; - const parentIds = parents.map(p => p.id); + const parentIds = parents.map((p) => p.id); // 1️⃣ Delete documents first await this.prisma.hostParenetDocuments.deleteMany({ @@ -701,7 +716,6 @@ export class HostService { }); } - async addOrUpdateCompanyDetails( user_xid: number, companyData: HostCompanyDetailsInput, @@ -716,7 +730,7 @@ export class HostService { where: { userXid: user_xid }, include: { hostParent: true }, }); - console.log(existingHostCompany, "-: Existing hai") + console.log(existingHostCompany, '-: Existing hai'); let existingParentCompany; @@ -725,9 +739,9 @@ export class HostService { where: { hostXid: existingHostCompany.id }, select: { id: true, - logoPath: true - } - }) + logoPath: true, + }, + }); } let hostStatusInternal; @@ -745,7 +759,8 @@ export class HostService { // CASE 1: Host was asked to update AND is submitting final if ( existingHostCompany && - existingHostCompany.hostStatusInternal === HOST_STATUS_INTERNAL.HOST_TO_UPDATE && + existingHostCompany.hostStatusInternal === + HOST_STATUS_INTERNAL.HOST_TO_UPDATE && !isDraft ) { hostStatusInternal = HOST_STATUS_INTERNAL.HOST_SUBMITTED; @@ -757,7 +772,8 @@ export class HostService { // CASE 2: Host was asked to update BUT saving draft else if ( existingHostCompany && - existingHostCompany.hostStatusInternal === HOST_STATUS_INTERNAL.HOST_TO_UPDATE && + existingHostCompany.hostStatusInternal === + HOST_STATUS_INTERNAL.HOST_TO_UPDATE && isDraft ) { // keep original @@ -792,14 +808,17 @@ export class HostService { // ------------------------------------------------------- if (!existingHostCompany) { if (!isDraft) { - console.log("First time direct final submit.") + console.log('First time direct final submit.'); const existingByPan = await tx.hostHeader.findFirst({ where: { panNumber: companyData.panNumber }, }); if (existingByPan) - throw new ApiError(400, 'Company already exists with this pan/bin number'); + throw new ApiError( + 400, + 'Company already exists with this pan/bin number', + ); } - console.log("First Time Aaya hai") + console.log('First Time Aaya hai'); const createdHost = await tx.hostHeader.create({ data: { @@ -807,9 +826,15 @@ export class HostService { companyName: companyData.companyName, address1: companyData.address1, address2: companyData.address2, - cities: companyData.cityXid ? { connect: { id: companyData.cityXid } } : undefined, - states: companyData.stateXid ? { connect: { id: companyData.stateXid } } : undefined, - countries: companyData.countryXid ? { connect: { id: companyData.countryXid } } : undefined, + cities: companyData.cityXid + ? { connect: { id: companyData.cityXid } } + : undefined, + states: companyData.stateXid + ? { connect: { id: companyData.stateXid } } + : undefined, + countries: companyData.countryXid + ? { connect: { id: companyData.countryXid } } + : undefined, pinCode: companyData.pinCode, logoPath: companyData.logoPath || null, isSubsidairy: companyData.isSubsidairy, @@ -849,7 +874,7 @@ export class HostService { // parent create if (companyData.isSubsidairy && parentCompanyData) { - console.log("Parent ke saath aaya hai first time.") + console.log('Parent ke saath aaya hai first time.'); const createdParent = await tx.hostParent.create({ data: { host: { connect: { id: createdHost.id } }, @@ -857,17 +882,23 @@ export class HostService { address1: parentCompanyData.address1 || null, address2: parentCompanyData.address2 || null, // Safely handle city connection - only connect if valid ID exists - cities: parentCompanyData?.cityXid && !isNaN(Number(parentCompanyData.cityXid)) - ? { connect: { id: Number(parentCompanyData.cityXid) } } - : undefined, + cities: + parentCompanyData?.cityXid && + !isNaN(Number(parentCompanyData.cityXid)) + ? { connect: { id: Number(parentCompanyData.cityXid) } } + : undefined, - states: parentCompanyData?.stateXid && !isNaN(Number(parentCompanyData.stateXid)) - ? { connect: { id: Number(parentCompanyData.stateXid) } } - : undefined, + states: + parentCompanyData?.stateXid && + !isNaN(Number(parentCompanyData.stateXid)) + ? { connect: { id: Number(parentCompanyData.stateXid) } } + : undefined, - countries: parentCompanyData?.countryXid && !isNaN(Number(parentCompanyData.countryXid)) - ? { connect: { id: Number(parentCompanyData.countryXid) } } - : undefined, + countries: + parentCompanyData?.countryXid && + !isNaN(Number(parentCompanyData.countryXid)) + ? { connect: { id: Number(parentCompanyData.countryXid) } } + : undefined, pinCode: parentCompanyData.pinCode || null, logoPath: parentCompanyData.logoPath || null, registrationNumber: parentCompanyData.registrationNumber || null, @@ -922,19 +953,22 @@ export class HostService { address1: companyData.address1, address2: companyData.address2, // Safely handle city connection - only connect if valid ID exists - cities: companyData.cityXid && !isNaN(Number(companyData.cityXid)) - ? { connect: { id: Number(companyData.cityXid) } } - : undefined, // Don't change if not provided + cities: + companyData.cityXid && !isNaN(Number(companyData.cityXid)) + ? { connect: { id: Number(companyData.cityXid) } } + : undefined, // Don't change if not provided // Same for state - states: companyData.stateXid && !isNaN(Number(companyData.stateXid)) - ? { connect: { id: Number(companyData.stateXid) } } - : undefined, + states: + companyData.stateXid && !isNaN(Number(companyData.stateXid)) + ? { connect: { id: Number(companyData.stateXid) } } + : undefined, // Same for country - countries: companyData.countryXid && !isNaN(Number(companyData.countryXid)) - ? { connect: { id: Number(companyData.countryXid) } } - : undefined, + countries: + companyData.countryXid && !isNaN(Number(companyData.countryXid)) + ? { connect: { id: Number(companyData.countryXid) } } + : undefined, pinCode: companyData.pinCode, logoPath: companyData.logoPath || existingHostCompany.logoPath, isSubsidairy: companyData.isSubsidairy, @@ -977,7 +1011,9 @@ export class HostService { where: { id: existingDoc.id }, data: { filePath: doc.filePath, - documentName: sanitizeDocumentName(doc.documentName) || existingDoc.documentName, + documentName: + sanitizeDocumentName(doc.documentName) || + existingDoc.documentName, }, }); } else { @@ -996,29 +1032,40 @@ export class HostService { // parent logic untouched if (companyData.isSubsidairy) { const parentRecords = existingHostCompany.hostParent; - const parentRecord = Array.isArray(parentRecords) ? parentRecords[0] : parentRecords; - console.log("Yaha aaya update in the apretn me") + const parentRecord = Array.isArray(parentRecords) + ? parentRecords[0] + : parentRecords; + console.log('Yaha aaya update in the apretn me'); if (!parentRecord) { - console.log("Parent record nahi mila to create kar raha hai.") + console.log('Parent record nahi mila to create kar raha hai.'); const createdParent = await tx.hostParent.create({ data: { host: { connect: { id: updatedHost.id } }, companyName: parentCompanyData.companyName || null, address1: parentCompanyData.address1 || null, address2: parentCompanyData.address2 || null, - cities: parentCompanyData?.cityXid && !isNaN(Number(parentCompanyData.cityXid)) - ? { connect: { id: Number(parentCompanyData.cityXid) } } - : undefined, + cities: + parentCompanyData?.cityXid && + !isNaN(Number(parentCompanyData.cityXid)) + ? { connect: { id: Number(parentCompanyData.cityXid) } } + : undefined, - states: parentCompanyData?.stateXid && !isNaN(Number(parentCompanyData.stateXid)) - ? { connect: { id: Number(parentCompanyData.stateXid) } } - : undefined, + states: + parentCompanyData?.stateXid && + !isNaN(Number(parentCompanyData.stateXid)) + ? { connect: { id: Number(parentCompanyData.stateXid) } } + : undefined, - countries: parentCompanyData?.countryXid && !isNaN(Number(parentCompanyData.countryXid)) - ? { connect: { id: Number(parentCompanyData.countryXid) } } - : undefined, + countries: + parentCompanyData?.countryXid && + !isNaN(Number(parentCompanyData.countryXid)) + ? { connect: { id: Number(parentCompanyData.countryXid) } } + : undefined, pinCode: parentCompanyData.pinCode || null, - logoPath: parentCompanyData?.logoPath || existingParentCompany?.logoPath || null, + logoPath: + parentCompanyData?.logoPath || + existingParentCompany?.logoPath || + null, registrationNumber: parentCompanyData.registrationNumber || null, panNumber: parentCompanyData.panNumber || null, gstNumber: parentCompanyData.gstNumber || null, @@ -1055,19 +1102,28 @@ export class HostService { companyName: parentCompanyData.companyName || null, address1: parentCompanyData.address1 || null, address2: parentCompanyData.address2 || null, - cities: parentCompanyData?.cityXid && !isNaN(Number(parentCompanyData.cityXid)) - ? { connect: { id: Number(parentCompanyData.cityXid) } } - : undefined, + cities: + parentCompanyData?.cityXid && + !isNaN(Number(parentCompanyData.cityXid)) + ? { connect: { id: Number(parentCompanyData.cityXid) } } + : undefined, - states: parentCompanyData?.stateXid && !isNaN(Number(parentCompanyData.stateXid)) - ? { connect: { id: Number(parentCompanyData.stateXid) } } - : undefined, + states: + parentCompanyData?.stateXid && + !isNaN(Number(parentCompanyData.stateXid)) + ? { connect: { id: Number(parentCompanyData.stateXid) } } + : undefined, - countries: parentCompanyData?.countryXid && !isNaN(Number(parentCompanyData.countryXid)) - ? { connect: { id: Number(parentCompanyData.countryXid) } } - : undefined, + countries: + parentCompanyData?.countryXid && + !isNaN(Number(parentCompanyData.countryXid)) + ? { connect: { id: Number(parentCompanyData.countryXid) } } + : undefined, pinCode: parentCompanyData.pinCode || null, - logoPath: parentCompanyData?.logoPath || existingParentCompany?.logoPath || null, + logoPath: + parentCompanyData?.logoPath || + existingParentCompany?.logoPath || + null, registrationNumber: parentCompanyData.registrationNumber || null, panNumber: parentCompanyData.panNumber || null, gstNumber: parentCompanyData.gstNumber || null, @@ -1087,19 +1143,23 @@ export class HostService { if (parentDocuments?.length) { for (const doc of parentDocuments) { - const existingParentDoc = await tx.hostParenetDocuments.findFirst({ - where: { - hostParentXid: parentRecord.id, - documentTypeXid: doc.documentTypeXid, + const existingParentDoc = await tx.hostParenetDocuments.findFirst( + { + where: { + hostParentXid: parentRecord.id, + documentTypeXid: doc.documentTypeXid, + }, }, - }); + ); if (existingParentDoc) { await tx.hostParenetDocuments.update({ where: { id: existingParentDoc.id }, data: { filePath: doc.filePath, - documentName: sanitizeDocumentName(doc.documentName) || existingParentDoc.documentName, + documentName: + sanitizeDocumentName(doc.documentName) || + existingParentDoc.documentName, }, }); } else { @@ -1116,13 +1176,17 @@ export class HostService { } } } else { - console.log("Last ke else block me aaya hai") + console.log('Last ke else block me aaya hai'); const previousParent = existingHostCompany.hostParent; let prevParentId = null; if (Array.isArray(previousParent) && previousParent.length) { prevParentId = previousParent[0].id; - } else if (previousParent && typeof previousParent === 'object' && 'id' in previousParent) { + } else if ( + previousParent && + typeof previousParent === 'object' && + 'id' in previousParent + ) { prevParentId = previousParent.id; } @@ -1160,8 +1224,6 @@ export class HostService { }); } - - async getSuggestionDetails(user_xid: number) { const hostDetails = await this.prisma.hostHeader.findFirst({ where: { userXid: user_xid, isActive: true }, @@ -1171,7 +1233,7 @@ export class HostService { id: true, emailAddress: true, firstName: true, - userRefNumber: true + userRefNumber: true, }, }, accountManager: { @@ -1331,7 +1393,9 @@ export class HostService { // Overall percent const overallPercentage = - totalMaxPoints > 0 ? round2((totalUserPoints / totalMaxPoints) * 100) : 0; + totalMaxPoints > 0 + ? round2((totalUserPoints / totalMaxPoints) * 100) + : 0; // ---------- 🔥 ONLY FIRST 2 CATEGORIES ---------- const categoryArray = Object.values(categories); @@ -1351,14 +1415,14 @@ export class HostService { await this.prisma.activities.update({ where: { - id: activityXid + id: activityXid, }, data: { totalScore: round2(overallPercentage), sustainabilityScore: round2(categoryWise.Sustainability), safetyScore: round2(categoryWise.Safety), - } - }) + }, + }); // Return final score object return { @@ -1384,10 +1448,7 @@ export class HostService { }); } - async findHeaderByCompositeKey( - activityXid: number, - pqqQuestionXid: number, - ) { + async findHeaderByCompositeKey(activityXid: number, pqqQuestionXid: number) { return await this.prisma.activityPQQheader.findFirst({ where: { activityXid, @@ -1396,7 +1457,11 @@ export class HostService { }); } - async updateHeader(headerId: number, pqqAnswerXid: number, comments?: string | null) { + async updateHeader( + headerId: number, + pqqAnswerXid: number, + comments?: string | null, + ) { return await this.prisma.activityPQQheader.update({ where: { id: headerId, @@ -1441,15 +1506,17 @@ export class HostService { activityDisplayStatus: true, activityInternalStatus: true, amInternalStatus: true, - amDisplayStatus: true - } - }) + amDisplayStatus: true, + }, + }); if (!activity) { - throw new ApiError(404, "Activity not found") + throw new ApiError(404, 'Activity not found'); } - if (activity.activityInternalStatus == ACTIVITY_INTERNAL_STATUS.PQ_TO_UPDATE) { + if ( + activity.activityInternalStatus == ACTIVITY_INTERNAL_STATUS.PQ_TO_UPDATE + ) { return await this.prisma.$transaction(async (tx) => { await this.prisma.activities.update({ where: { id: activity_xid }, @@ -1457,9 +1524,9 @@ export class HostService { activityInternalStatus: ACTIVITY_INTERNAL_STATUS.PQ_SUBMITTED, activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.PQ_IN_REVIEW, amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.PQ_TO_REVIEW, - amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.REVISED - } - }) + amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.REVISED, + }, + }); await tx.activityTrack.create({ data: { @@ -1468,10 +1535,10 @@ export class HostService { trackStatus: ACTIVITY_TRACK_STATUS.PQ_SUBMITTED, updatedByXid: user_xid, updatedByRole: ROLE_NAME.HOST, - updatedOn: new Date() - } - }) - }) + updatedOn: new Date(), + }, + }); + }); } else { return await this.prisma.$transaction(async (tx) => { await this.prisma.activities.update({ @@ -1480,9 +1547,9 @@ export class HostService { activityInternalStatus: ACTIVITY_INTERNAL_STATUS.PQ_SUBMITTED, activityDisplayStatus: ACTIVITY_DISPLAY_STATUS.PQ_IN_REVIEW, amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.PQ_TO_REVIEW, - amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.NEW - } - }) + amDisplayStatus: ACTIVITY_AM_DISPLAY_STATUS.NEW, + }, + }); await tx.activityTrack.create({ data: { @@ -1491,13 +1558,12 @@ export class HostService { trackStatus: ACTIVITY_TRACK_STATUS.PQ_SUBMITTED, updatedByXid: user_xid, updatedByRole: ROLE_NAME.HOST, - updatedOn: new Date() - } - }) - }) + updatedOn: new Date(), + }, + }); + }); } - }) - + }); } async updateSupportingFile( @@ -1525,20 +1591,24 @@ export class HostService { }); } - async markPQQSuggestionReviewed(user_xid: number, activityPqqHeaderXid: number, activityPQQSuggestionId: number) { + async markPQQSuggestionReviewed( + user_xid: number, + activityPqqHeaderXid: number, + activityPQQSuggestionId: number, + ) { return await this.prisma.activityPQQSuggestions.update({ where: { id: activityPQQSuggestionId, activityPqqHeaderXid: activityPqqHeaderXid, isActive: true, - isReviewed: false + isReviewed: false, }, data: { isReviewed: true, reviewedByXid: user_xid, - reviewedOn: new Date() - } - }) + reviewedOn: new Date(), + }, + }); } async getAllPQQQuesAndSubmittedAns(activity_xid: number) { @@ -1565,11 +1635,11 @@ export class HostService { id: true, categoryName: true, displayOrder: true, - } - } - } - } - } + }, + }, + }, + }, + }, }, ActivityPQQSuggestions: { select: { @@ -1579,22 +1649,22 @@ export class HostService { isReviewed: true, reviewedBy: true, reviewedOn: true, - } + }, }, pqqAnswers: { select: { id: true, displayOrder: true, answerName: true, - answerPoints: true - } + answerPoints: true, + }, }, ActivityPQQSupportings: { select: { id: true, mediaFileName: true, mediaType: true, - } + }, }, }, }); @@ -1637,9 +1707,7 @@ export class HostService { activityTypeXid: number, frequenciesXid?: number, ) { - return await this.prisma.$transaction(async (tx) => { - // Fetch host const host = await tx.hostHeader.findFirst({ where: { userXid: userId, isActive: true }, @@ -1684,10 +1752,9 @@ export class HostService { async createActivityAndAllQuestionsEntry( userId: number, activityTypeXid: number, - frequenciesXid: number + frequenciesXid: number, ) { return await this.prisma.$transaction(async (tx) => { - const host = await tx.hostHeader.findFirst({ where: { userXid: userId, isActive: true }, }); @@ -1790,10 +1857,10 @@ export class HostService { select: { id: true, categoryName: true, - displayOrder: true - } - } - } + displayOrder: true, + }, + }, + }, }, // 🔥 ALL ANSWER OPTIONS FOR THIS QUESTION @@ -1803,11 +1870,11 @@ export class HostService { id: true, answerName: true, answerPoints: true, - displayOrder: true + displayOrder: true, }, - orderBy: { displayOrder: "asc" } - } - } + orderBy: { displayOrder: 'asc' }, + }, + }, }, ActivityPQQSuggestions: { where: { isActive: true }, @@ -1815,19 +1882,19 @@ export class HostService { id: true, title: true, comments: true, - activityPqqHeaderXid: true - } + activityPqqHeaderXid: true, + }, }, ActivityPQQSupportings: { where: { isActive: true }, select: { id: true, mediaType: true, - mediaFileName: true - } + mediaFileName: true, + }, }, }, - orderBy: { id: "asc" } + orderBy: { id: 'asc' }, }); // ---------------- GROUPING ------------------ @@ -1844,19 +1911,21 @@ export class HostService { id: cat.id, categoryName: cat.categoryName, displayOrder: cat.displayOrder, - pqqsubCategories: [] + pqqsubCategories: [], }; } const category = grouped[cat.id]; - let subCat = category.pqqsubCategories.find((s: any) => s.id === sub.id); + let subCat = category.pqqsubCategories.find( + (s: any) => s.id === sub.id, + ); if (!subCat) { subCat = { id: sub.id, subCategoryName: sub.subCategoryName, displayOrder: sub.displayOrder, - questions: [] + questions: [], }; category.pqqsubCategories.push(subCat); } @@ -1873,20 +1942,25 @@ export class HostService { }); } - const sortedCategories: any = Object.values(grouped) - .sort((a: any, b: any) => a.displayOrder - b.displayOrder); + const sortedCategories: any = Object.values(grouped).sort( + (a: any, b: any) => a.displayOrder - b.displayOrder, + ); for (const cat of sortedCategories) { - cat.pqqsubCategories.sort((a: any, b: any) => a.displayOrder - b.displayOrder); + cat.pqqsubCategories.sort( + (a: any, b: any) => a.displayOrder - b.displayOrder, + ); for (const sub of cat.pqqsubCategories) { - sub.questions.sort((a: any, b: any) => a.displayOrder - b.displayOrder); + sub.questions.sort( + (a: any, b: any) => a.displayOrder - b.displayOrder, + ); } } return { activity_xid: created.id, - sortedCategories + sortedCategories, }; }); } @@ -1896,7 +1970,7 @@ export class HostService { * This method will create Activities + ActivityOtherDetails + ActivitiesMedia + * ActivityVenues + ActivityPrices + ActivityFoodTypes + ActivityCuisine + * ActivityPickUpTransport/Details + ActivityNavigationModes + ActivityEquipments + - * ActivityAmenities + ActivityEligibility + * ActivityAmenities + ActivityEligibility */ async createOrUpdateActivity( userId: number, @@ -1981,10 +2055,7 @@ async createOrUpdateActivity( } if (v.isMinPeopleReqMandatory && !v.minPeopleRequired) { - throw new ApiError( - 400, - `venues[${idx}] min people requirement missing`, - ); + throw new ApiError(400, `venues[${idx}] min people requirement missing`); } if (!Array.isArray(v.prices) || !v.prices.length) { @@ -2038,7 +2109,7 @@ async createOrUpdateActivity( } /* -------------------------------- - * 3️⃣ STATUS DECISION (YOUR LOGIC) + * 3️⃣ STATUS DECISION * -------------------------------- */ let activityInternalStatus; let activityDisplayStatus; @@ -2051,43 +2122,27 @@ async createOrUpdateActivity( if (wasRejected) { if (isDraft) { - activityInternalStatus = - existingActivity.activityInternalStatus; - activityDisplayStatus = - existingActivity.activityDisplayStatus; - amInternalStatus = - existingActivity.amInternalStatus; - amDisplayStatus = - existingActivity.amDisplayStatus; + activityInternalStatus = existingActivity.activityInternalStatus; + activityDisplayStatus = existingActivity.activityDisplayStatus; + amInternalStatus = existingActivity.amInternalStatus; + amDisplayStatus = existingActivity.amDisplayStatus; } else { - activityInternalStatus = - ACTIVITY_INTERNAL_STATUS.ACTIVITY_SUBMITTED; - activityDisplayStatus = - ACTIVITY_DISPLAY_STATUS.ACTIVITY_IN_REVIEW; - amInternalStatus = - ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_TO_REVIEW; - amDisplayStatus = - ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_NEW; + activityInternalStatus = ACTIVITY_INTERNAL_STATUS.ACTIVITY_SUBMITTED; + activityDisplayStatus = ACTIVITY_DISPLAY_STATUS.ACTIVITY_IN_REVIEW; + amInternalStatus = ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_TO_REVIEW; + amDisplayStatus = ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_NEW; } } else { if (isDraft) { - activityInternalStatus = - ACTIVITY_INTERNAL_STATUS.ACTIVITY_DRAFT; - activityDisplayStatus = - ACTIVITY_DISPLAY_STATUS.ACTIVITY_DRAFT; - amInternalStatus = - ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_DRAFT; - amDisplayStatus = - ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_DRAFT; + activityInternalStatus = ACTIVITY_INTERNAL_STATUS.ACTIVITY_DRAFT; + activityDisplayStatus = ACTIVITY_DISPLAY_STATUS.ACTIVITY_DRAFT; + amInternalStatus = ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_DRAFT; + amDisplayStatus = ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_DRAFT; } else { - activityInternalStatus = - ACTIVITY_INTERNAL_STATUS.ACTIVITY_SUBMITTED; - activityDisplayStatus = - ACTIVITY_DISPLAY_STATUS.ACTIVITY_IN_REVIEW; - amInternalStatus = - ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_TO_REVIEW; - amDisplayStatus = - ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_NEW; + activityInternalStatus = ACTIVITY_INTERNAL_STATUS.ACTIVITY_SUBMITTED; + activityDisplayStatus = ACTIVITY_DISPLAY_STATUS.ACTIVITY_IN_REVIEW; + amInternalStatus = ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_TO_REVIEW; + amDisplayStatus = ACTIVITY_AM_DISPLAY_STATUS.ACTIVITY_NEW; } } @@ -2097,19 +2152,39 @@ async createOrUpdateActivity( const activity = await tx.activities.update({ where: { id: existingActivity.id }, data: { + activityTypeXid: payload.activityTypeXid ?? undefined, + frequenciesXid: payload.frequenciesXid ?? undefined, activityTitle: payload.activityTitle ?? undefined, activityDescription: payload.activityDescription ?? undefined, + + checkInLat: payload.checkInLat ?? undefined, + checkInLong: payload.checkInLong ?? undefined, + checkInAddress: payload.checkInAddress ?? undefined, + isCheckOutSame: toBool(payload.isCheckOutSame), + checkOutLat: payload.checkOutLat ?? undefined, + checkOutLong: payload.checkOutLong ?? undefined, + checkOutAddress: payload.checkOutAddress ?? undefined, + + energyLevelXid: payload.energyLevelXid ?? undefined, + activityDurationMins: payload.activityDurationMins ?? undefined, + currencyXid: payload.currencyXid ?? undefined, + sustainabilityScore: payload.sustainabilityScore ?? undefined, + safetyScore: payload.safetyScore ?? undefined, isInstantBooking: payload.isInstantBooking ?? undefined, foodAvailable: payload.foodAvailable, + foodIsChargeable: toBool(payload.foodIsChargeable), alcoholAvailable: payload.alcoholAvailable, trainerAvailable: payload.trainerAvailable, + trainerIsChargeable: toBool(payload.trainerIsChargeable), pickUpDropAvailable: payload.pickUpDropAvailable, + pickUpDropIsChargeable: toBool(payload.pickUpDropIsChargeable), inActivityAvailable: payload.inActivityAvailable, + inActivityIsChargeable: toBool(payload.inActivityIsChargeable), equipmentAvailable: payload.equipmentAvailable, + equipmentIsChargeable: toBool(payload.equipmentIsChargeable), cancellationAvailable: payload.cancellationAvailable, - isCheckOutSame: payload.isCheckOutSame, activityInternalStatus, activityDisplayStatus, @@ -2121,22 +2196,47 @@ async createOrUpdateActivity( const activityXid = activity.id; /* -------------------------------- - * 5️⃣ CLEAN OLD VENUES + * 5️⃣ CLEAN OLD ACTIVITY MEDIA + * -------------------------------- */ + await tx.activitiesMedia.deleteMany({ where: { activityXid } }); + + /* -------------------------------- + * 6️⃣ SAVE NEW ACTIVITY MEDIA + * -------------------------------- */ + if (Array.isArray(payload.media) && payload.media.length) { + await tx.activitiesMedia.createMany({ + data: payload.media.map((m, index) => ({ + activityXid, + mediaType: m.mediaType ?? 'unknown', + mediaFileName: m.mediaFileName, + displayOrder: index + 1, + })), + }); + } + + /* -------------------------------- + * 7️⃣ CLEAN OLD VENUES & RELATED DATA * -------------------------------- */ const oldVenueIds = ( await tx.activityVenues.findMany({ where: { activityXid }, select: { id: true }, }) - ).map(v => v.id); + ).map((v) => v.id); if (oldVenueIds.length) { + // Clean venue artifacts (media) + await tx.activityVenueArtifacts.deleteMany({ + where: { activityVenueXid: { in: oldVenueIds } }, + }); + + // Clean price taxes and prices const priceIds = ( await tx.activityPrices.findMany({ where: { activityVenueXid: { in: oldVenueIds } }, select: { id: true }, }) - ).map(p => p.id); + ).map((p) => p.id); if (priceIds.length) { await tx.activityPriceTaxes.deleteMany({ @@ -2147,32 +2247,48 @@ async createOrUpdateActivity( }); } + // Clean venues await tx.activityVenues.deleteMany({ where: { id: { in: oldVenueIds } }, }); } /* -------------------------------- - * 6️⃣ CREATE VENUES (MULTIPLE) + * 8️⃣ CREATE VENUES WITH MEDIA & PRICES * -------------------------------- */ for (const venue of payload.venues ?? []) { const venueRow = await tx.activityVenues.create({ data: { activityXid, venueName: venue.venueName, - venueCapacity: venue.venueCapacity ?? 0, - availableSeats: venue.availableSeats ?? 0, + venueCapacity: toNumber(venue.venueCapacity) ?? 0, + availableSeats: toNumber(venue.availableSeats) ?? 0, isMinPeopleReqMandatory: venue.isMinPeopleReqMandatory, - minPeopleRequired: venue.minPeopleRequired ?? null, + minPeopleRequired: toNumber(venue.minPeopleRequired) ?? null, minReqfullfilledBeforeMins: - venue.minReqfullfilledBeforeMins ?? null, + toNumber(venue.minReqfullfilledBeforeMins) ?? null, + venueDescription: venue.venueDescription ?? null, }, }); - for (const price of venue.prices) { + // Create venue media/artifacts + if (Array.isArray(venue.media) && venue.media.length) { + await tx.activityVenueArtifacts.createMany({ + data: venue.media.map((m) => ({ + activityVenueXid: venueRow.id, + mediaType: m.mediaType ?? 'image', + mediaFileName: m.mediaFileName, + })), + }); + } + + // Create venue prices with taxes + for (const price of venue.prices ?? []) { const sellPrice = Number(price.sellPrice); - const { basePrice, taxDetails } = - computeBasePriceAndTaxes(sellPrice, rootTaxes); + const { basePrice, taxDetails } = computeBasePriceAndTaxes( + sellPrice, + rootTaxes, + ); const priceRow = await tx.activityPrices.create({ data: { @@ -2180,8 +2296,7 @@ async createOrUpdateActivity( noOfSession: price.noOfSession ?? 1, isPackage: price.isPackage ?? false, sessionValidity: price.sessionValidity ?? 0, - sessionValidityFrequency: - price.sessionValidityFrequency ?? 'Days', + sessionValidityFrequency: price.sessionValidityFrequency ?? 'Days', basePrice, sellPrice, }, @@ -2189,7 +2304,7 @@ async createOrUpdateActivity( if (taxDetails.length) { await tx.activityPriceTaxes.createMany({ - data: taxDetails.map(t => ({ + data: taxDetails.map((t) => ({ activityPriceXid: priceRow.id, taxXid: t.taxXid, taxPer: t.taxPer, @@ -2201,15 +2316,80 @@ async createOrUpdateActivity( } /* -------------------------------- - * 7️⃣ TRAINER + * 9️⃣ CLEAN & CREATE EQUIPMENT WITH TAXES * -------------------------------- */ - if (payload.trainerAvailable) { - const { basePrice, taxDetails } = - computeBasePriceAndTaxes( - payload.trainerTotalAmount, + const oldEquipmentIds = ( + await tx.activityEquipments.findMany({ + where: { activityXid }, + select: { id: true }, + }) + ).map((e) => e.id); + + if (oldEquipmentIds.length) { + await tx.activityEquipmentTaxes.deleteMany({ + where: { activityEquipmentXid: { in: oldEquipmentIds } }, + }); + await tx.activityEquipments.deleteMany({ + where: { id: { in: oldEquipmentIds } }, + }); + } + + if (Array.isArray(payload.equipments) && payload.equipments.length) { + for (const eq of payload.equipments) { + const totalPrice = toNumber(eq.equipmentTotalPrice) ?? 0; + const { basePrice, taxDetails } = computeBasePriceAndTaxes( + totalPrice, rootTaxes, ); + const equipment = await tx.activityEquipments.create({ + data: { + activityXid, + equipmentName: eq.equipmentName, + isEquipmentChargeable: toBool(eq.isEquipmentChargeable), + equipmentBasePrice: basePrice, + equipmentTotalPrice: totalPrice, + }, + }); + + if (taxDetails.length) { + await tx.activityEquipmentTaxes.createMany({ + data: taxDetails.map((t) => ({ + activityEquipmentXid: equipment.id, + taxXid: t.taxXid, + taxPer: t.taxPer, + taxAmount: t.taxAmount, + })), + }); + } + } + } + + /* -------------------------------- + * 🔟 CLEAN & CREATE TRAINER WITH TAXES + * -------------------------------- */ + const oldTrainerIds = ( + await tx.activityTrainers.findMany({ + where: { activityXid }, + select: { id: true }, + }) + ).map((t) => t.id); + + if (oldTrainerIds.length) { + await tx.activityTrainerTaxes.deleteMany({ + where: { activityTrainerXid: { in: oldTrainerIds } }, + }); + await tx.activityTrainers.deleteMany({ + where: { id: { in: oldTrainerIds } }, + }); + } + + if (payload.trainerAvailable) { + const { basePrice, taxDetails } = computeBasePriceAndTaxes( + payload.trainerTotalAmount, + rootTaxes, + ); + const trainer = await tx.activityTrainers.create({ data: { activityXid, @@ -2218,20 +2398,251 @@ async createOrUpdateActivity( }, }); - for (const t of taxDetails) { - await tx.activityTrainerTaxes.create({ - data: { + if (taxDetails.length) { + await tx.activityTrainerTaxes.createMany({ + data: taxDetails.map((t) => ({ activityTrainerXid: trainer.id, taxXid: t.taxXid, taxPer: t.taxPer, taxAmount: t.taxAmount, - }, + })), }); } } /* -------------------------------- - * 8️⃣ ACTIVITY TRACK + * 1️⃣1️⃣ CLEAN & CREATE PICKUP/DROP TRANSPORTS WITH DETAILS & TAXES + * -------------------------------- */ + const oldTransportIds = ( + await tx.activityPickUpTransport.findMany({ + where: { activityXid }, + select: { id: true }, + }) + ).map((t) => t.id); + + if (oldTransportIds.length) { + // Get all pickup details for these transports + const oldPickupDetailIds = ( + await tx.activityPickUpDetails.findMany({ + where: { activityPickUpTransportXid: { in: oldTransportIds } }, + select: { id: true }, + }) + ).map((p) => p.id); + + if (oldPickupDetailIds.length) { + // Delete taxes first + await tx.activityPickUpTransportTaxes.deleteMany({ + where: { activityPickUpDetailsXid: { in: oldPickupDetailIds } }, + }); + // Delete pickup details + await tx.activityPickUpDetails.deleteMany({ + where: { id: { in: oldPickupDetailIds } }, + }); + } + + // Delete transports + await tx.activityPickUpTransport.deleteMany({ + where: { id: { in: oldTransportIds } }, + }); + } + + if ( + Array.isArray(payload.pickupTransports) && + payload.pickupTransports.length + ) { + for (const transport of payload.pickupTransports) { + // Create transport mode + const transportRow = await tx.activityPickUpTransport.create({ + data: { + activityXid, + transportModeXid: transport.transportModeXid, + isTransportModeChargeable: toBool( + transport.isTransportModeChargeable, + ), + }, + }); + + // Create pickup details for this transport + if ( + Array.isArray(transport.pickupDetails) && + transport.pickupDetails.length + ) { + for (const detail of transport.pickupDetails) { + const totalPrice = toNumber(detail.transportTotalPrice) ?? 0; + const { basePrice, taxDetails } = computeBasePriceAndTaxes( + totalPrice, + rootTaxes, + ); + + const pickupDetail = await tx.activityPickUpDetails.create({ + data: { + activityPickUpTransportXid: transportRow.id, + isPickUp: toBool(detail.isPickUp), + locationLat: toNumber(detail.locationLat), + locationLong: toNumber(detail.locationLong), + locationAddress: detail.locationAddress ?? null, + transportBasePrice: basePrice, + transportTotalPrice: totalPrice, + }, + }); + + if (taxDetails.length) { + await tx.activityPickUpTransportTaxes.createMany({ + data: taxDetails.map((t) => ({ + activityPickUpDetailsXid: pickupDetail.id, + taxXid: t.taxXid, + taxPer: t.taxPer, + taxAmount: t.taxAmount, + })), + }); + } + } + } + } + } + + /* -------------------------------- + * 1️⃣2️⃣ CLEAN & CREATE NAVIGATION MODES WITH TAXES + * -------------------------------- */ + const oldNavIds = ( + await tx.activityNavigationModes.findMany({ + where: { activityXid }, + select: { id: true }, + }) + ).map((n) => n.id); + + if (oldNavIds.length) { + await tx.activityNavigationModesTaxes.deleteMany({ + where: { activityNavigationModeXid: { in: oldNavIds } }, + }); + await tx.activityNavigationModes.deleteMany({ + where: { id: { in: oldNavIds } }, + }); + } + + if ( + Array.isArray(payload.navigationModes) && + payload.navigationModes.length + ) { + const totalPrice = toNumber(payload.navigationModeTotalPrice) ?? 0; + const { basePrice, taxDetails } = computeBasePriceAndTaxes( + totalPrice, + rootTaxes, + ); + + for (const modeId of payload.navigationModes) { + const navMode = await tx.activityNavigationModes.create({ + data: { + activityXid, + navigationModeXid: modeId, + isInActivityChargeable: toBool(payload.navigationModeIsChargeable), + navigationModesBasePrice: basePrice, + navigationModesTotalPrice: totalPrice, + }, + }); + + if (taxDetails.length) { + await tx.activityNavigationModesTaxes.createMany({ + data: taxDetails.map((t) => ({ + activityNavigationModeXid: navMode.id, + taxXid: t.taxXid, + taxPer: t.taxPer, + taxAmount: t.taxAmount, + })), + }); + } + } + } + + /* -------------------------------- + * 1️⃣3️⃣ CLEAN & CREATE AMENITIES + * -------------------------------- */ + await tx.activityAmenities.deleteMany({ where: { activityXid } }); + + if (Array.isArray(payload.amenitiesIds) && payload.amenitiesIds.length) { + await tx.activityAmenities.createMany({ + data: payload.amenitiesIds.map((amenityId) => ({ + activityXid, + amenitiesXid: amenityId, + })), + }); + } + + /* -------------------------------- + * 1️⃣4️⃣ CLEAN & CREATE ELIGIBILITY + * -------------------------------- */ + await tx.activityEligibility.deleteMany({ where: { activityXid } }); + + if (payload.eligibility) { + await tx.activityEligibility.create({ + data: { + activityXid, + isAgeRestriction: toBool(payload.eligibility.isAgeRestriction), + ageRestrictionXid: toNumber(payload.eligibility.ageRestrictionXid), + isWeightRestriction: toBool(payload.eligibility.isWeightRestriction), + weightRestrictionName: payload.eligibility.weightRestrictionName ?? null, + weightEntered: toNumber(payload.eligibility.weightEntered), + weightIn: payload.eligibility.weightIn ?? null, + minWeight: toNumber(payload.eligibility.minWeight), + maxWeight: toNumber(payload.eligibility.maxWeight), + isHeightRestriction: toBool(payload.eligibility.isHeightRestriction), + heightRestrictionName: payload.eligibility.heightRestrictionName ?? null, + heightEntered: toNumber(payload.eligibility.heightEntered), + heightIn: payload.eligibility.heightIn ?? null, + minHeight: toNumber(payload.eligibility.minHeight), + maxHeight: toNumber(payload.eligibility.maxHeight), + }, + }); + } + + /* -------------------------------- + * 1️⃣5️⃣ CLEAN & CREATE OTHER DETAILS + * -------------------------------- */ + await tx.activityOtherDetails.deleteMany({ where: { activityXid } }); + + if (payload.otherDetails) { + await tx.activityOtherDetails.create({ + data: { + activityXid, + exclusiveNotes: payload.otherDetails.exclusiveNotes ?? null, + dosNotes: payload.otherDetails.dosNotes ?? null, + dontsNotes: payload.otherDetails.dontsNotes ?? null, + tipsNotes: payload.otherDetails.tipsNotes ?? null, + termsAndCondition: payload.otherDetails.termsAndCondition ?? null, + }, + }); + } + + /* -------------------------------- + * 1️⃣6️⃣ CLEAN & CREATE FOOD TYPES + * -------------------------------- */ + await tx.activityFoodTypes.deleteMany({ where: { activityXid } }); + + if (Array.isArray(payload.foodTypeIds) && payload.foodTypeIds.length) { + await tx.activityFoodTypes.createMany({ + data: payload.foodTypeIds.map((foodTypeId) => ({ + activityXid, + foodTypeXid: foodTypeId, + })), + }); + } + + /* -------------------------------- + * 1️⃣7️⃣ CLEAN & CREATE CUISINES + * -------------------------------- */ + await tx.activityCuisine.deleteMany({ where: { activityXid } }); + + if (Array.isArray(payload.cuisineIds) && payload.cuisineIds.length) { + await tx.activityCuisine.createMany({ + data: payload.cuisineIds.map((cuisineId) => ({ + activityXid, + foodCuisineXid: cuisineId, + })), + }); + } + + /* -------------------------------- + * 1️⃣8️⃣ ACTIVITY TRACK * -------------------------------- */ await tx.activityTrack.create({ data: { @@ -2245,19 +2656,15 @@ async createOrUpdateActivity( }); /* -------------------------------- - * 9️⃣ RESPONSE + * 1️⃣9️⃣ RESPONSE * -------------------------------- */ return { activityXid, activityRefNumber: activity.activityRefNumber, - status: isDraft - ? 'ACTIVITY_SAVED_AS_DRAFT' - : 'ACTIVITY_SUBMITTED', + status: isDraft ? 'ACTIVITY_SAVED_AS_DRAFT' : 'ACTIVITY_SUBMITTED', }; }); } - - async getAllPQUpdatedResponse(activityXid: number) { const pqqHeaderData = await this.prisma.activityPQQheader.findMany({ where: { @@ -2283,10 +2690,10 @@ async createOrUpdateActivity( select: { id: true, categoryName: true, - displayOrder: true - } - } - } + displayOrder: true, + }, + }, + }, }, // 🔥 ALL ANSWER OPTIONS FOR THIS QUESTION @@ -2296,11 +2703,11 @@ async createOrUpdateActivity( id: true, answerName: true, answerPoints: true, - displayOrder: true + displayOrder: true, }, - orderBy: { displayOrder: "asc" } - } - } + orderBy: { displayOrder: 'asc' }, + }, + }, }, ActivityPQQSuggestions: { where: { isActive: true }, @@ -2308,19 +2715,19 @@ async createOrUpdateActivity( id: true, title: true, comments: true, - activityPqqHeaderXid: true - } + activityPqqHeaderXid: true, + }, }, ActivityPQQSupportings: { where: { isActive: true }, select: { id: true, mediaType: true, - mediaFileName: true - } + mediaFileName: true, + }, }, }, - orderBy: { id: "asc" } + orderBy: { id: 'asc' }, }); // ---------- GROUPING START ---------- @@ -2338,11 +2745,11 @@ async createOrUpdateActivity( id: cat.id, categoryName: cat.categoryName, displayOrder: cat.displayOrder, - activityPqqHeaderId: item.id, // ✅ Added to match AM response - pqqsubCategories: [] + activityPqqHeaderId: item.id, // ✅ Added to match AM response + pqqsubCategories: [], }; } else if (!grouped[cat.id].activityPqqHeaderId) { - grouped[cat.id].activityPqqHeaderId = item.id; // Ensures it's set if missing + grouped[cat.id].activityPqqHeaderId = item.id; // Ensures it's set if missing } const category = grouped[cat.id]; @@ -2354,7 +2761,7 @@ async createOrUpdateActivity( id: sub.id, subCategoryName: sub.subCategoryName, displayOrder: sub.displayOrder, - questions: [] + questions: [], }; category.pqqsubCategories.push(subCat); } @@ -2367,18 +2774,21 @@ async createOrUpdateActivity( pqqAnswerXid: item.pqqAnswerXid, comments: item.comments || null, displayOrder: q.displayOrder, - allAnswerOptions: q.PQQAnswers || [], // 🔥 All answers + allAnswerOptions: q.PQQAnswers || [], // 🔥 All answers suggestions: item.ActivityPQQSuggestions, supportings: item.ActivityPQQSupportings, }); } // ---------- SORTING ---------- - const sortedCategories: any = Object.values(grouped) - .sort((a: any, b: any) => a.displayOrder - b.displayOrder); + const sortedCategories: any = Object.values(grouped).sort( + (a: any, b: any) => a.displayOrder - b.displayOrder, + ); for (const cat of sortedCategories) { - cat.pqqsubCategories.sort((a: any, b: any) => a.displayOrder - b.displayOrder); + cat.pqqsubCategories.sort( + (a: any, b: any) => a.displayOrder - b.displayOrder, + ); for (const sub of cat.pqqsubCategories) { sub.questions.sort((a: any, b: any) => a.displayOrder - b.displayOrder); @@ -2393,8 +2803,8 @@ async createOrUpdateActivity( for (const doc of q.supportings) { if (doc.mediaFileName) { const filePath = doc.mediaFileName; - const key = filePath.startsWith("http") - ? filePath.split(".com/")[1] + const key = filePath.startsWith('http') + ? filePath.split('.com/')[1] : filePath; doc.presignedUrl = await getPresignedUrl(bucket, key);