From c2d3ab9da29c9cf4807daf3f8765f1610de77349 Mon Sep 17 00:00:00 2001 From: paritosh18 Date: Wed, 31 Dec 2025 14:42:30 +0530 Subject: [PATCH] navigation modes per price update --- src/modules/host/dto/createActivity.schema.ts | 44 +++++++---------- src/modules/host/services/host.service.ts | 49 +++++++++++++------ 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/modules/host/dto/createActivity.schema.ts b/src/modules/host/dto/createActivity.schema.ts index 02dbdab..353895f 100644 --- a/src/modules/host/dto/createActivity.schema.ts +++ b/src/modules/host/dto/createActivity.schema.ts @@ -2,20 +2,18 @@ import { z } from 'zod'; /* ================= MEDIA ================= */ export const MediaDto = z.object({ - mediaType: z.string().optional(), // "image/jpeg", "video/mp4", etc. - mediaFileName: z.string(), // S3 file URL + mediaType: z.string().optional(), + mediaFileName: z.string(), }); -/* ================= PRICE ================= - * ❌ No tax info here; root-level only - */ +/* ================= PRICE ================= */ export const PriceDto = z.object({ noOfSession: z.number().int().optional().default(1), isPackage: z.boolean().optional().default(false), 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(), }); /* ================= VENUE ================= */ @@ -28,11 +26,7 @@ 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([]), }); @@ -58,6 +52,13 @@ export const EquipmentDto = z.object({ equipmentTotalPrice: z.number().int().optional().default(0), }); +/* ================= NAVIGATION MODE ================= */ +export const NavigationModeDto = z.object({ + navigationModeXid: z.number().int(), + isChargeable: z.boolean().optional().default(false), + totalPrice: z.number().int().optional().default(0), +}); + /* ================= ELIGIBILITY ================= */ export const EligibilityDto = z.object({ isAgeRestriction: z.boolean().optional().default(false), @@ -93,16 +94,13 @@ export const OtherDetailsDto = z.object({ /* ================= CREATE ACTIVITY ================= */ export const CreateActivityDto = z.object({ - /* 🔑 REQUIRED */ activityXid: z.number().int(), - /* OPTIONAL CORE */ activityTypeXid: z.number().int().optional(), frequenciesXid: z.number().int().nullable().optional(), activityTitle: z.string().optional(), activityDescription: z.string().optional(), - /* LOCATION */ checkInLat: z.number().nullable().optional(), checkInLong: z.number().nullable().optional(), checkInAddress: z.string().nullable().optional(), @@ -111,13 +109,11 @@ export const CreateActivityDto = z.object({ checkOutLong: z.number().nullable().optional(), checkOutAddress: z.string().nullable().optional(), - /* DURATION / ENERGY */ energyLevelXid: z.number().int().nullable().optional(), durationDays: z.number().int().optional(), durationHours: z.number().int().optional(), durationMins: z.number().int().optional(), - /* FLAGS */ foodAvailable: z.boolean().optional().default(false), foodIsChargeable: z.boolean().optional().default(false), alcoholAvailable: z.boolean().optional().default(false), @@ -137,37 +133,35 @@ export const CreateActivityDto = z.object({ cancellationAvailable: z.boolean().optional().default(false), cancellationAllowedBeforeMins: z.number().int().nullable().optional(), - /* MONEY / CURRENCY */ currencyXid: z.number().int().nullable().optional(), sustainabilityScore: z.number().int().nullable().optional(), safetyScore: z.number().int().nullable().optional(), isInstantBooking: z.boolean().optional().default(false), - /* 🔥 ROOT-LEVEL TAX (SINGLE SOURCE OF TRUTH) */ taxXids: z.array(z.number().int()).optional().default([]), - /* 🔥 MEDIA ARRAYS */ - media: z.array(MediaDto).optional().default([]), // Activity-level media - venues: z.array(VenueDto).optional().default([]), // Each venue’s media + prices + media: z.array(MediaDto).optional().default([]), + venues: z.array(VenueDto).optional().default([]), - /* RELATION ARRAYS */ foodTypeIds: z.array(z.number().int()).optional().default([]), cuisineIds: z.array(z.number().int()).optional().default([]), pickupTransports: z.array(PickupTransportDto).optional().default([]), - navigationModes: z.array(z.number().int()).optional().default([]), + + navigationModes: z + .array(NavigationModeDto) + .optional() + .default([]), + equipments: z.array(EquipmentDto).optional().default([]), amenitiesIds: z.array(z.number().int()).optional().default([]), foodTotalAmount: z.number().int().optional().default(0), - /* EXTRA OBJECTS */ eligibility: EligibilityDto.optional(), otherDetails: OtherDetailsDto.optional(), allowedEntryTypes: z.array(z.number().int()).optional().default([]), - navigationModeIsChargeable: z.boolean().optional().default(false), trainerTotalAmount: z.number().int().optional().default(0), - navigationModeTotalPrice: z.number().int().optional().default(0), }); export type CreateActivityInput = z.infer; diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index 5c18e38..cc1ead2 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -3025,34 +3025,55 @@ export class HostService { 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, - ); + /* -------------------------------- + * 1️⃣2️⃣ CREATE NAVIGATION MODES (PER MODE) + * -------------------------------- */ + if (Array.isArray(payload.navigationModes)) { + for (const mode of payload.navigationModes) { + const isChargeable = toBool(mode.isChargeable); + const totalPrice = isChargeable + ? (toNumber(mode.totalPrice) ?? 0) + : 0; - for (const modeId of payload.navigationModes) { + if (isChargeable && totalPrice <= 0) { + throw new ApiError( + 400, + 'totalPrice must be > 0 when navigation mode is chargeable', + ); + } + + let basePrice = 0; + let taxDetails: Array<{ + taxXid: number; + taxPer: number; + taxAmount: number; + }> = []; + + if (isChargeable) { + const result = computeBasePriceAndTaxes(totalPrice, rootTaxes); + + basePrice = result.basePrice; + taxDetails = result.taxDetails; + } + + /* 1️⃣ CREATE NAVIGATION MODE ROW */ const navMode = await tx.activityNavigationModes.create({ data: { activityXid, - navigationModeXid: modeId, - isInActivityChargeable: toBool( - payload.navigationModeIsChargeable, - ), + navigationModeXid: mode.navigationModeXid, + isInActivityChargeable: isChargeable, navigationModesBasePrice: basePrice, navigationModesTotalPrice: totalPrice, }, }); + /* 2️⃣ CREATE TAXES (ONLY IF CHARGEABLE) */ if (taxDetails.length) { await tx.activityNavigationModesTaxes.createMany({ data: taxDetails.map((t) => ({