const db = require('../models'); const Utils = require('../utils'); const config = require('../../config'); const crypto = require('crypto'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; function isCustomerUser(currentUser) { return currentUser?.app_role?.name === config.roles.customer; } function canManageInternalBookingRequestFields(currentUser) { return Boolean(currentUser?.app_role?.globalAccess); } function generateRequestCode() { const dateSegment = new Date().toISOString().slice(0, 10).replace(/-/g, ''); const randomSegment = crypto.randomBytes(2).toString('hex').toUpperCase(); return `BR-${dateSegment}-${randomSegment}`; } function resolveRequestCode(data, currentUser) { if (canManageInternalBookingRequestFields(currentUser) && data.request_code) { return data.request_code; } return generateRequestCode(); } function resolveStatusForCreate(data, currentUser) { if (canManageInternalBookingRequestFields(currentUser)) { return data.status || 'draft'; } return 'submitted'; } function resolveOrganizationId(data, currentUser) { if (canManageInternalBookingRequestFields(currentUser)) { return data.organization || currentUser.organization?.id || null; } return currentUser.organization?.id || null; } function resolveRequestedById(data, currentUser) { if (canManageInternalBookingRequestFields(currentUser)) { return data.requested_by || currentUser.id || null; } return currentUser.id || null; } function mergeWhereWithScope(where, scope) { if (!scope) { return where; } return { [Op.and]: [where, scope], }; } module.exports = class Booking_requestsDBApi { static async create(data, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; const booking_requests = await db.booking_requests.create( { id: data.id || undefined, request_code: resolveRequestCode(data, currentUser), status: resolveStatusForCreate(data, currentUser), check_in_at: data.check_in_at || null , check_out_at: data.check_out_at || null , preferred_bedrooms: data.preferred_bedrooms || null , guest_count: data.guest_count || null , purpose_of_stay: data.purpose_of_stay || null , special_requirements: data.special_requirements || null , budget_code: data.budget_code || null , max_budget_amount: data.max_budget_amount || null , currency: data.currency || null , importHash: data.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, }, { transaction }, ); await booking_requests.setTenant( canManageInternalBookingRequestFields(currentUser) ? data.tenant || null : null, { transaction, }); await booking_requests.setOrganization(resolveOrganizationId(data, currentUser), { transaction, }); await booking_requests.setRequested_by(resolveRequestedById(data, currentUser), { transaction, }); await booking_requests.setPreferred_property( data.preferred_property || null, { transaction, }); await booking_requests.setPreferred_unit_type( data.preferred_unit_type || null, { transaction, }); await booking_requests.setTravelers(data.travelers || [], { transaction, }); await booking_requests.setApproval_steps(data.approval_steps || [], { transaction, }); await booking_requests.setDocuments(data.documents || [], { transaction, }); await booking_requests.setComments(data.comments || [], { transaction, }); return booking_requests; } static async bulkImport(data, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method const booking_requestsData = data.map((item, index) => ({ id: item.id || undefined, request_code: item.request_code || null , status: item.status || null , check_in_at: item.check_in_at || null , check_out_at: item.check_out_at || null , preferred_bedrooms: item.preferred_bedrooms || null , guest_count: item.guest_count || null , purpose_of_stay: item.purpose_of_stay || null , special_requirements: item.special_requirements || null , budget_code: item.budget_code || null , max_budget_amount: item.max_budget_amount || null , currency: item.currency || null , importHash: item.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, createdAt: new Date(Date.now() + index * 1000), })); // Bulk create items const booking_requests = await db.booking_requests.bulkCreate(booking_requestsData, { transaction }); // For each item created, replace relation files return booking_requests; } static async update(id, data, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; const globalAccess = currentUser.app_role?.globalAccess; const canManageInternalFields = canManageInternalBookingRequestFields(currentUser); const booking_requests = await db.booking_requests.findOne({ where: mergeWhereWithScope({ id }, isCustomerUser(currentUser) ? { requested_byId: currentUser.id } : null), transaction, }); const updatePayload = {}; if (data.request_code !== undefined && canManageInternalFields) updatePayload.request_code = data.request_code; if (data.status !== undefined && canManageInternalFields) updatePayload.status = data.status; if (data.check_in_at !== undefined) updatePayload.check_in_at = data.check_in_at; if (data.check_out_at !== undefined) updatePayload.check_out_at = data.check_out_at; if (data.preferred_bedrooms !== undefined) updatePayload.preferred_bedrooms = data.preferred_bedrooms; if (data.guest_count !== undefined) updatePayload.guest_count = data.guest_count; if (data.purpose_of_stay !== undefined) updatePayload.purpose_of_stay = data.purpose_of_stay; if (data.special_requirements !== undefined) updatePayload.special_requirements = data.special_requirements; if (data.budget_code !== undefined) updatePayload.budget_code = data.budget_code; if (data.max_budget_amount !== undefined) updatePayload.max_budget_amount = data.max_budget_amount; if (data.currency !== undefined) updatePayload.currency = data.currency; updatePayload.updatedById = currentUser.id; await booking_requests.update(updatePayload, {transaction}); if (data.tenant !== undefined && canManageInternalFields) { await booking_requests.setTenant( data.tenant, { transaction } ); } if (data.organization !== undefined) { await booking_requests.setOrganization( (globalAccess ? data.organization : currentUser.organization.id), { transaction } ); } if (data.requested_by !== undefined && canManageInternalFields) { await booking_requests.setRequested_by( data.requested_by, { transaction } ); } if (data.preferred_property !== undefined) { await booking_requests.setPreferred_property( data.preferred_property, { transaction } ); } if (data.preferred_unit_type !== undefined) { await booking_requests.setPreferred_unit_type( data.preferred_unit_type, { transaction } ); } if (data.travelers !== undefined) { await booking_requests.setTravelers(data.travelers, { transaction }); } if (data.approval_steps !== undefined) { await booking_requests.setApproval_steps(data.approval_steps, { transaction }); } if (data.documents !== undefined) { await booking_requests.setDocuments(data.documents, { transaction }); } if (data.comments !== undefined) { await booking_requests.setComments(data.comments, { transaction }); } return booking_requests; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; const booking_requests = await db.booking_requests.findAll({ where: mergeWhereWithScope({ id: { [Op.in]: ids, }, }, isCustomerUser(currentUser) ? { requested_byId: currentUser.id } : null), transaction, }); await db.sequelize.transaction(async (transaction) => { for (const record of booking_requests) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } for (const record of booking_requests) { await record.destroy({transaction}); } }); return booking_requests; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; const booking_requests = await db.booking_requests.findOne({ where: mergeWhereWithScope({ id }, isCustomerUser(currentUser) ? { requested_byId: currentUser.id } : null), transaction, }); if (!booking_requests) { return null; } await booking_requests.update({ deletedBy: currentUser.id }, { transaction, }); await booking_requests.destroy({ transaction }); return booking_requests; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; const currentUser = (options && options.currentUser) || null; const booking_requests = await db.booking_requests.findOne({ where: mergeWhereWithScope(where, isCustomerUser(currentUser) ? { requested_byId: currentUser.id } : null), transaction, }); if (!booking_requests) { return booking_requests; } const output = booking_requests.get({plain: true}); output.booking_request_travelers_booking_request = await booking_requests.getBooking_request_travelers_booking_request({ transaction }); output.approval_steps_booking_request = await booking_requests.getApproval_steps_booking_request({ transaction }); output.reservations_booking_request = await booking_requests.getReservations_booking_request({ transaction }); output.documents_booking_request = await booking_requests.getDocuments_booking_request({ transaction }); output.activity_comments_booking_request = await booking_requests.getActivity_comments_booking_request({ transaction }); output.tenant = await booking_requests.getTenant({ transaction }); output.organization = await booking_requests.getOrganization({ transaction }); output.requested_by = await booking_requests.getRequested_by({ transaction }); output.preferred_property = await booking_requests.getPreferred_property({ transaction }); output.preferred_unit_type = await booking_requests.getPreferred_unit_type({ transaction }); output.travelers = await booking_requests.getTravelers({ transaction }); output.approval_steps = await booking_requests.getApproval_steps({ transaction }); output.documents = await booking_requests.getDocuments({ transaction }); output.comments = await booking_requests.getComments({ transaction }); return output; } static async findAll( filter, globalAccess, options ) { const limit = filter.limit || 0; let offset = 0; let where = {}; const currentPage = +filter.page; const user = (options && options.currentUser) || null; const userOrganizations = (user && user.organizations?.id) || null; const customerScope = isCustomerUser(user) ? { requested_byId: user.id } : null; if (userOrganizations) { if (options?.currentUser?.organizationsId) { where.organizationId = options.currentUser.organizationsId; } } if (customerScope) { where.requested_byId = user.id; } offset = currentPage * limit; let include = [ { model: db.tenants, as: 'tenant', where: filter.tenant ? { [Op.or]: [ { id: { [Op.in]: filter.tenant.split('|').map(term => Utils.uuid(term)) } }, { name: { [Op.or]: filter.tenant.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] } : {}, }, { model: db.organizations, as: 'organization', }, { model: db.users, as: 'requested_by', where: filter.requested_by ? { [Op.or]: [ { id: { [Op.in]: filter.requested_by.split('|').map(term => Utils.uuid(term)) } }, { firstName: { [Op.or]: filter.requested_by.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] } : {}, }, { model: db.properties, as: 'preferred_property', where: filter.preferred_property ? { [Op.or]: [ { id: { [Op.in]: filter.preferred_property.split('|').map(term => Utils.uuid(term)) } }, { name: { [Op.or]: filter.preferred_property.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] } : {}, }, { model: db.unit_types, as: 'preferred_unit_type', where: filter.preferred_unit_type ? { [Op.or]: [ { id: { [Op.in]: filter.preferred_unit_type.split('|').map(term => Utils.uuid(term)) } }, { name: { [Op.or]: filter.preferred_unit_type.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] } : {}, }, { model: db.booking_request_travelers, as: 'travelers', required: false, }, { model: db.approval_steps, as: 'approval_steps', required: false, }, { model: db.documents, as: 'documents', required: false, }, { model: db.activity_comments, as: 'comments', required: false, }, ]; if (filter) { if (filter.id) { where = { ...where, ['id']: Utils.uuid(filter.id), }; } if (filter.request_code) { where = { ...where, [Op.and]: Utils.ilike( 'booking_requests', 'request_code', filter.request_code, ), }; } if (filter.purpose_of_stay) { where = { ...where, [Op.and]: Utils.ilike( 'booking_requests', 'purpose_of_stay', filter.purpose_of_stay, ), }; } if (filter.special_requirements) { where = { ...where, [Op.and]: Utils.ilike( 'booking_requests', 'special_requirements', filter.special_requirements, ), }; } if (filter.budget_code) { where = { ...where, [Op.and]: Utils.ilike( 'booking_requests', 'budget_code', filter.budget_code, ), }; } if (filter.currency) { where = { ...where, [Op.and]: Utils.ilike( 'booking_requests', 'currency', filter.currency, ), }; } if (filter.check_in_atRange) { const [start, end] = filter.check_in_atRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, check_in_at: { ...where.check_in_at, [Op.gte]: start, }, }; } if (end !== undefined && end !== null && end !== '') { where = { ...where, check_in_at: { ...where.check_in_at, [Op.lte]: end, }, }; } } if (filter.check_out_atRange) { const [start, end] = filter.check_out_atRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, check_out_at: { ...where.check_out_at, [Op.gte]: start, }, }; } if (end !== undefined && end !== null && end !== '') { where = { ...where, check_out_at: { ...where.check_out_at, [Op.lte]: end, }, }; } } if (filter.preferred_bedroomsRange) { const [start, end] = filter.preferred_bedroomsRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, preferred_bedrooms: { ...where.preferred_bedrooms, [Op.gte]: start, }, }; } if (end !== undefined && end !== null && end !== '') { where = { ...where, preferred_bedrooms: { ...where.preferred_bedrooms, [Op.lte]: end, }, }; } } if (filter.guest_countRange) { const [start, end] = filter.guest_countRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, guest_count: { ...where.guest_count, [Op.gte]: start, }, }; } if (end !== undefined && end !== null && end !== '') { where = { ...where, guest_count: { ...where.guest_count, [Op.lte]: end, }, }; } } if (filter.max_budget_amountRange) { const [start, end] = filter.max_budget_amountRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, max_budget_amount: { ...where.max_budget_amount, [Op.gte]: start, }, }; } if (end !== undefined && end !== null && end !== '') { where = { ...where, max_budget_amount: { ...where.max_budget_amount, [Op.lte]: end, }, }; } } if (filter.active !== undefined) { where = { ...where, active: filter.active === true || filter.active === 'true' }; } if (filter.status) { where = { ...where, status: filter.status, }; } if (filter.organization) { const listItems = filter.organization.split('|').map(item => { return Utils.uuid(item) }); where = { ...where, organizationId: {[Op.or]: listItems} }; } if (filter.travelers) { const searchTerms = filter.travelers.split('|'); include = [ { model: db.booking_request_travelers, as: 'travelers_filter', required: searchTerms.length > 0, where: searchTerms.length > 0 ? { [Op.or]: [ { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, { full_name: { [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) } } ] } : undefined }, ...include, ] } if (filter.approval_steps) { const searchTerms = filter.approval_steps.split('|'); include = [ { model: db.approval_steps, as: 'approval_steps_filter', required: searchTerms.length > 0, where: searchTerms.length > 0 ? { [Op.or]: [ { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, { decision_notes: { [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) } } ] } : undefined }, ...include, ] } if (filter.documents) { const searchTerms = filter.documents.split('|'); include = [ { model: db.documents, as: 'documents_filter', required: searchTerms.length > 0, where: searchTerms.length > 0 ? { [Op.or]: [ { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, { file_name: { [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) } } ] } : undefined }, ...include, ] } if (filter.comments) { const searchTerms = filter.comments.split('|'); include = [ { model: db.activity_comments, as: 'comments_filter', required: searchTerms.length > 0, where: searchTerms.length > 0 ? { [Op.or]: [ { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, { body: { [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) } } ] } : undefined }, ...include, ] } if (filter.createdAtRange) { const [start, end] = filter.createdAtRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, ['createdAt']: { ...where.createdAt, [Op.gte]: start, }, }; } if (end !== undefined && end !== null && end !== '') { where = { ...where, ['createdAt']: { ...where.createdAt, [Op.lte]: end, }, }; } } } if (globalAccess) { delete where.organizationId; } const queryOptions = { where, include, distinct: true, order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']], transaction: options?.transaction, logging: console.log }; if (!options?.countOnly) { queryOptions.limit = limit ? Number(limit) : undefined; queryOptions.offset = offset ? Number(offset) : undefined; } try { const { rows, count } = await db.booking_requests.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, count: count }; } catch (error) { console.error('Error executing query:', error); throw error; } } static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId, currentUser) { let where = {}; if (isCustomerUser(currentUser)) { where.requested_byId = currentUser.id; } if (!globalAccess && organizationId) { where.organizationId = organizationId; } if (query) { where = { [Op.and]: [ where, { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( 'booking_requests', 'request_code', query, ), ], }, ], }; } const records = await db.booking_requests.findAll({ attributes: [ 'id', 'request_code' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, orderBy: [['request_code', 'ASC']], }); return records.map((record) => ({ id: record.id, label: record.request_code, })); } };