2 Commits

6 changed files with 807 additions and 4 deletions

View File

@@ -877,7 +877,10 @@ model HostParent {
id Int @id @default(autoincrement())
hostXid Int @map("host_xid")
host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade)
companyName String? @map("company_name") @db.VarChar(100)
companyName String? @map("company_name") @db.VarChar(100)
firstName String? @map("first_name") @db.VarChar(50)
lastName String? @map("last_name") @db.VarChar(50)
mobileNumber String? @map("mobile_number") @db.VarChar(15)
address1 String? @map("address_1") @db.VarChar(150)
address2 String? @map("address_2") @db.VarChar(150)
cityXid Int? @map("city_xid")

View File

@@ -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

View File

@@ -46,6 +46,18 @@ export const parentCompanySchema = z.object({
companyTypeXid: z.number()
.optional(),
firstName: z.string()
.max(50, "First name cannot exceed 50 characters")
.optional(),
lastName: z.string()
.max(50, "Last name cannot exceed 50 characters")
.optional(),
mobileNumber: z.string()
.max(15, "Mobile number cannot exceed 15 characters")
.optional(),
websiteUrl: z.string().nullable().optional(),
instagramUrl: z.string().nullable().optional(),
facebookUrl: z.string().nullable().optional(),

View File

@@ -1655,6 +1655,9 @@ export class HostService {
data: {
host: { connect: { id: createdHost.id } },
companyName: parentCompanyData.companyName || null,
firstName: parentCompanyData.firstName || null,
lastName: parentCompanyData.lastName || null,
mobileNumber: parentCompanyData.mobileNumber || null,
address1: parentCompanyData.address1 || null,
address2: parentCompanyData.address2 || null,
// Safely handle city connection - only connect if valid ID exists
@@ -1691,7 +1694,7 @@ export class HostService {
facebookUrl: parentCompanyData.facebookUrl || null,
linkedinUrl: parentCompanyData.linkedinUrl || null,
twitterUrl: parentCompanyData.twitterUrl || null,
},
} as any,
});
// parent docs
@@ -1871,6 +1874,9 @@ export class HostService {
data: {
host: { connect: { id: updatedHost.id } },
companyName: parentCompanyData.companyName || null,
firstName: parentCompanyData.firstName || null,
lastName: parentCompanyData.lastName || null,
mobileNumber: parentCompanyData.mobileNumber || null,
address1: parentCompanyData.address1 || null,
address2: parentCompanyData.address2 || null,
cities:
@@ -1910,7 +1916,7 @@ export class HostService {
facebookUrl: parentCompanyData.facebookUrl || null,
linkedinUrl: parentCompanyData.linkedinUrl || null,
twitterUrl: parentCompanyData.twitterUrl || null,
},
} as any,
});
if (parentDocuments?.length) {
@@ -1930,6 +1936,9 @@ export class HostService {
where: { id: parentRecord.id },
data: {
companyName: parentCompanyData.companyName || null,
firstName: parentCompanyData.firstName || null,
lastName: parentCompanyData.lastName || null,
mobileNumber: parentCompanyData.mobileNumber || null,
address1: parentCompanyData.address1 || null,
address2: parentCompanyData.address2 || null,
cities:
@@ -1969,7 +1978,7 @@ export class HostService {
facebookUrl: parentCompanyData.facebookUrl || null,
linkedinUrl: parentCompanyData.linkedinUrl || null,
twitterUrl: parentCompanyData.twitterUrl || null,
},
} as any,
});
// if (parentDocuments?.length) {

View File

@@ -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<APIGatewayProxyResult> => {
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,
}),
};
});

View File

@@ -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<number | null | undefined>) =>
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<string, CheckoutChargeItem[]>,
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<number, any[]>();
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<string, CheckoutChargeItem[]>();
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: {