@tomei/rental
Version:
Tomei Rental Package
493 lines (447 loc) • 15 kB
text/typescript
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;
}
}
}