UNPKG

@tomei/rental

Version:
493 lines (447 loc) 15.4 kB
import { ClassError, ObjectBase } from '@tomei/general'; import { BookingStatusEnum } from '../../enum/booking.enum'; import { IBookingAttr } from '../../interfaces/booking-attr.interface'; import { BookingRepository } from './booking.repository'; import { DateTime } from 'luxon'; import { LoginUser } from '@tomei/sso'; import { RentalPrice } from '../rental-price/rental-price'; import { ApplicationConfig } from '@tomei/config'; import { Op, where, WhereOptions } from 'sequelize'; import { Rental } from '../rental/rental'; import { ActionEnum, Activity } from '@tomei/activity-history'; import { IBookingFindAllSearchAttr } from '../../interfaces/booking-find-all-search-attr.interface'; export class Booking extends ObjectBase { ObjectId: string; ObjectName: string; ObjectType: string = 'Booking'; TableName = 'booking_Booking'; CustomerId: string; CustomerType: string; ItemId: string; ItemType: string; PriceId: string; protected _ScheduledStartDateTime: Date; protected _ScheduledEndDateTime: Date; BookingFee: number; Status: BookingStatusEnum; CancelRemarks: string; protected _CreatedById: string; protected _CreatedAt: Date; protected _UpdatedById: string; protected _UpdatedAt: Date; protected static _Repo = new BookingRepository(); get BookingNo(): string { return this.ObjectId; } set BookingNo(value: string) { this.ObjectId = value; } get CreatedById(): string { return this._CreatedById; } get CreatedAt(): Date { return this._CreatedAt; } get UpdatedById(): string { return this._UpdatedById; } get UpdatedAt(): Date { return this._UpdatedAt; } get ScheduledStartDateTime(): Date { return this._ScheduledStartDateTime; } get ScheduledEndDateTime(): Date { return this._ScheduledEndDateTime; } async setScheduledStartDateTime(datetime: Date) { const dateTime = DateTime.fromJSDate(datetime); const currentDateTime = DateTime.now(); if (dateTime <= currentDateTime) { throw new ClassError( 'Booking', 'BookingErrMsg01', 'Booking must be for future date time.', ); } this._ScheduledStartDateTime = datetime; } async setScheduledEndDateTime(datetime: Date) { const dateTime = DateTime.fromJSDate(datetime); const currentDateTime = DateTime.now(); if (dateTime <= currentDateTime) { throw new ClassError( 'Booking', 'BookingErrMsg01', 'Booking must be for future date time.', ); } if (!this._ScheduledStartDateTime) { throw new ClassError( 'Booking', 'BookingErrMsg01', 'Booking start date time must be set before setting end date time.', ); } const startDateTime = DateTime.fromJSDate(this._ScheduledStartDateTime); if (dateTime <= startDateTime) { throw new ClassError( 'Booking', 'BookingErrMsg03', 'Booking end date time must be more than start date time.', ); } this._ScheduledEndDateTime = datetime; } protected constructor(bookingAttr?: IBookingAttr) { super(); if (bookingAttr) { this.ObjectId = bookingAttr.BookingNo; this.CustomerId = bookingAttr.CustomerId; this.CustomerType = bookingAttr.CustomerType; this.ItemId = bookingAttr.ItemId; this.ItemType = bookingAttr.ItemType; this.PriceId = bookingAttr.PriceId; this._ScheduledStartDateTime = bookingAttr.ScheduledStartDateTime; this._ScheduledEndDateTime = bookingAttr.ScheduledEndDateTime; this.BookingFee = bookingAttr.BookingFee; this.Status = bookingAttr.Status; this.CancelRemarks = bookingAttr.CancelRemarks; this._CreatedById = bookingAttr.CreatedById; this._CreatedAt = bookingAttr.CreatedAt; this._UpdatedById = bookingAttr.UpdatedById; this._UpdatedAt = bookingAttr.UpdatedAt; } } public static async init(dbTransaction?: any, bookingNo?: string) { try { if (bookingNo) { const booking = await Booking._Repo.findOne({ where: { BookingNo: bookingNo, }, transaction: dbTransaction, }); if (booking) { return new Booking(booking.get({ plain: true })); } else { throw new Error('Booking not found'); } } return new Booking(); } catch (error) { throw error; } } public async create( dbTransaction: any, loginUser: LoginUser, rentalPrice: RentalPrice, ) { //This method will create new booking record. try { //Part 1: Check Privilege const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'Booking - Create', ); if (!isPrivileged) { throw new ClassError( 'Booking', 'BookingErrMsg02', "You do not have 'Booking - Create' privilege.", ); } //Part 2: Booking Validation //check if item and booking available const isRentalAvailable = await Rental.isItemAvailable( this.ItemId, this.ItemType, this._ScheduledStartDateTime, this._ScheduledEndDateTime, dbTransaction, ); const isBookingAvailable = await Booking.isBookingItemAvailable( dbTransaction, this.ItemId, this.ItemType, this._ScheduledStartDateTime, this._ScheduledEndDateTime, ); if (!isRentalAvailable || !isBookingAvailable) { throw new ClassError( 'Booking', 'BookingErrMsg02', 'Booking is not available for the item on the chosen date.', ); } //Part 3: Insert Booking & The Agreed Rental Price const rp = await rentalPrice.create(loginUser, dbTransaction); //Set below Booking attributes: this.BookingNo = this.createId(); this.PriceId = rp.PriceId; this._CreatedById = loginUser.ObjectId; this._CreatedAt = DateTime.now().toJSDate(); this._UpdatedById = loginUser.ObjectId; this._UpdatedAt = DateTime.now().toJSDate(); //call this class repo create const data: IBookingAttr = { BookingNo: this.BookingNo, CustomerId: this.CustomerId, CustomerType: this.CustomerType, ItemId: this.ItemId, ItemType: this.ItemType, PriceId: this.PriceId, ScheduledStartDateTime: this._ScheduledStartDateTime, ScheduledEndDateTime: this._ScheduledEndDateTime, BookingFee: this.BookingFee, Status: this.Status, CancelRemarks: this.CancelRemarks, CreatedById: this._CreatedById, CreatedAt: this._CreatedAt, UpdatedById: this._UpdatedById, UpdatedAt: this._UpdatedAt, }; await Booking._Repo.create(data, { transaction: dbTransaction, }); //Part 4: Record Create Booking Activity const activity = new Activity(); activity.ActivityId = activity.createId(); activity.Action = ActionEnum.CREATE; activity.Description = 'Add Booking'; activity.EntityId = this.BookingNo; activity.EntityType = this.ObjectType; activity.EntityValueBefore = JSON.stringify({}); activity.EntityValueAfter = JSON.stringify(data); await activity.create(loginUser.ObjectId, dbTransaction); return this; } catch (error) { throw error; } } public static async isBookingItemAvailable( dbTransaction: any, itemId: string, itemType: string, startDateTime: Date, endDateTime: Date, ) { //This method will check if booking item available. try { //call this class repo findOne method const booking = await Booking._Repo.findOne({ where: { ItemId: itemId, ItemType: itemType, [Op.and]: [ { ScheduledStartDateTime: { [Op.lte]: endDateTime, }, }, { ScheduledEndDateTime: { [Op.gte]: startDateTime, }, }, ], }, transaction: dbTransaction, }); //if booking record exists, return false. if (booking) { return false; } return true; } catch (error) { throw error; } } public static async findAll( dbTransaction: any, page?: number, row?: number, search?: IBookingFindAllSearchAttr, ) { try { const queryObj: any = {}; let options: any = { transaction: dbTransaction, order: [['CreatedAt', 'DESC']], }; if (page && row) { options = { ...options, limit: row, offset: row * (page - 1), }; } if (search) { Object.entries(search).forEach(([key, value]) => { if (key === 'ScheduledStartDateTime') { queryObj[key] = { [Op.gte]: value, }; } else if (key === 'ScheduledEndDateTime') { queryObj[key] = { [Op.lte]: value, }; } else { queryObj[key] = { [Op.substring]: value, }; } }); options = { ...options, where: queryObj, }; } return await Booking._Repo.findAndCountAll(options); } catch (err) { throw err; } } public static async findOne( loginUser: LoginUser, dbTransaction: any, searchOptions: WhereOptions, ) { //This method will create new booking record. try { // Part 1: Privilege Checking // Call loginUser.checkPrivileges() by passing: // SystemCode: "<get_from_app_config>" // PrivilegeCode: "BOOKING_VIEW" const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'BOOKING_VIEW', ); if (!isPrivileged) { throw new ClassError( 'Booking', 'BookingErrMsg3', "You do not have 'BOOKING_VIEW' privilege.", ); } // Part 2: Find Booking // 2.1 Call Booking._Repo findOne by passing: // where: Params.searchOptions // dbTransaction const record = await Booking._Repo.findOne({ where: searchOptions, transaction: dbTransaction, }); // 2.2 If record found, call Booking.init() method by passing: // dbTransaction // BookingNo: record.BookingNo // Return the booking instance. // If record not found, return null. if (record) { const booking = await Booking.init(dbTransaction, record.BookingNo); return booking; } else { return null; } } catch (error) { throw error; } } public static async updateLeadToCustomer( loginUser: LoginUser, //The user performing the operation. Used for tracking who initiated the update. dbTransaction: any, //The database transaction object to ensure atomicity of the operation. LeadId: string, //The identifier for the lead whose bookings will be updated. CustomerId: string, //The identifier of the customer to replace the lead in the booking records. ) { // This method updates all booking records where LeadId matches the provided LeadId by replacing the LeadId with the CustomerId and updating the CustomerType from 'Lead' to 'Customer'. try { // Part 1: Privilege Checking // Call loginUser.checkPrivileges() with parameters: // SystemCode: Retrieve from app config. // PrivilegeCode: 'BOOKING_UPDATE'. const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'BOOKING_UPDATE', ); if (!isPrivileged) { throw new ClassError( 'Booking', 'BookingErrMsg02', "You do not have 'BOOKING_UPDATE' privilege", ); } // Part 2: Validation // Validate the LeadId and CustomerId to ensure they are not null or invalid. if (!LeadId || !CustomerId) { throw new ClassError( 'Booking', 'BookingErrMsg02', 'LeadId and CustomerId cannot be null or invalid', ); } // Part 3: Retrieve Records // Retrieve all booking records: // where // [Op.AND]: // CustomerId: Params.LeadId // CustomerType: "Lead" // dbTransaction const bookings = await Booking._Repo.findAll({ where: { [Op.and]: [{ CustomerId: LeadId }, { CustomerType: 'Lead' }], }, transaction: dbTransaction, }); // Part 5: Update Records and Record Activity // For each matching booking record: for (const booking of bookings) { // Update the CustomerId from LeadId to CustomerId. // Update the CustomerType from 'Lead' to 'Customer'. // Ensure the UpdatedById and UpdatedAt fields in the SDB booking record are updated to reflect the loginUser and the current timestamp. const EntityValueBefore = { ...booking.get({ plain: true }), }; booking.CustomerId = CustomerId; booking.CustomerType = 'Customer'; booking.UpdatedById = loginUser.ObjectId; booking.UpdatedAt = DateTime.now().toJSDate(); await booking.save({ transaction: dbTransaction, }); // Record Update Activity: // Initialise EntityValueAfter variable and set it to the updated SDB booking record. // Instantiate a new activity from the Activity class, and set: // ActivityId: activity.createId() // Action: ActionEnum.Update // Description: "Update Lead to Customer" // EntityType: "Booking" // EntityId: <UpdatedBookingRecord>.BookingId // EntityValueBefore: Stringified representation of the original SDB booking record. // EntityValueAfter: EntityValueAfter (stringified representation of the updated booking record). const activity = new Activity(); activity.ActivityId = activity.createId(); activity.Action = ActionEnum.UPDATE; activity.Description = 'Update Lead to Customer'; activity.EntityType = 'Booking'; activity.EntityId = booking.BookingNo; activity.EntityValueBefore = JSON.stringify(EntityValueBefore); activity.EntityValueAfter = JSON.stringify( booking.get({ plain: true }), ); // Call the activity create() method by passing: // dbTransaction // userId: loginUser.UserId await activity.create(loginUser.ObjectId, dbTransaction); } } catch (error) { throw error; } } }