fixed the schema and the NA problem and multiple transport creation

This commit is contained in:
2026-01-05 13:28:25 +05:30
parent fafb5d06a7
commit e286cffa49
4 changed files with 95 additions and 81 deletions

View File

@@ -874,19 +874,19 @@ model Activities {
checkOutAddress String? @map("check_out_address") @db.VarChar(150)
set_early_checkin_time_mins String @default("null") @map("set_early_checkin_time_mins") @db.VarChar(200)
activityDurationMins Int? @map("activity_duration_mins")
foodAvailable Boolean? @default(false) @map("food_available")
foodIsChargeable Boolean? @default(false) @map("food_is_chargeable")
alcoholAvailable Boolean? @default(false) @map("alcohol_available")
trainerAvailable Boolean? @default(false) @map("trainer_available")
trainerIsChargeable Boolean? @default(false) @map("trainer_is_chargeable")
pickUpDropAvailable Boolean? @default(false) @map("pick_up_drop_available")
pickUpDropIsChargeable Boolean? @default(false) @map("pick_up_drop_is_chargeable")
inActivityAvailable Boolean? @default(false) @map("in_activity_available")
inActivityIsChargeable Boolean? @default(false) @map("in_activity_is_chargeable")
is_late_checking_allowed Boolean? @default(false) @map("is_late_checking_allowed")
equipmentAvailable Boolean? @default(false) @map("equipment_available")
equipmentIsChargeable Boolean? @default(false) @map("equipment_is_chargeable")
cancellationAvailable Boolean? @default(false) @map("cancellation_available")
foodAvailable Boolean? @map("food_available")
foodIsChargeable Boolean? @map("food_is_chargeable")
alcoholAvailable Boolean? @map("alcohol_available")
trainerAvailable Boolean? @map("trainer_available")
trainerIsChargeable Boolean? @map("trainer_is_chargeable")
pickUpDropAvailable Boolean? @map("pick_up_drop_available")
pickUpDropIsChargeable Boolean? @map("pick_up_drop_is_chargeable")
inActivityAvailable Boolean? @map("in_activity_available")
inActivityIsChargeable Boolean? @map("in_activity_is_chargeable")
is_late_checking_allowed Boolean? @map("is_late_checking_allowed")
equipmentAvailable Boolean? @map("equipment_available")
equipmentIsChargeable Boolean? @map("equipment_is_chargeable")
cancellationAvailable Boolean? @map("cancellation_available")
// 🔹 Creator / owner
userId Int?
user User? @relation("UserActivities", fields: [userId], references: [id])
@@ -1385,7 +1385,6 @@ model ActivityPickUpDetails {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
activityPickUpTransport ActivityPickUpTransport[]
activityPickUpTransportTaxes ActivityPickUpTransportTaxes[]
@@map("activity_pick_up_details")
@@ -1393,17 +1392,15 @@ model ActivityPickUpDetails {
}
model ActivityPickUpTransport {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
transportModeXid Int @map("transport_mode_xid")
transportMode TransportModes @relation(fields: [transportModeXid], references: [id], onDelete: Restrict)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
activityPickUpDetails ActivityPickUpDetails? @relation(fields: [activityPickUpDetailsId], references: [id])
activityPickUpDetailsId Int?
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
transportModeXid Int @map("transport_mode_xid")
transportMode TransportModes @relation(fields: [transportModeXid], references: [id], onDelete: Restrict)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("activity_pick_up_transport")
@@schema("act")

View File

@@ -47,7 +47,7 @@ export const PickupTransportDto = z.object({
/* ================= EQUIPMENT ================= */
export const EquipmentDto = z.object({
equipmentName: z.string(),
isEquipmentChargeable: z.boolean().optional().default(false),
isEquipmentChargeable: z.boolean().optional(),
equipmentBasePrice: z.number().int().optional().default(0),
equipmentTotalPrice: z.number().int().optional().default(0),
});
@@ -55,7 +55,7 @@ export const EquipmentDto = z.object({
/* ================= NAVIGATION MODE ================= */
export const NavigationModeDto = z.object({
navigationModeXid: z.number().int(),
isChargeable: z.boolean().optional().default(false),
isChargeable: z.boolean().optional(),
totalPrice: z.number().int().optional().default(0),
});
@@ -114,23 +114,23 @@ export const CreateActivityDto = z.object({
durationHours: z.number().int().optional(),
durationMins: z.number().int().optional(),
foodAvailable: z.boolean().optional().default(false),
foodAvailable: z.boolean().optional(),
foodIsChargeable: z.boolean().optional().default(false),
alcoholAvailable: z.boolean().optional().default(false),
alcoholAvailable: z.boolean().optional(),
trainerAvailable: z.boolean().optional().default(false),
trainerAvailable: z.boolean().optional(),
trainerIsChargeable: z.boolean().optional().default(false),
pickUpDropAvailable: z.boolean().optional().default(false),
pickUpDropAvailable: z.boolean().optional(),
pickUpDropIsChargeable: z.boolean().optional().default(false),
inActivityAvailable: z.boolean().optional().default(false),
inActivityAvailable: z.boolean().optional(),
inActivityIsChargeable: z.boolean().optional().default(false),
equipmentAvailable: z.boolean().optional().default(false),
equipmentAvailable: z.boolean().optional(),
equipmentIsChargeable: z.boolean().optional().default(false),
cancellationAvailable: z.boolean().optional().default(false),
cancellationAvailable: z.boolean().optional(),
cancellationAllowedBeforeMins: z.number().int().nullable().optional(),
currencyXid: z.number().int().nullable().optional(),

View File

@@ -1,4 +1,3 @@
// mediaPresignUpload.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
@@ -11,10 +10,10 @@ const s3 = new S3Client({ region: config.aws.region });
export const handler: APIGatewayProxyHandler = async (event) => {
try {
const body = JSON.parse(event.body || '{}');
const { fileName, mimeType } = body;
const { files } = body;
if (!fileName || !mimeType) {
throw new ApiError(400, 'fileName and mimeType are required');
if (!Array.isArray(files) || files.length === 0) {
throw new ApiError(400, 'files array is required');
}
const activityXid = event.pathParameters?.activityXid;
@@ -22,35 +21,45 @@ export const handler: APIGatewayProxyHandler = async (event) => {
throw new ApiError(400, 'activityXid is required in path parameters');
}
const safeFileName = fileName
.trim()
.replace(/\s+/g, '_')
.replace(/[^a-zA-Z0-9._-]/g, '')
.toLowerCase();
const results = [];
// const key = `temporary/uploads/activity/activity_${activityXid}/${uuid()}_${safeFileName}`;
const key = `ActivityOnboarding/Activity_${activityXid}/Artifacts/${uuid()}_${safeFileName}`;
for (const file of files) {
const { fileName, mimeType } = file;
const command = new PutObjectCommand({
Bucket: config.aws.bucketName!,
Key: key,
ContentType: mimeType, // IMPORTANT: must match frontend header
// ❌ DO NOT SET ACL
});
if (!fileName || !mimeType) {
throw new ApiError(400, 'Each file must have fileName and mimeType');
}
const uploadUrl = await getSignedUrl(s3, command, {
expiresIn: 300, // 5 minutes
});
const safeFileName = fileName
.trim()
.replace(/\s+/g, '_')
.replace(/[^a-zA-Z0-9._-]/g, '')
.toLowerCase();
return response(200, {
uploadUrl,
key,
fileUrl: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${key}`,
});
const key = `ActivityOnboarding/Activity_${activityXid}/Artifacts/${uuid()}_${safeFileName}`;
const command = new PutObjectCommand({
Bucket: config.aws.bucketName!,
Key: key,
ContentType: mimeType,
});
const uploadUrl = await getSignedUrl(s3, command, {
expiresIn: 300,
});
results.push({
uploadUrl,
key,
fileUrl: `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${key}`,
});
}
return response(200, { files: results });
} catch (err) {
console.error(err);
return response(500, 'Failed to generate presigned URL');
return response(500, 'Failed to generate presigned URLs');
}
};

View File

@@ -1747,6 +1747,7 @@ export class HostService {
pickUpDropAvailable: true,
pickUpDropIsChargeable: true,
activityDurationMins: true,
totalScore: true,
activityType: {
select: {
id: true,
@@ -1830,6 +1831,7 @@ export class HostService {
},
},
},
orderBy: { createdAt: 'asc' },
},
},
},
@@ -2367,6 +2369,12 @@ export class HostService {
const toBool = (v: any) =>
v === true || v === 'true' || v === 1 || v === '1';
const toBoolOrNull = (v: any): boolean | null => {
if (v === null || v === undefined || v === '') return null;
return v === true || v === 'true' || v === 1 || v === '1';
};
const toNumber = (v: any) =>
v === undefined || v === null || v === '' ? undefined : Number(v);
@@ -2413,16 +2421,16 @@ export class HostService {
/* =====================================================
* HARD NORMALIZATION (SERVICE-LEVEL)
* ===================================================== */
payload.foodAvailable = toBool(payload.foodAvailable);
payload.alcoholAvailable = toBool(payload.alcoholAvailable);
payload.trainerAvailable = toBool(payload.trainerAvailable);
payload.pickUpDropAvailable = toBool(payload.pickUpDropAvailable);
payload.inActivityAvailable = toBool(payload.inActivityAvailable);
payload.equipmentAvailable = toBool(payload.equipmentAvailable);
payload.cancellationAvailable = toBool(payload.cancellationAvailable);
payload.foodAvailable = toBoolOrNull(payload.foodAvailable);
payload.alcoholAvailable = toBoolOrNull(payload.alcoholAvailable);
payload.trainerAvailable = toBoolOrNull(payload.trainerAvailable);
payload.pickUpDropAvailable = toBoolOrNull(payload.pickUpDropAvailable);
payload.inActivityAvailable = toBoolOrNull(payload.inActivityAvailable);
payload.equipmentAvailable = toBoolOrNull(payload.equipmentAvailable);
payload.cancellationAvailable = toBoolOrNull(payload.cancellationAvailable);
payload.isInstantBooking = toBool(payload.isInstantBooking);
payload.isCheckOutSame = toBool(payload.isCheckOutSame);
payload.alcoholAvailable = toBool(payload.alcoholAvailable);
payload.alcoholAvailable = toBoolOrNull(payload.alcoholAvailable);
payload.trainerTotalAmount = toNumber(payload.trainerTotalAmount);
payload.cancellationAllowedBeforeMins = toNumber(
@@ -2967,7 +2975,7 @@ export class HostService {
await tx.activityPickUpTransport.deleteMany({
where: {
activityPickUpDetailsId: { in: oldPickupDetailIds },
activityXid: Number(activityXid)
},
});
@@ -2978,10 +2986,18 @@ export class HostService {
if (Array.isArray(payload.pickupTransports)) {
for (const transport of payload.pickupTransports) {
// transport.transportModeXid is here ✅
// ✅ CREATE TRANSPORT ONCE PER MODE
const pickupTransport = await tx.activityPickUpTransport.create({
data: {
activityXid: activityXid,
transportModeXid: transport.transportModeXid,
},
});
if (Array.isArray(transport.pickupDetails)) {
for (const detail of transport.pickupDetails) {
const totalPrice = toNumber(detail.transportTotalPrice) ?? 0;
const { basePrice, taxDetails } = computeBasePriceAndTaxes(
@@ -2989,7 +3005,7 @@ export class HostService {
rootTaxes,
);
/* 1⃣ CREATE PICKUP DETAILS (PRICE OWNER) */
/* 1⃣ CREATE PICKUP DETAILS */
const pickupDetail = await tx.activityPickUpDetails.create({
data: {
activitiesXid: activityXid,
@@ -3002,16 +3018,7 @@ export class HostService {
},
});
/* 2⃣ CREATE TRANSPORT MODE */
await tx.activityPickUpTransport.create({
data: {
activityXid: activityXid, // required by your schema
transportModeXid: transport.transportModeXid,
activityPickUpDetailsId: pickupDetail.id,
},
});
/* 3⃣ CREATE TAXES */
/* 2⃣ CREATE TAXES */
if (taxDetails.length) {
await tx.activityPickUpTransportTaxes.createMany({
data: taxDetails.map((t) => ({
@@ -3027,6 +3034,7 @@ export class HostService {
}
}
/* --------------------------------
* 1⃣2⃣ CLEAN & CREATE NAVIGATION MODES WITH TAXES
* -------------------------------- */