UNPKG

@tomei/rental

Version:
1,399 lines (1,265 loc) 43.6 kB
import { IRentalAttr } from '../../interfaces/rental-attr.interface'; import { RentalStatusEnum } from '../../enum/rental-status.enum'; import { RentalRepository } from './rental.repository'; import { ClassError, ObjectBase } from '@tomei/general'; import { ApplicationConfig } from '@tomei/config'; import { AuthContext, LoginUser } from '@tomei/sso'; import { RentalPrice } from '../rental-price/rental-price'; import { Op, QueryTypes } from 'sequelize'; import { ActionEnum, Activity } from '@tomei/activity-history'; import { IRentalFindAllSearchAttr } from '../../interfaces/rental-find-all-search-attr.interface'; import { RentalAccountTypeEnum } from '../../enum/account-type.enum'; import { JointHirer } from '../joint-hirer/joint-hirer'; import { JointHirerModel } from '../../models/joint-hirer.entity'; import { AgreementRepository } from '../agreement/agreement.repository'; import * as rentalDb from '../../database'; import { RentalModel } from '../../models/rental.entity'; import { JointHirerRepository } from '../joint-hirer/joint-hirer.repository'; import { AgreementModel } from '../../models'; import { Agreement } from '../agreement/agreement'; import { AggrementStatusEnum } from '../../enum/aggrement-status.enum'; import { AgreementSignatureRepository } from '../agreement-signature/agreement-signature.repository'; import { AgreementSignatureStatusEnum } from '../../enum/agreement-signature-status.enum'; import { HirerTypeEnum } from '../../enum/hirer-type.enum'; import { AgreementHistoryRepository } from '../agreement-history/agreement-history.repository'; import { DocType, Document, IAccountSystem } from '@tomei/finance'; import { ItemClassMap } from '../../ClassMappings/ItemClassMap'; import { AgreementSignatureVerificationMethodEnum } from '../../enum/agreement-signature-verification-method.enum'; export class Rental extends ObjectBase { ObjectId: string; ObjectName: string; ObjectType = 'Rental'; TableName: string; CustomerId: string; CustomerType: string; ItemId: string; ItemType: string; PriceId: string; StartDateTime: Date; EndDateTime: Date; CancelRemarks: string; TerminateRemarks: string; AgreementNo: string; AccountType: RentalAccountTypeEnum; JointHirers: JointHirer[] = []; Invoices: Document[] = []; Item: any; protected _Status: RentalStatusEnum | string; protected _EscheatmentYN: string = 'N'; protected _CreatedById: string; protected _CreatedAt: Date; protected _UpdatedById: string; protected _UpdatedAt: Date; protected static _Repo = new RentalRepository(); protected static _AgreementRepo = new AgreementRepository(); protected static _JointHirerRepo = new JointHirerRepository(); protected static _AgreementSignatureRepo = new AgreementSignatureRepository(); protected static _AgreementHistoryRepo = new AgreementHistoryRepository(); get RentalId(): string { return this.ObjectId; } set RentalId(value: string) { this.ObjectId = value; } get Status(): RentalStatusEnum | string { return this._Status; } get EscheatmentYN(): string { return this._EscheatmentYN; } get CreatedById(): string { return this._CreatedById; } get CreatedAt(): Date { return this._CreatedAt; } get UpdatedById(): string { return this._UpdatedById; } get UpdatedAt(): Date { return this._UpdatedAt; } private setTerminated() { this._Status = RentalStatusEnum.TERMINATED; } protected constructor(rentalAttr?: IRentalAttr) { super(); if (rentalAttr) { this.RentalId = rentalAttr.RentalId; this.CustomerId = rentalAttr.CustomerId; this.CustomerType = rentalAttr.CustomerType; this.ItemId = rentalAttr.ItemId; this.ItemType = rentalAttr.ItemType; this.PriceId = rentalAttr.PriceId; this.StartDateTime = rentalAttr.StartDateTime; this.EndDateTime = rentalAttr.EndDateTime; this.CancelRemarks = rentalAttr.CancelRemarks; this.TerminateRemarks = rentalAttr.TerminateRemarks; this.AgreementNo = rentalAttr.AgreementNo; this.AccountType = rentalAttr.AccountType; this._Status = rentalAttr.Status; this._EscheatmentYN = rentalAttr.EscheatmentYN; this._CreatedById = rentalAttr.CreatedById; this._CreatedAt = rentalAttr.CreatedAt; this._UpdatedById = rentalAttr.UpdatedById; this._UpdatedAt = rentalAttr.UpdatedAt; } } public static async init(dbTransaction?: any, rentalId?: string) { try { if (rentalId) { const rental = await Rental._Repo.findByPk(rentalId, dbTransaction); if (rental) { return new Rental(rental); } else { throw new ClassError('Rental', 'RentalErrMsg00', 'Rental Not Found'); } } return new Rental(); } catch (error) { throw new ClassError( 'Rental', 'RentalErrMsg00', 'Failed To Initialize Price', ); } } /** * Create a new Rental record. * @param {RentalPrice} rentalPrice - The rental pricing information. * @param {LoginUser} loginUser - The user initiating the creation. * @param {any} [dbTransaction] - Optional database transaction for atomic operations. * @throws {ClassError} Throws an error if: * 1. loginUser does not have 'Rental - Create' privilege. * 2. Rental item is not available at the current date. * @returns {Promise<any>} A Promise resolving to the created rental record. */ public async create( rentalPrice: RentalPrice, loginUser: LoginUser, jointHirers?: JointHirer[], dbTransaction?: any, ): Promise<any> { try { /*Part 1: Check Privilege*/ const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'Rental - Create', ); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'Rental - Create' privilege.", ); } /*Part 2: Make Sure Rental Item Available*/ const isItemAvailable = await Rental.isItemAvailable( this.ItemId, this.ItemType, this.StartDateTime, this.EndDateTime, dbTransaction, ); if (!isItemAvailable) { throw new ClassError( 'Rental', 'RentalErrMsg02', 'Rental Item is not available at current date.', ); } // Part 3: Create Rental Agreement Record // Call Rental._AgreementRepo create method by passing: // AgreementNo: this.AgreementNo // Status: "Not Generated" // dbTransaction await Rental._AgreementRepo.create( { AgreementNo: this.AgreementNo, Status: 'Not Generated', }, { transaction: dbTransaction, }, ); //Create a record into rental_AgreementSignature table //Create the main customer's signature record await Rental._AgreementSignatureRepo.create( { SignatureId: this.createId(), AgreementNo: this.AgreementNo, Party: HirerTypeEnum.PRIMARY, PartyId: this.CustomerId, PartyType: 'Customer', SignatureStatus: AgreementSignatureStatusEnum.PENDING, SignedAt: new Date(), VerificationMethod: AgreementSignatureVerificationMethodEnum.FINGERPRINT, VerificationJustification: '', VerifiedById: loginUser.ObjectId, CreatedById: loginUser.ObjectId, CreatedAt: new Date(), UpdatedById: loginUser.ObjectId, UpdatedAt: new Date(), }, { transaction: dbTransaction }, ); // Create signatures record for joint hirers if any if (jointHirers && jointHirers.length > 0) { for (const jointHirer of jointHirers) { await Rental._AgreementSignatureRepo.create( { SignatureId: this.createId(), AgreementNo: this.AgreementNo, Party: HirerTypeEnum.JOINT, PartyId: jointHirer.CustomerId, PartyType: 'Customer', SignatureStatus: AgreementSignatureStatusEnum.PENDING, SignedAt: new Date(), VerificationMethod: AgreementSignatureVerificationMethodEnum.FINGERPRINT, VerificationJustification: '', VerifiedById: loginUser.ObjectId, CreatedById: loginUser.ObjectId, CreatedAt: new Date(), UpdatedById: loginUser.ObjectId, UpdatedAt: new Date(), }, { transaction: dbTransaction }, ); } } /*Part 4: Insert Rental & The Agreed Rental Price*/ if (!rentalPrice.PriceId) { const rentalPriceData = await rentalPrice.create( loginUser, dbTransaction, ); this.PriceId = rentalPriceData.PriceId; } else { this.PriceId = rentalPrice.PriceId; } this.RentalId = this.createId(); // if (!this.Status) { this._Status = RentalStatusEnum.PENDING_SIGNING; // } this._CreatedById = loginUser.ObjectId; this._UpdatedById = loginUser.ObjectId; this._CreatedAt = new Date(); this._UpdatedAt = new Date(); const data = { RentalId: this.RentalId, CustomerId: this.CustomerId, CustomerType: this.CustomerType, ItemId: this.ItemId, ItemType: this.ItemType, PriceId: this.PriceId, StartDateTime: this.StartDateTime, EndDateTime: this.EndDateTime, CancelRemarks: this.CancelRemarks, TerminateRemarks: this.TerminateRemarks, AgreementNo: this.AgreementNo, AccountType: this.AccountType, Status: this.Status, EscheatmentYN: this.EscheatmentYN, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; await Rental._Repo.create(data, { transaction: dbTransaction, }); //For every data inside Param.jointHirers, update the rentalId and call jointHirer.create if (jointHirers) { for (let i = 0; i < jointHirers.length; i++) { jointHirers[i].RentalId = this.RentalId; await jointHirers[i].create(loginUser, dbTransaction); this.JointHirers.push(jointHirers[i]); } } /*Part 5: Record Create Rental Activity*/ const activity = new Activity(); activity.ActivityId = activity.createId(); activity.Action = ActionEnum.CREATE; activity.Description = 'Add Rental'; activity.EntityType = 'Rental'; activity.EntityId = this.RentalId; activity.EntityValueBefore = JSON.stringify({}); activity.EntityValueAfter = JSON.stringify(data); await activity.create(loginUser.ObjectId, dbTransaction); /*Part 6: Return Result*/ return this; } catch (error) { throw error; } } public static async isItemAvailable( itemId: string, itemType: string, startDateTime: Date, endDateTime: Date, dbTransaction?: any, ) { try { const rental = await Rental._Repo.findOne({ where: { ItemId: itemId, ItemType: itemType, StartDateTime: { [Op.lte]: endDateTime, }, EndDateTime: { [Op.gte]: startDateTime, }, }, transaction: dbTransaction, }); if (!rental) { // No rental found → item is available return true; } if ( rental.Status === RentalStatusEnum.TERMINATED || rental.Status === RentalStatusEnum.CANCELLED ) { // Rental found but status is Terminated or Cancelled → item is available return true; } // Rental exists and is active → item is not available return false; } catch (error) { throw error; } } public static async findAll( authContext: AuthContext, dbTransaction: any, page?: number, row?: number, search?: IRentalFindAllSearchAttr, ) { try { const systemCode = await ApplicationConfig.getComponentConfigValue('system-code'); if ('loginUser' in authContext) { const isPrivileged = await authContext.loginUser.checkPrivileges( systemCode, 'Rental - View', ); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'Rental - View' privilege.", ); } } 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 === 'StartDateTime') { queryObj[key] = { [Op.gte]: value, }; } else if (key === 'EndDateTime') { queryObj[key] = { [Op.lte]: value, }; } else if (key === 'CustomerId') { queryObj[key] = { [Op.substring]: value, }; options.include = [ { model: JointHirerModel, required: false, where: { CustomerId: { [Op.substring]: value, }, }, }, ]; } else { queryObj[key] = { [Op.substring]: value, }; } }); if (options?.include?.length > 1) { options.include.push({ model: AgreementModel, required: false, }); } else { options.include = [ { model: AgreementModel, required: false, }, ]; } options = { ...options, where: queryObj, }; } return await Rental._Repo.findAndCountAll(options); } catch (err) { throw err; } } public static async checkActiveByItemId( loginUser: LoginUser, itemId: string, itemType: string, dbTransaction?: any, ) { try { const systemCode = await ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'Rental - View', ); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'Rental - View' privilege.", ); } const queryObj: any = { ItemId: itemId, ItemType: itemType, Status: RentalStatusEnum.ACTIVE, }; const options: any = { transaction: dbTransaction, where: queryObj, }; const rental = await Rental._Repo.findOne(options); if (rental) { return true; } else { return false; } } catch (err) { throw err; } } /** * Sets the StartDateTime property of the Rental class as custom setter. * @param {Date} startDateTime - The start date and time of the rental. * @throws {ClassError} Throws an error if: * 1. startDateTime is a past date. * 2. startDateTime is more than the EndDateTime. * @returns {void} */ setStartDateTime(startDateTime: Date): void { const startDateTimeObject = new Date(startDateTime); startDateTimeObject.setHours(0, 0, 0, 0); /*Part 1: Make Sure Future Date Time*/ if (startDateTimeObject < new Date(new Date().setHours(0, 0, 0, 0))) { throw new ClassError( 'Rental', 'RentalErrMsg02', 'StartDateTime cannot be a past datetime.', ); } /*Part 2: Make Sure Less Than End Date Time*/ if (this.EndDateTime && startDateTimeObject > this.EndDateTime) { throw new ClassError( 'Rental', 'RentalErrMsg03', 'StartDateTime cannot be more than EndDateTime.', ); } /*Part 3: Set Status Whether Reserved or Active*/ if (startDateTimeObject > new Date(new Date().setHours(0, 0, 0, 0))) { this._Status = RentalStatusEnum.RESERVED; } else { this._Status = RentalStatusEnum.ACTIVE; } } public async periodEndProcess( loginUser: LoginUser, dbTransaction: any, stockInventory: any, ) { try { // Privilege Checking const systemCode = await ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'Rental – PeriodEndProcess', ); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg04', "You do not have 'Rental - PeriodEndProcess' privilege.", ); } // Validate Existing Rental if (this.AgreementNo === undefined || this.AgreementNo === null) { throw new ClassError( 'Rental', 'RentalErrMsg05', 'Rental must be existing rental.', ); } // Validate Rental End Period if (this.EndDateTime === new Date(new Date().setHours(0, 0, 0, 0))) { throw new ClassError( 'Rental', 'RentalErrMsg06', 'Rental period is not ending today.', ); } // Mark Rental Item as Available await stockInventory.setAvailable(loginUser, dbTransaction); // Capture Record Info Before Changes const entityValueBefore = { RentalId: this.RentalId, CustomerId: this.CustomerId, CustomerType: this.CustomerType, ItemId: this.ItemId, ItemType: this.ItemType, PriceId: this.PriceId, StartDateTime: this.StartDateTime, EndDateTime: this.EndDateTime, CancelRemarks: this.CancelRemarks, TerminateRemarks: this.TerminateRemarks, AgreementNo: this.AgreementNo, AccountType: this.AccountType, Status: this.Status, EscheatmentYN: this.EscheatmentYN, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; // Mark Rental as Terminated and Capture Updater Info this.setTerminated(); this._UpdatedById = loginUser.ObjectId; this._UpdatedAt = new Date(); // Capture Record Info after Changes const entityValueAfter = { RentalId: this.RentalId, CustomerId: this.CustomerId, CustomerType: this.CustomerType, ItemId: this.ItemId, ItemType: this.ItemType, PriceId: this.PriceId, StartDateTime: this.StartDateTime, EndDateTime: this.EndDateTime, CancelRemarks: this.CancelRemarks, TerminateRemarks: this.TerminateRemarks, AgreementNo: this.AgreementNo, AccountType: this.AccountType, Status: this.Status, EscheatmentYN: this.EscheatmentYN, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; // Record the Update Activity const activity = new Activity(); activity.ActivityId = activity.createId(); activity.Action = ActionEnum.UPDATE; activity.Description = 'Set Rental as Terminated'; activity.EntityType = 'Rental'; activity.EntityId = this.RentalId; activity.EntityValueBefore = JSON.stringify(entityValueBefore); activity.EntityValueAfter = JSON.stringify(entityValueAfter); await activity.create(loginUser.ObjectId, dbTransaction); return this; } catch (err) { throw err; } } async getCustomerActiveRentals( loginUser: LoginUser, dbTransaction: any, CustomerId: string, ): Promise<IRentalAttr[]> { try { // Part 1: Privilege Checking // Call loginUser.checkPrivileges() by passing: // SystemCode: <get_from_app_config> // PrivilegeCode: "RENTAL_VIEW" const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = loginUser.checkPrivileges(systemCode, 'RENTAL_VIEW'); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'Rental - View' privilege.", ); } // Part 2: Retrieve Rentals and Returns // Call Rental._Repo findAll method by passing: // where: // Status: "Active" // [Op.OR]: // CustomerId: Params.CustomerId // '$JointHirers.CustomerId$': Params.CustomerId // include: // model: JointHirerModel // attributes: [] const query = ` SELECT r.* FROM rental_Rental r LEFT JOIN rental_JointHirer jh ON r.RentalId = jh.RentalId WHERE r.Status = 'Active' AND ( r.CustomerId = '${CustomerId}' OR jh.CustomerId = '${CustomerId}' ) GROUP BY r.RentalId `; const db = rentalDb.getConnection(); const result = await db.query(query, { type: QueryTypes.SELECT, transaction: dbTransaction, model: RentalModel, mapToModel: true, }); // Format the returned records const records: IRentalAttr[] = result.map((record: RentalModel) => { return { RentalId: record.RentalId, CustomerId: record.CustomerId, CustomerType: record.CustomerType, ItemId: record.ItemId, ItemType: record.ItemType, PriceId: record.PriceId, StartDateTime: record.StartDateTime, EndDateTime: record.EndDateTime, CancelRemarks: record.CancelRemarks, TerminateRemarks: record.TerminateRemarks, AgreementNo: record.AgreementNo, AccountType: record.AccountType, Status: record.Status, EscheatmentYN: record.EscheatmentYN, CreatedById: record.CreatedById, CreatedAt: record.CreatedAt, UpdatedById: record.UpdatedById, UpdatedAt: record.UpdatedAt, }; }); // Return the returned records. return records; } catch (error) { throw error; } } async getJointHirers(dbTransaction: any) { try { if (!this.RentalId) { throw new ClassError('Rental', 'RentalErrMsg01', 'RentalId is missing'); } if (this.AccountType !== RentalAccountTypeEnum.JOINT) { throw new ClassError( 'Rental', 'RentalErrMsg07', 'This rental does not have joint hirers.', ); } const jointHirers = await Rental._JointHirerRepo.findAll({ where: { RentalId: this.RentalId, }, transaction: dbTransaction, }); // Map the jointHirers to JointHirer instances const jointHirerInstances = Promise.all( jointHirers.map(async (hirer: JointHirerModel) => { return await JointHirer.init(hirer.HirerId, dbTransaction); }), ); return jointHirerInstances; } catch (error) { console.error('Error in getJointHirers:', error); throw error; } } async getCustomerIds(loginUser: LoginUser, dbTransaction: any) { try { // Part 1: Privilege Checking // Call loginUser.checkPrivileges() by passing: // SystemCode: <get_from_app_config> // PrivilegeCode: "RENTAL_VIEW" const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = loginUser.checkPrivileges(systemCode, 'RENTAL_VIEW'); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'Rental - View' privilege.", ); } // Part 2: Validation // Make sure this.RentalId exists, if not throw new ClassError by passing if (!this.RentalId) { throw new ClassError('Rental', 'RentalErrMsg01', 'RentalId is missing'); } //Part 3: Retrieve Listing and Returns // 3.1 Call Rental._JointHirerRepo findAll method by passing: // where: // RentalId: this.RentalId // dbTransaction const options: any = { where: { RentalId: this.RentalId, }, transaction: dbTransaction, }; const joinHirers = await Rental._JointHirerRepo.findAll(options); // 3.2 Create a new array variable and set its value to joint hirer and Rental.CustomerId // array translated to only CustomerId. Then, append this.CustomerId to the array. const customerIds: string[] = [this.CustomerId]; for (let i = 0; i < joinHirers.length; i++) { const jointHirer = joinHirers[i]; customerIds.push(jointHirer.CustomerId); } // 3.3 Return the array. return customerIds; } catch (error) { throw error; } } async signAgreement( loginUser: LoginUser, party: string, PartyId: string, PartyType: string, dbTransaction: any, statusAfterSign?: RentalStatusEnum | string, method?: AgreementSignatureVerificationMethodEnum, justification?: string, ) { try { // Part 1: Privilege Checking // Call loginUser.checkPrivileges() by passing: // SystemCode: <get_from_app_config> // PrivilegeCode: "RENTAL_SIGN" const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = loginUser.checkPrivileges(systemCode, 'RENTAL_SIGN'); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'RENTAL_SIGN' privilege.", ); } // Part 2: Validation // Make sure this.Status is "Pending Signing" and agreementStatus is "Generated", if not throw new ClassError const agreement = await Agreement.init(this.AgreementNo, dbTransaction); if ( this.Status !== RentalStatusEnum.PENDING_SIGNING && agreement.Status !== AggrementStatusEnum.GENERATED ) { throw new ClassError( 'Rental', 'RentalErrMsg01', 'Rental is not ready to be signed yet.', ); } //Make sure CustomerId inside the listing and SignatureStatus is "Pending" const signatureList = await Agreement.getSignatureList( loginUser, this.AgreementNo, dbTransaction, ); const customerSignature = signatureList.find( (sig) => sig.PartyId === PartyId && sig.PartyType === PartyType && sig.Party === party && sig.SignatureStatus === AgreementSignatureStatusEnum.PENDING, ); if (!customerSignature) { throw new ClassError( 'Rental', 'RentalErrMsg01', 'Signature is not pending.', ); } //Update rental_AgreementSignature const signaturePayload = { SignatureStatus: AgreementSignatureStatusEnum.SIGNED, SignedAt: new Date(), UpdatedById: loginUser.ObjectId, UpdatedAt: new Date(), VerificationMethod: method, VerificationJustification: justification, VerifiedById: loginUser.ObjectId, }; await Rental._AgreementSignatureRepo.update(signaturePayload, { where: { AgreementNo: this.AgreementNo, Party: party, PartyId: PartyId, PartyType: PartyType, }, transaction: dbTransaction, }); const signatureActivity = new Activity(); signatureActivity.ActivityId = signatureActivity.createId(); signatureActivity.Action = ActionEnum.UPDATE; signatureActivity.Description = 'Update hirer signature'; signatureActivity.EntityType = 'AgreementSignature'; signatureActivity.EntityId = this.AgreementNo; signatureActivity.EntityValueBefore = JSON.stringify(customerSignature); signatureActivity.EntityValueAfter = JSON.stringify(signaturePayload); await signatureActivity.create(loginUser.ObjectId, dbTransaction); const updatedSignatureList = await Agreement.getSignatureList( loginUser, this.AgreementNo, dbTransaction, ); const pendingSignatures = updatedSignatureList.filter( (sig) => sig.SignatureStatus === 'Pending', ); if (pendingSignatures.length > 0) { return; } //Part 3: Update Agreement Status // 3.1 Set EntityValueBefore to current agreement instance. const entityValueAgreementBefore = { AgreementNo: agreement.AgreementNo, Status: agreement.Status, DateSigned: agreement.DateSigned, }; // 3.2 Set below agreement attributes: // DateSigned: current date & time // Status: "Signed" const payload = { DateSigned: new Date(), Status: AggrementStatusEnum.SIGNED, }; // 3.3 Call Rental._AgreementRepo update() await Rental._AgreementRepo.update(payload, { where: { AgreementNo: this.AgreementNo, }, transaction: dbTransaction, }); const entityValueAgreementAfter = { entityValueAgreementBefore, ...payload, }; // Part 4: Record Update Agreement Activity const activity = new Activity(); activity.ActivityId = activity.createId(); activity.Action = ActionEnum.UPDATE; activity.Description = 'Update agreement'; activity.EntityType = 'RentalAgreement'; activity.EntityId = this.AgreementNo; activity.EntityValueBefore = JSON.stringify(entityValueAgreementBefore); activity.EntityValueAfter = JSON.stringify(entityValueAgreementAfter); await activity.create(loginUser.ObjectId, dbTransaction); // Part 5: Update Rental Status // 5.1 Set EntityValueBefore to current rental instance. const entityValueRentalBefore = { RentalId: this.RentalId, CustomerId: this.CustomerId, CustomerType: this.CustomerType, ItemId: this.ItemId, ItemType: this.ItemType, PriceId: this.PriceId, StartDateTime: this.StartDateTime, EndDateTime: this.EndDateTime, CancelRemarks: this.CancelRemarks, TerminateRemarks: this.TerminateRemarks, AgreementNo: this.AgreementNo, AccountType: this.AccountType, Status: this.Status, EscheatmentYN: this.EscheatmentYN, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; // 5.2: Set below rental attributes: // Status: "Pending Key Collection" // UpdatedById: loginUser.ObjectId // UpdatedAt: current date & time this._Status = statusAfterSign ? statusAfterSign : RentalStatusEnum.ACTIVE; const payloadRental = { Status: this._Status, UpdatedById: loginUser.ObjectId, UpdatedAt: new Date(), }; // 5.3: Call Rental._Repo update() method by passing: // populate rental instance attributes // dbTransaction await Rental._Repo.update(payloadRental, { where: { RentalId: this.RentalId, }, transaction: dbTransaction, }); const entityValueRentalAfter = { entityValueRentalBefore, ...payloadRental, }; // Part 6: Record Update Agreement Activity const rentalActivity = new Activity(); rentalActivity.ActivityId = activity.createId(); rentalActivity.Action = ActionEnum.UPDATE; rentalActivity.Description = 'Sign rental agreement'; rentalActivity.EntityType = 'Rental'; rentalActivity.EntityId = this.RentalId; rentalActivity.EntityValueBefore = JSON.stringify( entityValueRentalBefore, ); rentalActivity.EntityValueAfter = JSON.stringify(entityValueRentalAfter); await rentalActivity.create(loginUser.ObjectId, dbTransaction); } catch (error) { throw error; } } async generateAgreement( loginUser: LoginUser, dbTransaction: any, MediaId: string, ) { //This method will generate a new rental agreement. try { // Part 1: Privilege Checking // Call loginUser.checkPrivileges() by passing: // SystemCode: "<get_from_app_config>" // PrivilegeCode: "RENTAL_AGREEMENT_CREATE" const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = loginUser.checkPrivileges( systemCode, 'RENTAL_AGREEMENT_CREATE', ); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'RENTAL_AGREEMENT_CREATE' privilege.", ); } // Part 2: Validation // Instantiate existing RentalAgreement by passing: // dbTransaction // AgreementNo: this.AgreementNo // Make sure Params.MediaId exists, if not throw new ClassError by passing: // Classname: "Rental" // MessageCode: "RentalErrMsg0X" // Message: "MediaId is required." const agreement = await Agreement.init(this.AgreementNo, dbTransaction); if (!MediaId) { throw new ClassError( 'Rental', 'RentalErrMsg0X', 'MediaId is required.', ); } // Part 3: Update Agreement Record // Set EntityValueBefore to the agreement instance. // Set below agreement attributes: // Status: "Generated" // MediaId: Params.MediaId const EntityValueBefore = agreement; agreement.Status = AggrementStatusEnum.GENERATED; agreement.MediaId = MediaId; // Call Rental._AgreementRepo update method by passing: // Status: agreement.Status // MediaId: agreement.MediaId // dbTransaction await Rental._AgreementRepo.update( { Status: agreement.Status, MediaId: agreement.MediaId, }, { where: { AgreementNo: this.AgreementNo, }, transaction: dbTransaction, }, ); // Part 4: Record Update Agreement Activity // Initialise EntityValueAfter variable and set to agreement instance const EntityValueAfter = agreement; // Instantiate new activity from Activity class, call createId() method, then set: const activity = new Activity(); // Action: ActionEnum.UPDATE // Description: "Generate agreement." // EntityType: "RentalAgreement" // EntityId: this.AgreementNo // EntityValueBefore: EntityValueBefore // EntityValueAfter: EntityValueAfter activity.ActivityId = activity.createId(); activity.Action = ActionEnum.UPDATE; activity.Description = 'Generate agreement.'; activity.EntityType = 'RentalAgreement'; activity.EntityId = this.AgreementNo; activity.EntityValueBefore = JSON.stringify(EntityValueBefore); activity.EntityValueAfter = JSON.stringify(EntityValueAfter); // Call new activity create method by passing: // dbTransaction // userId: loginUser.ObjectId await activity.create(loginUser.ObjectId, dbTransaction); // Part 5: Add Record To Agreement History Table // Make sure there is a private static readonly _AgreementHistoryRepo variable. // Set newRecord to an object with below key-value: // AgreementNo: this.AgreementNo // MediaId: params.MediaId // ActivityCompleted: "Agreement Generated" // CreatedAt: Current Timestamp // Use _AgreementHistoryRepo.create() method and pass newRecord and dbTransaction. const agreementHistory = await Rental._AgreementHistoryRepo.create( { AgreementNo: this.AgreementNo, MediaId, ActivityCompleted: 'Agreement Generated', CreatedAt: new Date(), }, { transaction: dbTransaction }, ); // Part 6: Record Agreement History Activity // Initialise EntityValueAfter variable and set to newRecord from previous part. // Instantiate new activity from Activity class, call createId() method, then set: // Action: ActionEnum.CREATE // Description: "Generate agreement." // EntityType: "RentalAgreementHistory" // EntityId: HistoryId from previous part. // EntityValueBefore: empty object. // EntityValueAfter: EntityValueAfter // Call new activity create method by passing: // dbTransaction // userId: loginUser.ObjectId const entityValueAfter = agreementHistory.get({ plain: true }); const activityHistory = new Activity(); activityHistory.ActivityId = activityHistory.createId(); activityHistory.Action = ActionEnum.CREATE; activityHistory.Description = 'Generate agreement.'; activityHistory.EntityType = 'RentalAgreementHistory'; activityHistory.EntityId = agreementHistory.HistoryId.toString(); activityHistory.EntityValueBefore = JSON.stringify({}); activityHistory.EntityValueAfter = JSON.stringify(entityValueAfter); await activityHistory.create(loginUser.ObjectId, dbTransaction); } catch (error) { throw error; } } public toJSON(): IRentalAttr { return { RentalId: this.RentalId, CustomerId: this.CustomerId, CustomerType: this.CustomerType, ItemId: this.ItemId, ItemType: this.ItemType, PriceId: this.PriceId, StartDateTime: this.StartDateTime, EndDateTime: this.EndDateTime, Status: this.Status, CancelRemarks: this.CancelRemarks, TerminateRemarks: this.TerminateRemarks, EscheatmentYN: this.EscheatmentYN, AgreementNo: this.AgreementNo, AccountType: this.AccountType, CreatedById: this.CreatedById, CreatedAt: this.CreatedAt, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }; } public async getInvoices( loginUser: LoginUser, dbTransaction: any, accountingSystem?: IAccountSystem, ) { try { // Part 1: Privilege Checking // Call loginUser.checkPrivileges() by passing: // SystemCode: <get_from_app_config> // PrivilegeCode: "RENTAL_VIEW_INVOICE" const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = loginUser.checkPrivileges( systemCode, 'RENTAL_VIEW_INVOICE', ); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'RENTAL_VIEW_INVOICE' privilege.", ); } // Part 2: Validation // Make sure this._ObjectId exists, if not throw new ClassError by passing if (!this.ObjectId) { throw new ClassError( 'Rental', 'RentalErrMsg01', 'ObjectId is missing.', ); } // Part 3: Check Existing Invoices if (this.Invoices.length > 0) { return this.Invoices; } // Part 4: Retrieve Invoices and Returns const search = { DocType: DocType.INVOICE, RelatedObjectId: this.ObjectId, RelatedObjectType: this.ObjectType, }; const documents = await Document.findAll( loginUser, dbTransaction, undefined, undefined, search, undefined, accountingSystem, ); this.Invoices = documents.rows.map((doc) => { const document = new Document(dbTransaction, doc); return document; }); return this.Invoices; } catch (error) { throw error; } } public async getItem(dbTransaction: any): Promise<any> { if (this.Item) { return this.Item; } else { const ItemClass = ItemClassMap[this.ItemType]; if (!ItemClass || typeof ItemClass.init !== 'function') { throw new Error(`Invalid or unregistered ItemType: ${this.ItemType}`); } ItemClass._Repo = ItemClass._Repo || ItemClass.getRepo(); if (!ItemClass._Repo) { throw new Error( `ItemType ${this.ItemType} does not have a repository.`, ); } const itemInstance = await ItemClass.init(this.ItemId, dbTransaction); if (!itemInstance) { throw new Error(`${this.ItemType} not found with ID ${this.ItemId}`); } this.Item = itemInstance; // Make sure item has setAvailable method if (typeof this.Item.setAvailable !== 'function') { throw new Error(`${this.ItemType} does not implement setAvailable()`); } return this.Item; } } public async cancel( loginUser: LoginUser, dbTransaction: any, remarks: string, ): Promise<Rental> { try { // Part 1: Privilege Checking const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'RENTAL_CANCEL', ); if (!isPrivileged) { throw new ClassError( 'Rental', 'RentalErrMsg01', "You do not have 'RENTAL_CANCEL' privilege.", ); } // Part 2: Validation if (!this.ObjectId) { throw new ClassError( 'Rental', 'RentalErrMsg01', 'Rental not found. Rental Id is empty.', ); } if (!remarks) { throw new ClassError( 'Rental', 'RentalErrMsg01', 'Cancellation remarks is required.', ); } // Part 3: Capture Record Info Before Changes const entityValueBefore = this.toJSON(); // Part 4: Update Rental Status and Remarks this._Status = RentalStatusEnum.CANCELLED; this.CancelRemarks = remarks; this._UpdatedById = loginUser.ObjectId; this._UpdatedAt = new Date(); // Part 5: Update Rental Record await Rental._Repo.update( { Status: this.Status, CancelRemarks: this.CancelRemarks, UpdatedById: this.UpdatedById, UpdatedAt: this.UpdatedAt, }, { where: { RentalId: this.RentalId, }, transaction: dbTransaction, }, ); // Part 6: Mark Item as Available const item = await this.getItem(dbTransaction); await item.setAvailable(loginUser, dbTransaction); // Part 7: Capture Record Info After Changes const entityValueAfter = this.toJSON(); // Part 8: Record Activity const activity = new Activity(); activity.ActivityId = activity.createId(); activity.Action = ActionEnum.UPDATE; activity.Description = 'Rental Cancellation'; activity.EntityType = this.ObjectType; activity.EntityId = this.ObjectId; activity.EntityValueBefore = JSON.stringify(entityValueBefore); activity.EntityValueAfter = JSON.stringify(entityValueAfter); await activity.create(loginUser.ObjectId, dbTransaction); return this; } catch (error) { throw error; } } }