Compare commits
2 Commits
c5cad4fdce
...
958a3e5cec
| Author | SHA1 | Date | |
|---|---|---|---|
| 958a3e5cec | |||
| 181f32b2e7 |
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user