@tomei/rental
Version:
Tomei Rental Package
1,399 lines (1,265 loc) • 43.6 kB
text/typescript
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;
}
}
}