From 958a3e5cec77f57ec2419e4e5c4288e6b29f23c8 Mon Sep 17 00:00:00 2001 From: Mayank Mishra Date: Mon, 13 Apr 2026 13:19:50 +0530 Subject: [PATCH] made the get itinerary checkout details api --- serverless/functions/user.yml | 15 + .../itinerary/getItineraryCheckoutDetails.ts | 61 ++ .../user/services/itinerary.service.ts | 703 ++++++++++++++++++ 3 files changed, 779 insertions(+) create mode 100644 src/modules/user/handlers/itinerary/getItineraryCheckoutDetails.ts diff --git a/serverless/functions/user.yml b/serverless/functions/user.yml index 55e0d06..6c15442 100644 --- a/serverless/functions/user.yml +++ b/serverless/functions/user.yml @@ -438,6 +438,21 @@ getUserItineraryDetails: path: /itinerary/get-user-itinerary-details method: get +getItineraryCheckoutDetails: + handler: src/modules/user/handlers/itinerary/getItineraryCheckoutDetails.handler + memorySize: 512 + package: + patterns: + - 'src/modules/user/**' + - ${file(./serverless/patterns/base.yml):pattern1} + - ${file(./serverless/patterns/base.yml):pattern2} + - ${file(./serverless/patterns/base.yml):pattern3} + - ${file(./serverless/patterns/base.yml):pattern4} + events: + - httpApi: + path: /itinerary/get-itinerary-checkout-details + method: get + saveUserItinerary: handler: src/modules/user/handlers/itinerary/saveUserItinerary.handler memorySize: 512 diff --git a/src/modules/user/handlers/itinerary/getItineraryCheckoutDetails.ts b/src/modules/user/handlers/itinerary/getItineraryCheckoutDetails.ts new file mode 100644 index 0000000..f543f37 --- /dev/null +++ b/src/modules/user/handlers/itinerary/getItineraryCheckoutDetails.ts @@ -0,0 +1,61 @@ +import { + APIGatewayProxyEvent, + APIGatewayProxyResult, + Context, +} from 'aws-lambda'; + +import { prismaClient } from '../../../../common/database/prisma.lambda.service'; +import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser'; +import { safeHandler } from '../../../../common/utils/handlers/safeHandler'; +import ApiError from '../../../../common/utils/helper/ApiError'; +import { ItineraryService } from '../../services/itinerary.service'; + +const itineraryService = new ItineraryService(prismaClient); + +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context, +): Promise => { + const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; + if (!token) { + throw new ApiError( + 400, + 'This is a protected route. Please provide a valid token.', + ); + } + + const userInfo = await verifyUserToken(token); + const userId = Number(userInfo.id); + + if (!userId || Number.isNaN(userId)) { + throw new ApiError(400, 'Invalid user ID'); + } + + const itineraryHeaderXidRaw = event.queryStringParameters?.itineraryHeaderXid; + if (!itineraryHeaderXidRaw) { + throw new ApiError(400, 'itineraryHeaderXid is required.'); + } + + const itineraryHeaderXid = Number(itineraryHeaderXidRaw); + if (!Number.isInteger(itineraryHeaderXid) || itineraryHeaderXid <= 0) { + throw new ApiError(400, 'Invalid itineraryHeaderXid.'); + } + + const result = await itineraryService.getItineraryCheckoutDetails( + userId, + itineraryHeaderXid, + ); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Itinerary checkout details retrieved successfully', + data: result, + }), + }; +}); diff --git a/src/modules/user/services/itinerary.service.ts b/src/modules/user/services/itinerary.service.ts index afc7902..0b03cac 100644 --- a/src/modules/user/services/itinerary.service.ts +++ b/src/modules/user/services/itinerary.service.ts @@ -30,6 +30,88 @@ const attachPresignedUrl = async (file: string | null | undefined) => { return getPresignedUrl(bucket, key); }; +type CheckoutTaxRow = { + id: number; + taxName: string; + taxPer: number; + taxAmount: number; +}; + +type CheckoutChargeItem = { + id: number; + baseAmount: number; + totalAmount: number | null; + taxes: CheckoutTaxRow[]; +}; + +type CheckoutChargeSummary = { + items: Array<{ + id: number; + baseAmount: number; + totalAmount: number; + taxAmount: number; + taxes: CheckoutTaxRow[]; + }>; + baseAmount: number; + taxAmount: number; + totalAmount: number; +}; + +const normalizeCheckoutKind = (kind?: string | null) => + (kind ?? '') + .trim() + .toUpperCase() + .replace(/\s+/g, '_'); + +const sumCheckoutValues = (values: Array) => + values.reduce((acc, value) => acc + (Number(value) || 0), 0); + +const mapCheckoutTaxes = (rows: any[] = []): CheckoutTaxRow[] => + rows.map((row) => ({ + id: row.id, + taxName: row.taxes?.taxName ?? '', + taxPer: Number(row.taxPer) || Number(row.taxes?.taxPer) || 0, + taxAmount: Number(row.taxAmount) || 0, + })); + +const summarizeCheckoutRows = ( + rows: CheckoutChargeItem[], +): CheckoutChargeSummary => { + const items = rows.map((row) => { + const taxAmount = sumCheckoutValues(row.taxes.map((tax) => tax.taxAmount)); + const totalAmount = row.totalAmount ?? row.baseAmount + taxAmount; + + return { + id: row.id, + baseAmount: row.baseAmount, + totalAmount, + taxAmount, + taxes: row.taxes, + }; + }); + + return { + items, + baseAmount: sumCheckoutValues(items.map((item) => item.baseAmount)), + taxAmount: sumCheckoutValues(items.map((item) => item.taxAmount)), + totalAmount: sumCheckoutValues(items.map((item) => item.totalAmount)), + }; +}; + +const pickCheckoutSummary = ( + groups: Map, + aliases: string[], +) => { + for (const alias of aliases) { + const rows = groups.get(alias); + if (rows?.length) { + return summarizeCheckoutRows(rows); + } + } + + return null; +}; + const attachMediaWithPresignedUrl = async ( mediaArr: Array<{ id: number; @@ -451,6 +533,627 @@ export class ItineraryService { }; } + async getItineraryCheckoutDetails(userXid: number, itineraryHeaderXid: number) { + const itinerary = await this.prisma.itineraryHeader.findFirst({ + where: { + id: itineraryHeaderXid, + ownerXid: userXid, + isActive: true, + deletedAt: null, + }, + select: { + id: true, + itineraryNo: true, + title: true, + fromDate: true, + fromTime: true, + toDate: true, + toTime: true, + itineraryStatus: true, + ItineraryActivities: { + where: { isActive: true, deletedAt: null }, + orderBy: { displayOrder: 'asc' }, + select: { + id: true, + displayOrder: true, + itineraryType: true, + activityXid: true, + venueXid: true, + scheduledHeaderXid: true, + occurenceDate: true, + startTime: true, + endTime: true, + endDate: true, + paxCount: true, + totalAmount: true, + bookingStatus: true, + activity: { + select: { + id: true, + activityTitle: true, + activityDescription: true, + ActivitiesMedia: { + where: { isActive: true, isCoverImage: true, deletedAt: null }, + orderBy: { displayOrder: 'asc' }, + take: 1, + select: { id: true, mediaFileName: true, mediaType: true, displayOrder: true }, + }, + activityFoodTypes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + foodTypeXid: true, + foodType: { select: { id: true, foodTypeName: true } }, + }, + }, + ActivityFoodCost: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + foodTypesId: true, + baseAmount: true, + totalAmount: true, + ActivityFoodTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + ActivityTrainers: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + baseAmount: true, + totalAmount: true, + ActivityTrainerTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + ActivityNavigationModes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + navigationModeName: true, + navigationModesBasePrice: true, + navigationModesTotalPrice: true, + ActivityNavigationModesTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + ActivityPickUpDetails: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + isPickUp: true, + locationLat: true, + locationLong: true, + locationAddress: true, + transportBasePrice: true, + transportTotalPrice: true, + activityPickUpTransportTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + activityPickUpTransports: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + transportModeXid: true, + transportMode: { + select: { + id: true, + transportModeName: true, + transportModeIcon: true, + }, + }, + }, + }, + ActivityEquipments: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + equipmentName: true, + equipmentBasePrice: true, + equipmentTotalPrice: true, + ActivityEquipmentTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + }, + }, + itineraryActivitySelections: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + itineraryMemberXid: true, + isFoodOpted: true, + isTrainerOpted: true, + isInActivityNavigationOpted: true, + itineraryMember: { + select: { + id: true, + memberXid: true, + memberRole: true, + memberStatus: true, + member: { + select: { + id: true, + firstName: true, + lastName: true, + mobileNumber: true, + }, + }, + }, + }, + selectedFoodTypes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + activityFoodTypeXid: true, + activityFoodType: { + select: { + id: true, + foodTypeXid: true, + foodType: { + select: { + id: true, + foodTypeName: true, + }, + }, + }, + }, + }, + }, + activityNavigationMode: { + select: { + id: true, + navigationModeName: true, + navigationModesBasePrice: true, + navigationModesTotalPrice: true, + ActivityNavigationModesTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + selectedEquipments: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + activityEquipmentXid: true, + activityEquipment: { + select: { + id: true, + equipmentName: true, + equipmentBasePrice: true, + equipmentTotalPrice: true, + ActivityEquipmentTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + }, + }, + }, + }, + ItineraryDetails: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + itineraryMemberXid: true, + itineraryKind: true, + hasOpted: true, + baseAmount: true, + totalAmount: true, + description1: true, + description2: true, + offlineCode: true, + activityStatus: true, + isChargeable: true, + itineraryMember: { + select: { + id: true, + memberXid: true, + memberRole: true, + memberStatus: true, + member: { + select: { + id: true, + firstName: true, + lastName: true, + mobileNumber: true, + }, + }, + }, + }, + ItineraryDetailTaxes: { + where: { isActive: true, deletedAt: null }, + select: { + id: true, + taxPer: true, + taxAmount: true, + taxes: { select: { id: true, taxName: true, taxPer: true } }, + }, + }, + }, + }, + }, + }, + }, + }); + + if (!itinerary) { + throw new ApiError(404, 'Itinerary not found.'); + } + + const activities = await Promise.all( + (itinerary.ItineraryActivities ?? []).map(async (item) => { + const coverImage = item.activity?.ActivitiesMedia?.[0]?.mediaFileName ?? null; + const coverImagePresignedUrl = await attachPresignedUrl(coverImage); + + const details = item.ItineraryDetails ?? []; + const memberSelectionGroups = new Map(); + for (const detail of details) { + const list = memberSelectionGroups.get(detail.itineraryMemberXid) ?? []; + list.push(detail); + memberSelectionGroups.set(detail.itineraryMemberXid, list); + } + + const memberSelections = (item.itineraryActivitySelections ?? []).map((selection) => { + const memberDetails = memberSelectionGroups.get(selection.itineraryMemberXid) ?? []; + const detailGroups = new Map(); + for (const detail of memberDetails) { + const key = normalizeCheckoutKind(detail.itineraryKind); + const list = detailGroups.get(key) ?? []; + list.push({ + id: detail.id, + baseAmount: Number(detail.baseAmount) || 0, + totalAmount: detail.totalAmount === null ? null : Number(detail.totalAmount), + taxes: mapCheckoutTaxes(detail.ItineraryDetailTaxes ?? []), + }); + detailGroups.set(key, list); + } + + const activityCharge = + pickCheckoutSummary(detailGroups, ['ACTIVITY']) ?? + { + items: [], + baseAmount: Number(item.totalAmount) || 0, + taxAmount: 0, + totalAmount: Number(item.totalAmount) || 0, + }; + + const foodCharge = + pickCheckoutSummary(detailGroups, ['FOOD']) ?? + summarizeCheckoutRows( + selection.isFoodOpted + ? selection.selectedFoodTypes + .map((selectedFoodType) => { + const matchedCost = + item.activity?.ActivityFoodCost.find( + (cost) => cost.foodTypesId === selectedFoodType.activityFoodType.foodTypeXid, + ) ?? item.activity?.ActivityFoodCost?.[0]; + + return matchedCost + ? { + id: matchedCost.id, + baseAmount: Number(matchedCost.baseAmount) || 0, + totalAmount: + matchedCost.totalAmount === null + ? null + : Number(matchedCost.totalAmount), + taxes: mapCheckoutTaxes(matchedCost.ActivityFoodTaxes ?? []), + } + : null; + }) + .filter(Boolean) as CheckoutChargeItem[] + : [], + ); + + const trainerCharge = + pickCheckoutSummary(detailGroups, ['TRAINER']) ?? + (selection.isTrainerOpted && item.activity?.ActivityTrainers?.[0] + ? summarizeCheckoutRows([ + { + id: item.activity.ActivityTrainers[0].id, + baseAmount: Number(item.activity.ActivityTrainers[0].baseAmount) || 0, + totalAmount: + item.activity.ActivityTrainers[0].totalAmount === null + ? null + : Number(item.activity.ActivityTrainers[0].totalAmount), + taxes: mapCheckoutTaxes( + item.activity.ActivityTrainers[0].ActivityTrainerTaxes ?? [], + ), + }, + ]) + : { items: [], baseAmount: 0, taxAmount: 0, totalAmount: 0 }); + + const navigationCharge = + pickCheckoutSummary(detailGroups, ['NAVIGATION', 'IN_ACTIVITY_NAVIGATION']) ?? + (selection.isInActivityNavigationOpted && selection.activityNavigationMode + ? summarizeCheckoutRows([ + { + id: selection.activityNavigationMode.id, + baseAmount: + Number(selection.activityNavigationMode.navigationModesBasePrice) || 0, + totalAmount: + selection.activityNavigationMode.navigationModesTotalPrice === null + ? null + : Number(selection.activityNavigationMode.navigationModesTotalPrice), + taxes: mapCheckoutTaxes( + selection.activityNavigationMode.ActivityNavigationModesTaxes ?? [], + ), + }, + ]) + : { items: [], baseAmount: 0, taxAmount: 0, totalAmount: 0 }); + + const pickupCharge = + pickCheckoutSummary(detailGroups, ['PICKUP', 'PICK_UP', 'PICKUP_DROP']) ?? + summarizeCheckoutRows( + (item.activity?.ActivityPickUpDetails ?? []).map((pickUpDetail) => ({ + id: pickUpDetail.id, + baseAmount: Number(pickUpDetail.transportBasePrice) || 0, + totalAmount: + pickUpDetail.transportTotalPrice === null + ? null + : Number(pickUpDetail.transportTotalPrice), + taxes: mapCheckoutTaxes(pickUpDetail.activityPickUpTransportTaxes ?? []), + })), + ); + + const equipmentCharge = + pickCheckoutSummary(detailGroups, ['EQUIPMENT']) ?? + summarizeCheckoutRows( + selection.selectedEquipments.map((selectedEquipment) => ({ + id: selectedEquipment.activityEquipment.id, + baseAmount: Number(selectedEquipment.activityEquipment.equipmentBasePrice) || 0, + totalAmount: + selectedEquipment.activityEquipment.equipmentTotalPrice === null + ? null + : Number(selectedEquipment.activityEquipment.equipmentTotalPrice), + taxes: mapCheckoutTaxes( + selectedEquipment.activityEquipment.ActivityEquipmentTaxes ?? [], + ), + })), + ); + + return { + id: selection.id, + itineraryMemberXid: selection.itineraryMemberXid, + member: selection.itineraryMember, + selectedFoodTypes: selection.selectedFoodTypes.map((selectedFoodType) => ({ + id: selectedFoodType.id, + activityFoodTypeXid: selectedFoodType.activityFoodTypeXid, + foodTypeXid: selectedFoodType.activityFoodType.foodTypeXid, + foodTypeName: selectedFoodType.activityFoodType.foodType.foodTypeName, + })), + selectedNavigationMode: selection.activityNavigationMode + ? { + id: selection.activityNavigationMode.id, + navigationModeName: selection.activityNavigationMode.navigationModeName, + baseAmount: + Number(selection.activityNavigationMode.navigationModesBasePrice) || 0, + totalAmount: + Number(selection.activityNavigationMode.navigationModesTotalPrice) || 0, + taxes: mapCheckoutTaxes( + selection.activityNavigationMode.ActivityNavigationModesTaxes ?? [], + ), + } + : null, + selectedEquipments: selection.selectedEquipments.map((selectedEquipment) => ({ + id: selectedEquipment.id, + activityEquipmentXid: selectedEquipment.activityEquipmentXid, + equipmentName: selectedEquipment.activityEquipment.equipmentName, + baseAmount: Number(selectedEquipment.activityEquipment.equipmentBasePrice) || 0, + totalAmount: Number(selectedEquipment.activityEquipment.equipmentTotalPrice) || 0, + taxes: mapCheckoutTaxes( + selectedEquipment.activityEquipment.ActivityEquipmentTaxes ?? [], + ), + })), + pricing: { + activity: activityCharge, + food: foodCharge, + trainer: trainerCharge, + navigation: navigationCharge, + pickup: pickupCharge, + equipment: equipmentCharge, + totalAmount: + activityCharge.totalAmount + + foodCharge.totalAmount + + trainerCharge.totalAmount + + navigationCharge.totalAmount + + pickupCharge.totalAmount + + equipmentCharge.totalAmount, + }, + pricingSource: memberDetails.length ? 'itinerary_details' : 'fallback', + detailRows: memberDetails.map((detail) => ({ + id: detail.id, + itineraryKind: detail.itineraryKind, + hasOpted: detail.hasOpted, + baseAmount: detail.baseAmount, + totalAmount: detail.totalAmount, + description1: detail.description1, + description2: detail.description2, + offlineCode: detail.offlineCode, + activityStatus: detail.activityStatus, + isChargeable: detail.isChargeable, + taxes: mapCheckoutTaxes(detail.ItineraryDetailTaxes ?? []), + member: detail.itineraryMember, + })), + }; + }); + + const activityTotalAmount = sumCheckoutValues( + memberSelections.map((selection) => selection.pricing.totalAmount), + ); + + return { + itineraryActivityXid: item.id, + displayOrder: item.displayOrder, + itineraryType: item.itineraryType, + activityXid: item.activityXid, + venueXid: item.venueXid, + scheduledHeaderXid: item.scheduledHeaderXid, + occurenceDate: item.occurenceDate, + startTime: item.startTime, + endTime: item.endTime, + endDate: item.endDate, + paxCount: item.paxCount, + bookingStatus: item.bookingStatus, + activity: { + id: item.activity?.id ?? null, + activityTitle: item.activity?.activityTitle ?? null, + activityDescription: item.activity?.activityDescription ?? null, + coverImage, + coverImagePresignedUrl, + foodOptions: (item.activity?.activityFoodTypes ?? []).map((foodType) => ({ + id: foodType.id, + foodTypeXid: foodType.foodTypeXid, + foodTypeName: foodType.foodType.foodTypeName, + })), + trainerOptions: (item.activity?.ActivityTrainers ?? []).map((trainer) => ({ + id: trainer.id, + baseAmount: trainer.baseAmount, + totalAmount: trainer.totalAmount, + taxes: mapCheckoutTaxes(trainer.ActivityTrainerTaxes ?? []), + })), + navigationOptions: (item.activity?.ActivityNavigationModes ?? []).map((navigationMode) => ({ + id: navigationMode.id, + navigationModeName: navigationMode.navigationModeName, + navigationModesBasePrice: navigationMode.navigationModesBasePrice, + navigationModesTotalPrice: navigationMode.navigationModesTotalPrice, + taxes: mapCheckoutTaxes(navigationMode.ActivityNavigationModesTaxes ?? []), + })), + pickupOptions: (item.activity?.ActivityPickUpDetails ?? []).map((pickUpDetail) => ({ + id: pickUpDetail.id, + isPickUp: pickUpDetail.isPickUp, + locationLat: pickUpDetail.locationLat, + locationLong: pickUpDetail.locationLong, + locationAddress: pickUpDetail.locationAddress, + transportBasePrice: pickUpDetail.transportBasePrice, + transportTotalPrice: pickUpDetail.transportTotalPrice, + taxes: mapCheckoutTaxes(pickUpDetail.activityPickUpTransportTaxes ?? []), + })), + pickupTransportModes: (item.activity?.activityPickUpTransports ?? []).map((transport) => ({ + id: transport.id, + transportModeXid: transport.transportModeXid, + transportModeName: transport.transportMode.transportModeName, + transportModeIcon: transport.transportMode.transportModeIcon, + })), + equipmentOptions: (item.activity?.ActivityEquipments ?? []).map((equipment) => ({ + id: equipment.id, + equipmentName: equipment.equipmentName, + equipmentBasePrice: equipment.equipmentBasePrice, + equipmentTotalPrice: equipment.equipmentTotalPrice, + taxes: mapCheckoutTaxes(equipment.ActivityEquipmentTaxes ?? []), + })), + }, + memberSelections, + activityTotalAmount, + }; + }), + ); + + const summary = { + activityTotal: sumCheckoutValues(activities.map((activity) => activity.activityTotalAmount)), + foodTotal: sumCheckoutValues( + activities.flatMap((activity) => activity.memberSelections.map((selection) => selection.pricing.food.totalAmount)), + ), + trainerTotal: sumCheckoutValues( + activities.flatMap((activity) => activity.memberSelections.map((selection) => selection.pricing.trainer.totalAmount)), + ), + navigationTotal: sumCheckoutValues( + activities.flatMap((activity) => activity.memberSelections.map((selection) => selection.pricing.navigation.totalAmount)), + ), + pickupTotal: sumCheckoutValues( + activities.flatMap((activity) => activity.memberSelections.map((selection) => selection.pricing.pickup.totalAmount)), + ), + equipmentTotal: sumCheckoutValues( + activities.flatMap((activity) => activity.memberSelections.map((selection) => selection.pricing.equipment.totalAmount)), + ), + taxTotal: sumCheckoutValues( + activities.flatMap((activity) => + activity.memberSelections.flatMap((selection) => [ + selection.pricing.activity.taxAmount, + selection.pricing.food.taxAmount, + selection.pricing.trainer.taxAmount, + selection.pricing.navigation.taxAmount, + selection.pricing.pickup.taxAmount, + selection.pricing.equipment.taxAmount, + ]), + ), + ), + }; + + const grandTotal = + summary.activityTotal + + summary.foodTotal + + summary.trainerTotal + + summary.navigationTotal + + summary.pickupTotal + + summary.equipmentTotal; + + return { + itineraryHeaderXid: itinerary.id, + itineraryNo: itinerary.itineraryNo, + title: itinerary.title, + fromDate: itinerary.fromDate, + fromTime: itinerary.fromTime, + toDate: itinerary.toDate, + toTime: itinerary.toTime, + itineraryStatus: itinerary.itineraryStatus, + summary: { + ...summary, + grandTotal, + }, + activities, + }; + } + async saveUserItinerary( ownerXid: number, payload: {