diff --git a/cron.js b/cron.js index 2ca8250a..48f3c278 100644 --- a/cron.js +++ b/cron.js @@ -26,6 +26,7 @@ import UtsavDb from './models/utsav_db.model.js'; import { sendCancellationEmail, sendOpenBookingEmail } from './helpers/mailer.helper.js'; import { getBooking, + getBookings, getBookingType, getBookingTypeFromBooking } from './helpers/booking.helper.js'; @@ -109,19 +110,26 @@ async function getUnpaidOnlineBookingsAndTransactions(bookings, transactions) { .subtract(MAX_APP_PAYMENT_DURATION, 'minutes'); const pendingTransactions = await getPendingTransactions(cancelTimeFilter); + const bookingsByType = {}; + for (const transaction of pendingTransactions) { const bookingType = getBookingType(transaction); - // TODO: optimize, get all bookings at once // Food bookings are handled in a special way if (bookingType != TYPE_FOOD) { - const booking = await getBooking(bookingType, transaction.bookingid); - if (booking) { - bookings.push(booking); + if (!bookingsByType[bookingType]) { + bookingsByType[bookingType] = new Set(); } + bookingsByType[bookingType].add(transaction.bookingid); } transactions.push(transaction); } + + for (const [bookingType, bookingIdsSet] of Object.entries(bookingsByType)) { + const bookingIds = Array.from(bookingIdsSet); + const fetchedBookings = await getBookings(bookingType, bookingIds); + bookings.push(...fetchedBookings); + } } async function cancelBookings(systemUser, bookings, userBookingIds, openBookings, t) { diff --git a/helpers/booking.helper.js b/helpers/booking.helper.js index e6f501ea..24fa42e9 100644 --- a/helpers/booking.helper.js +++ b/helpers/booking.helper.js @@ -84,6 +84,57 @@ export async function getBooking(bookingType, bookingid) { return booking; } +export async function getBookings(bookingType, bookingids) { + if (!bookingids || bookingids.length === 0) { + return []; + } + + let bookings = []; + + switch (bookingType) { + case TYPE_ROOM: + bookings = await RoomBooking.findAll({ + where: { bookingid: bookingids } + }); + break; + + case TYPE_FLAT: + bookings = await FlatBooking.findAll({ + where: { bookingid: bookingids } + }); + break; + + case TYPE_ADHYAYAN: + bookings = await ShibirBookingDb.findAll({ + where: { bookingid: bookingids } + }); + break; + + case TYPE_FOOD: + bookings = await FoodDb.findAll({ + where: { id: bookingids } + }); + break; + + case TYPE_TRAVEL: + bookings = await TravelDb.findAll({ + where: { bookingid: bookingids } + }); + break; + + case TYPE_UTSAV: + bookings = await UtsavBooking.findAll({ + where: { bookingid: bookingids } + }); + break; + + default: + throw new ApiError(400, `${ERR_INVALID_BOOKING_TYPE}: ${bookingType}`); + } + + return bookings; +} + export function ifMigrated(transaction) { let bookingid = transaction.bookingid; let description = transaction.description; diff --git a/tests/optimize_booking_retrieval.test.js b/tests/optimize_booking_retrieval.test.js new file mode 100644 index 00000000..d352d84d --- /dev/null +++ b/tests/optimize_booking_retrieval.test.js @@ -0,0 +1,80 @@ +import { expect, it, describe, mock } from "bun:test"; +import { + TYPE_ROOM, + TYPE_FLAT, + TYPE_ADHYAYAN, + TYPE_FOOD, + TYPE_TRAVEL, + TYPE_UTSAV +} from '../config/constants.js'; + +mock.module("../models/associations.js", () => ({ + RoomBooking: { findAll: async (q) => q.where.bookingid.map(id => ({ bookingid: id })) }, + FlatBooking: { findAll: async (q) => q.where.bookingid.map(id => ({ bookingid: id })) }, + ShibirBookingDb: { findAll: async (q) => q.where.bookingid.map(id => ({ bookingid: id })) }, + FoodDb: { findAll: async (q) => q.where.id.map(id => ({ id })) }, + TravelDb: { findAll: async (q) => q.where.bookingid.map(id => ({ bookingid: id })) }, + UtsavBooking: { findAll: async (q) => q.where.bookingid.map(id => ({ bookingid: id })) }, + CardDb: {}, + Transactions: {}, + ShibirDb: {}, + Departments: {}, + MaintenanceDb: {}, + GateRecord: {}, + CentreDb: {}, + BulkFoodBooking: {}, + FoodPhysicalPlate: {}, + RoomDb: {}, + FlatDb: {}, + UtsavDb: {}, + UtsavPackagesDb: {}, + AdminRoles: {}, + AdminUsers: {}, + Roles: {}, + Menu: {}, + Cities: {}, + States: {}, + Countries: {}, + GuestDb: {}, + GuestRelationship: {}, + RazorpayWebhook: {}, + RazorpaySettlement: {}, + SupportTickets: {}, + BlockDates: {}, + Updates: {}, + AdhyayanFeedback: {}, + RazorpaySettlementRecon: {}, + ShibirAttendanceDb: {} +})); + +const { getBookings } = await import('../helpers/booking.helper.js'); + +describe('getBookings', () => { + it('should return empty array for empty bookingids', async () => { + const result = await getBookings(TYPE_ROOM, []); + expect(result).toEqual([]); + }); + + it('should call RoomBooking.findAll for TYPE_ROOM', async () => { + const ids = ['room1']; + const result = await getBookings(TYPE_ROOM, ids); + expect(result).toHaveLength(1); + expect(result[0].bookingid).toBe('room1'); + }); + + it('should call FoodDb.findAll for TYPE_FOOD', async () => { + const ids = [1]; + const result = await getBookings(TYPE_FOOD, ids); + expect(result).toHaveLength(1); + expect(result[0].id).toBe(1); + }); + + it('should throw error for invalid booking type', async () => { + try { + await getBookings('INVALID', ['id1']); + expect(true).toBe(false); // Should not reach here + } catch (e) { + expect(e.message).toContain('Invalid booking type'); + } + }); +});