@tomei/rental 0.17.0 → 0.17.3-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/.husky/commit-msg +0 -6
  2. package/dist/src/components/agreement-signature/hirer-signature.d.ts +27 -0
  3. package/dist/src/components/agreement-signature/hirer-signature.js +112 -0
  4. package/dist/src/components/agreement-signature/hirer-signature.js.map +1 -0
  5. package/dist/src/components/agreement-signature/hirer-signature.repository.d.ts +8 -0
  6. package/dist/src/components/agreement-signature/hirer-signature.repository.js +67 -0
  7. package/dist/src/components/agreement-signature/hirer-signature.repository.js.map +1 -0
  8. package/dist/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.d.ts +32 -0
  9. package/dist/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.js +121 -0
  10. package/dist/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.js.map +1 -0
  11. package/dist/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.repository.d.ts +9 -0
  12. package/dist/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.repository.js +79 -0
  13. package/dist/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.repository.js.map +1 -0
  14. package/dist/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.d.ts +22 -0
  15. package/dist/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.js +104 -0
  16. package/dist/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.js.map +1 -0
  17. package/dist/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.repository.d.ts +9 -0
  18. package/dist/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.repository.js +80 -0
  19. package/dist/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.repository.js.map +1 -0
  20. package/dist/src/components/hirer-change-request-signature/hirer-change-request-signature.d.ts +30 -0
  21. package/dist/src/components/hirer-change-request-signature/hirer-change-request-signature.js +224 -0
  22. package/dist/src/components/hirer-change-request-signature/hirer-change-request-signature.js.map +1 -0
  23. package/dist/src/components/hirer-change-request-signature/hirer-change-request-signature.repository.d.ts +9 -0
  24. package/dist/src/components/hirer-change-request-signature/hirer-change-request-signature.repository.js +80 -0
  25. package/dist/src/components/hirer-change-request-signature/hirer-change-request-signature.repository.js.map +1 -0
  26. package/dist/src/components/hirer-signature/hirer-signature.d.ts +2 -2
  27. package/dist/src/components/hirer-signature/hirer-signature.js +1 -1
  28. package/dist/src/components/hirer-signature/hirer-signature.js.map +1 -1
  29. package/dist/src/components/hirer-signature/hirer-signature.repository.d.ts +1 -1
  30. package/dist/src/components/hirer-signature/hirer-signature.repository.js +6 -6
  31. package/dist/src/components/hirer-signature/hirer-signature.repository.js.map +1 -1
  32. package/dist/src/components/joint-hirer/joint-hirer.d.ts +3 -0
  33. package/dist/src/components/joint-hirer/joint-hirer.js +46 -0
  34. package/dist/src/components/joint-hirer/joint-hirer.js.map +1 -1
  35. package/dist/src/components/rental/rental.d.ts +1 -0
  36. package/dist/src/components/rental/rental.js +34 -4
  37. package/dist/src/components/rental/rental.js.map +1 -1
  38. package/dist/src/components/rental-hirer-change-request/rental-hirer-change-request.d.ts +70 -0
  39. package/dist/src/components/rental-hirer-change-request/rental-hirer-change-request.js +570 -0
  40. package/dist/src/components/rental-hirer-change-request/rental-hirer-change-request.js.map +1 -0
  41. package/dist/src/components/rental-hirer-change-request/rental-hirer-change-request.repository.d.ts +9 -0
  42. package/dist/src/components/rental-hirer-change-request/rental-hirer-change-request.repository.js +79 -0
  43. package/dist/src/components/rental-hirer-change-request/rental-hirer-change-request.repository.js.map +1 -0
  44. package/dist/src/database.js +8 -0
  45. package/dist/src/database.js.map +1 -1
  46. package/dist/src/enum/index.d.ts +4 -1
  47. package/dist/src/enum/index.js +7 -1
  48. package/dist/src/enum/index.js.map +1 -1
  49. package/dist/src/enum/rental-hirer-change-request-hirer-role.d.ts +4 -0
  50. package/dist/src/enum/rental-hirer-change-request-hirer-role.js +9 -0
  51. package/dist/src/enum/rental-hirer-change-request-hirer-role.js.map +1 -0
  52. package/dist/src/enum/rental-hirer-change-request-status.d.ts +7 -0
  53. package/dist/src/enum/rental-hirer-change-request-status.js +12 -0
  54. package/dist/src/enum/rental-hirer-change-request-status.js.map +1 -0
  55. package/dist/src/enum/rental-hirer-change-request-type.d.ts +4 -0
  56. package/dist/src/enum/rental-hirer-change-request-type.js +9 -0
  57. package/dist/src/enum/rental-hirer-change-request-type.js.map +1 -0
  58. package/dist/src/index.d.ts +9 -1
  59. package/dist/src/index.js +17 -1
  60. package/dist/src/index.js.map +1 -1
  61. package/dist/src/interfaces/hirer-change-request-new-hirer-attr.interface.d.ts +15 -0
  62. package/dist/src/interfaces/hirer-change-request-new-hirer-attr.interface.js +3 -0
  63. package/dist/src/interfaces/hirer-change-request-new-hirer-attr.interface.js.map +1 -0
  64. package/dist/src/interfaces/hirer-change-request-remove-hirer-attr.interface.d.ts +5 -0
  65. package/dist/src/interfaces/hirer-change-request-remove-hirer-attr.interface.js +3 -0
  66. package/dist/src/interfaces/hirer-change-request-remove-hirer-attr.interface.js.map +1 -0
  67. package/dist/src/interfaces/hirer-change-request-signature-attr.interface.d.ts +14 -0
  68. package/dist/src/interfaces/hirer-change-request-signature-attr.interface.js +3 -0
  69. package/dist/src/interfaces/hirer-change-request-signature-attr.interface.js.map +1 -0
  70. package/dist/src/interfaces/index.d.ts +6 -1
  71. package/dist/src/interfaces/joint-hirer-attr.interface.d.ts +1 -0
  72. package/dist/src/interfaces/rental-hirer-change-request.attr.interface.d.ts +16 -0
  73. package/dist/src/interfaces/rental-hirer-change-request.attr.interface.js +3 -0
  74. package/dist/src/interfaces/rental-hirer-change-request.attr.interface.js.map +1 -0
  75. package/dist/src/interfaces/rental-hirer-change-request.update.interface.d.ts +4 -0
  76. package/dist/src/interfaces/rental-hirer-change-request.update.interface.js +3 -0
  77. package/dist/src/interfaces/rental-hirer-change-request.update.interface.js.map +1 -0
  78. package/dist/src/models/hirer-change-request-new-hirer.entity.d.ts +19 -0
  79. package/dist/src/models/hirer-change-request-new-hirer.entity.js +121 -0
  80. package/dist/src/models/hirer-change-request-new-hirer.entity.js.map +1 -0
  81. package/dist/src/models/hirer-change-request-remove-hirer.entity.d.ts +11 -0
  82. package/dist/src/models/hirer-change-request-remove-hirer.entity.js +57 -0
  83. package/dist/src/models/hirer-change-request-remove-hirer.entity.js.map +1 -0
  84. package/dist/src/models/hirer-change-request-signature.entity.d.ts +18 -0
  85. package/dist/src/models/hirer-change-request-signature.entity.js +101 -0
  86. package/dist/src/models/hirer-change-request-signature.entity.js.map +1 -0
  87. package/dist/src/models/hirer-signature.entity.d.ts +2 -2
  88. package/dist/src/models/hirer-signature.entity.js +18 -18
  89. package/dist/src/models/hirer-signature.entity.js.map +1 -1
  90. package/dist/src/models/index.d.ts +5 -1
  91. package/dist/src/models/index.js +9 -1
  92. package/dist/src/models/index.js.map +1 -1
  93. package/dist/src/models/joint-hirer.entity.d.ts +1 -0
  94. package/dist/src/models/joint-hirer.entity.js +12 -0
  95. package/dist/src/models/joint-hirer.entity.js.map +1 -1
  96. package/dist/src/models/rental-hirer-change-request.entity.d.ts +22 -0
  97. package/dist/src/models/rental-hirer-change-request.entity.js +108 -0
  98. package/dist/src/models/rental-hirer-change-request.entity.js.map +1 -0
  99. package/dist/tsconfig.tsbuildinfo +1 -1
  100. package/migrations/20250529092130-add-status-to-joint-hirer.js +19 -0
  101. package/migrations/hirer-change-request-new-hirer-migration.js +72 -0
  102. package/migrations/hirer-change-request-remove-hirer-migration.js +39 -0
  103. package/migrations/hirer-change-request-signature-migration.js +65 -0
  104. package/migrations/rental-hirer-change-request-migrations.js +64 -0
  105. package/package.json +3 -3
  106. package/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.repository.ts +64 -0
  107. package/src/components/hirer-change-request-new-hirer/hirer-change-request-new-hirer.ts +153 -0
  108. package/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.repository.ts +65 -0
  109. package/src/components/hirer-change-request-remove-hirer/hirer-change-request-remove-hirer.ts +134 -0
  110. package/src/components/hirer-change-request-signature/hirer-change-request-signature.repository.ts +65 -0
  111. package/src/components/hirer-change-request-signature/hirer-change-request-signature.ts +271 -0
  112. package/src/components/joint-hirer/joint-hirer.ts +52 -0
  113. package/src/components/rental/rental.ts +46 -4
  114. package/src/components/rental-hirer-change-request/rental-hirer-change-request.repository.ts +64 -0
  115. package/src/components/rental-hirer-change-request/rental-hirer-change-request.ts +919 -0
  116. package/src/database.ts +8 -0
  117. package/src/enum/index.ts +6 -0
  118. package/src/enum/rental-hirer-change-request-hirer-role.ts +4 -0
  119. package/src/enum/rental-hirer-change-request-status.ts +7 -0
  120. package/src/enum/rental-hirer-change-request-type.ts +4 -0
  121. package/src/index.ts +16 -0
  122. package/src/interfaces/hirer-change-request-new-hirer-attr.interface.ts +15 -0
  123. package/src/interfaces/hirer-change-request-remove-hirer-attr.interface.ts +5 -0
  124. package/src/interfaces/hirer-change-request-signature-attr.interface.ts +15 -0
  125. package/src/interfaces/index.ts +10 -0
  126. package/src/interfaces/joint-hirer-attr.interface.ts +1 -0
  127. package/src/interfaces/rental-hirer-change-request.attr.interface.ts +17 -0
  128. package/src/interfaces/rental-hirer-change-request.update.interface.ts +4 -0
  129. package/src/models/hirer-change-request-new-hirer.entity.ts +102 -0
  130. package/src/models/hirer-change-request-remove-hirer.entity.ts +47 -0
  131. package/src/models/hirer-change-request-signature.entity.ts +86 -0
  132. package/src/models/index.ts +8 -0
  133. package/src/models/joint-hirer.entity.ts +7 -0
  134. package/src/models/rental-hirer-change-request.entity.ts +93 -0
@@ -0,0 +1,919 @@
1
+ import { ClassError, ObjectBase } from '@tomei/general';
2
+ import { RentalHirerChangeRequestRepository } from './rental-hirer-change-request.repository';
3
+ import { ApplicationConfig, ComponentConfig } from '@tomei/config';
4
+ import { LoginUser } from '@tomei/sso';
5
+ import { ActionEnum, Activity } from '@tomei/activity-history';
6
+ import { HirerChangeRequestTypeEnum } from '../../enum/rental-hirer-change-request-type';
7
+ import { HirerChangeRequestStatusEnum } from '../../enum/rental-hirer-change-request-status';
8
+ import {
9
+ IRentalHirerChangeRequestAttr,
10
+ IRentalHirerChangeRequestUpdate,
11
+ } from '../../interfaces';
12
+ import { Rental } from '../rental/rental';
13
+ import {
14
+ HirerChangeRequestHirerRoleEnum,
15
+ RentalAccountTypeEnum,
16
+ } from '../../enum';
17
+ import { Op } from 'sequelize';
18
+ import { JointHirerRepository } from '../../components/joint-hirer/joint-hirer.repository';
19
+ import { HirerChangeRequestSignatureRepository } from '../hirer-change-request-signature/hirer-change-request-signature.repository';
20
+ import { HirerChangeRequestSignature } from '../hirer-change-request-signature/hirer-change-request-signature';
21
+ import { HirerChangeRequestNewHirer } from '../hirer-change-request-new-hirer/hirer-change-request-new-hirer';
22
+ import { HirerChangeRequestRemoveHirer } from '../hirer-change-request-remove-hirer/hirer-change-request-remove-hirer';
23
+ import { JointHirer } from '../joint-hirer/joint-hirer';
24
+
25
+ export class RentalHirerChangeRequest
26
+ extends ObjectBase
27
+ implements IRentalHirerChangeRequestAttr
28
+ {
29
+ ObjectId: string;
30
+ ObjectName: string;
31
+ ObjectType: string = 'RentalHirerChangeRequest';
32
+ TableName: string = 'rental_HirerChangeRequest';
33
+
34
+ RentalId: string;
35
+ Type: HirerChangeRequestTypeEnum;
36
+ Status: HirerChangeRequestStatusEnum;
37
+ RequestedAt: Date;
38
+ RequestedById: string;
39
+ RequestingHirerId: string;
40
+ RequestingHirerType: HirerChangeRequestHirerRoleEnum;
41
+ CancelRemarks: string;
42
+ UpdatedAt: Date;
43
+ UpdatedById: string;
44
+
45
+ Signatures: HirerChangeRequestSignature[] = [];
46
+ Rental: Rental | null = null;
47
+
48
+ get RequestId(): string {
49
+ return this.ObjectId;
50
+ }
51
+
52
+ set RequestId(value: string) {
53
+ this.ObjectId = value;
54
+ }
55
+
56
+ protected static _Repository = new RentalHirerChangeRequestRepository();
57
+ protected static _HirerSignatureRepository =
58
+ new HirerChangeRequestSignatureRepository();
59
+ protected static _JointHirerRepository = new JointHirerRepository();
60
+
61
+ protected constructor(
62
+ hirerChangeRequestAttr?: IRentalHirerChangeRequestAttr,
63
+ ) {
64
+ super();
65
+ if (hirerChangeRequestAttr) {
66
+ this.RequestId = hirerChangeRequestAttr.RequestId;
67
+ this.RentalId = hirerChangeRequestAttr.RentalId;
68
+ this.Type = hirerChangeRequestAttr.Type;
69
+ this.Status = hirerChangeRequestAttr.Status;
70
+ this.RequestedAt = hirerChangeRequestAttr.RequestedAt;
71
+ this.RequestedById = hirerChangeRequestAttr.RequestedById;
72
+ this.RequestingHirerId = hirerChangeRequestAttr.RequestingHirerId;
73
+ this.RequestingHirerType = hirerChangeRequestAttr.RequestingHirerType;
74
+ this.CancelRemarks = hirerChangeRequestAttr.CancelRemarks;
75
+ this.UpdatedAt = hirerChangeRequestAttr.UpdatedAt;
76
+ this.UpdatedById = hirerChangeRequestAttr.UpdatedById;
77
+ }
78
+ }
79
+
80
+ toJSON(): IRentalHirerChangeRequestAttr {
81
+ return {
82
+ RequestId: this.RequestId,
83
+ RentalId: this.RentalId,
84
+ Type: this.Type,
85
+ Status: this.Status,
86
+ RequestedAt: this.RequestedAt,
87
+ RequestedById: this.RequestedById,
88
+ RequestingHirerId: this.RequestingHirerId,
89
+ RequestingHirerType: this.RequestingHirerType,
90
+ CancelRemarks: this.CancelRemarks,
91
+ UpdatedAt: this.UpdatedAt,
92
+ UpdatedById: this.UpdatedById,
93
+ };
94
+ }
95
+
96
+ public static async init(requestId?: string, dbTransaction?: any) {
97
+ try {
98
+ if (requestId) {
99
+ const hirerChangeReq =
100
+ await RentalHirerChangeRequest._Repository.findByPk(
101
+ requestId,
102
+ dbTransaction,
103
+ );
104
+ if (hirerChangeReq) {
105
+ return new RentalHirerChangeRequest(
106
+ hirerChangeReq.get({ plain: true }),
107
+ );
108
+ } else {
109
+ throw new ClassError(
110
+ 'HirerChangeRequest',
111
+ 'HirerChangeRequestErrMsg00',
112
+ 'HirerChangeRequest not found',
113
+ );
114
+ }
115
+ }
116
+ return new RentalHirerChangeRequest();
117
+ } catch (error) {
118
+ console.error('Error initializing RentalHirerChangeRequest:', error);
119
+ throw error;
120
+ }
121
+ }
122
+
123
+ private async checkForDuplicate(dbTransaction: any) {
124
+ try {
125
+ if (!this.RentalId) {
126
+ throw new ClassError(
127
+ 'HirerChangeRequest',
128
+ 'HirerChangeRequestErrMsg08',
129
+ 'RentalId is required to check for duplicate requests.',
130
+ );
131
+ }
132
+
133
+ const isDuplicate = await RentalHirerChangeRequest._Repository.findAll({
134
+ where: {
135
+ RentalId: this.RentalId,
136
+ Type: this.Type,
137
+ Status: {
138
+ [Op.notIn]: [
139
+ HirerChangeRequestStatusEnum.COMPLETED,
140
+ HirerChangeRequestStatusEnum.CANCELLED,
141
+ ],
142
+ },
143
+ },
144
+ transaction: dbTransaction,
145
+ });
146
+
147
+ if (isDuplicate.length > 0) {
148
+ throw new ClassError(
149
+ 'HirerChangeRequest',
150
+ 'HirerChangeRequestErrMsg04',
151
+ 'Duplicate record detected.',
152
+ );
153
+ }
154
+ } catch (error) {
155
+ throw error;
156
+ }
157
+ }
158
+
159
+ public async getSignatures(dbTransaction: any) {
160
+ try {
161
+ if (this.Signatures.length > 0) {
162
+ return this.Signatures;
163
+ }
164
+
165
+ const hirers =
166
+ await RentalHirerChangeRequest._HirerSignatureRepository.findAll({
167
+ where: {
168
+ RequestId: this.RequestId,
169
+ },
170
+ transaction: dbTransaction,
171
+ });
172
+
173
+ this.Signatures = await Promise.all(
174
+ hirers.map(async (hirer) => {
175
+ return await HirerChangeRequestSignature.init(
176
+ hirer.SignatureId,
177
+ dbTransaction,
178
+ );
179
+ }),
180
+ );
181
+ return this.Signatures;
182
+ } catch (error) {
183
+ throw error;
184
+ }
185
+ }
186
+
187
+ private async checkIsAllSigned(dbTransaction) {
188
+ try {
189
+ const listUpdatedHirer = await this.getSignatures(dbTransaction);
190
+ if (listUpdatedHirer.length > 0) {
191
+ return listUpdatedHirer.every((record) => record.SignedAt !== null);
192
+ } else {
193
+ return false;
194
+ }
195
+ } catch (error) {
196
+ throw error;
197
+ }
198
+ }
199
+
200
+ private async create(loginUser: LoginUser, dbTransaction?: any) {
201
+ try {
202
+ // Part 1: Check Privilege
203
+ // 1.1 Make sure user got "HIRER_CHANGE_REQUEST" privilege
204
+ const systemCode =
205
+ ApplicationConfig.getComponentConfigValue('system-code');
206
+ const isPrivileged = await loginUser.checkPrivileges(
207
+ systemCode,
208
+ 'HIRER_CHANGE_REQUEST',
209
+ );
210
+
211
+ if (!isPrivileged) {
212
+ throw new ClassError(
213
+ 'HirerChangeRequest',
214
+ 'HirerChangeRequestErrMsg01',
215
+ "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
216
+ );
217
+ }
218
+
219
+ // Part 2: Validation Rental
220
+ // 2.1 Make sure this.RentalId and this.RequestingHirerId got value.
221
+ if (!this.RentalId || !this.RequestingHirerId) {
222
+ throw new ClassError(
223
+ 'HirerChangeRequest',
224
+ 'HirerChangeRequestErrMsg02',
225
+ 'RentalId and RequestingHirerId are required.',
226
+ );
227
+ }
228
+ const rental = await Rental.init(dbTransaction, this.RentalId);
229
+
230
+ // 2.2 Only allow for joint account
231
+ if (rental.AccountType !== RentalAccountTypeEnum.JOINT) {
232
+ throw new ClassError(
233
+ 'HirerChangeRequest',
234
+ 'HirerChangeRequestErrMsg03',
235
+ 'Only Rental with Joint Account Type Allowed.',
236
+ );
237
+ }
238
+
239
+ // 2.3 only allow for rental account where its joint hirer not maximum yet (at this step you've retrieve the number of joint hirer)
240
+ const jointHirers = await rental.getJointHirers(dbTransaction);
241
+
242
+ if (this.Type === HirerChangeRequestTypeEnum.ADD) {
243
+ const maxJointHirer = ComponentConfig.getComponentConfigValue(
244
+ '@tomei/rental',
245
+ 'maxJointHirerLength',
246
+ );
247
+
248
+ if (jointHirers?.length >= maxJointHirer) {
249
+ throw new ClassError(
250
+ 'HirerChangeRequest',
251
+ 'HirerChangeRequestErrMsg05',
252
+ 'Joint Hirer exceed max joint hirer length.',
253
+ );
254
+ }
255
+ }
256
+ // For Remove type, we don't need to check the max joint hirer length
257
+ // but we need to ensure that there is at least one joint hirer left after removal
258
+ if (
259
+ this.Type == HirerChangeRequestTypeEnum.REMOVE &&
260
+ jointHirers?.length <= 1
261
+ ) {
262
+ throw new ClassError(
263
+ 'HirerChangeRequest',
264
+ 'HirerChangeRequestErrMsg05',
265
+ 'Cannot remove the last joint hirer.',
266
+ );
267
+ }
268
+
269
+ // 2.4 avoid duplicate request, call this.checkForDuplicate() private method, this method will ensure no record with same RentalId and same Type and Status is not "Completed" or "Cancelled"
270
+ await this.checkForDuplicate(dbTransaction);
271
+
272
+ // Part 3: Create Hirer Change Request and Hirer Change Request Hirer Records
273
+ // 3.1 Set other attributes
274
+ this.ObjectId = this.createId();
275
+ this.RequestedById = loginUser.ObjectId;
276
+ this.RequestedAt = new Date();
277
+ this.UpdatedById = loginUser.ObjectId;
278
+ this.UpdatedAt = new Date();
279
+ this.Status = HirerChangeRequestStatusEnum.PENDINGSIGNATURES;
280
+ this.RequestingHirerType =
281
+ rental.CustomerId === this.RequestingHirerId
282
+ ? HirerChangeRequestHirerRoleEnum.MAIN
283
+ : HirerChangeRequestHirerRoleEnum.JOINT;
284
+
285
+ // 3.2 Set Entity value after
286
+ const entityValueAfter: IRentalHirerChangeRequestAttr = {
287
+ RequestId: this.RequestId,
288
+ RentalId: this.RentalId,
289
+ Type: this.Type,
290
+ Status: this.Status,
291
+ RequestedById: this.RequestedById,
292
+ RequestedAt: this.RequestedAt,
293
+ RequestingHirerId: this.RequestingHirerId,
294
+ RequestingHirerType: this.RequestingHirerType,
295
+ CancelRemarks: this.CancelRemarks,
296
+ UpdatedById: this.UpdatedById,
297
+ UpdatedAt: this.UpdatedAt,
298
+ };
299
+
300
+ // 3.3 Call repo class create method by passing the class attributes and db transaction.
301
+ await RentalHirerChangeRequest._Repository.create(entityValueAfter, {
302
+ transaction: dbTransaction,
303
+ });
304
+
305
+ // Generate signatures, call this.generateSignature by passing:
306
+ await this.generateSignatures(loginUser, dbTransaction);
307
+
308
+ //Part 4: Record Create Hirer Change Request Activity
309
+ const activity = new Activity();
310
+ activity.ObjectId = this._createId();
311
+ activity.Action = ActionEnum.CREATE;
312
+ activity.Description =
313
+ this.Type === HirerChangeRequestTypeEnum.ADD
314
+ ? 'Add New Hirer'
315
+ : 'Remove Hirer';
316
+ activity.EntityId = this.ObjectId;
317
+ activity.EntityType = this.ObjectType;
318
+ activity.EntityValueBefore = JSON.stringify({});
319
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
320
+
321
+ await activity.create(loginUser.ObjectId, dbTransaction);
322
+
323
+ return this;
324
+ } catch (error) {
325
+ throw error;
326
+ }
327
+ }
328
+
329
+ public async update(
330
+ loginUser: LoginUser,
331
+ dbTransaction: any,
332
+ params: IRentalHirerChangeRequestUpdate,
333
+ ) {
334
+ try {
335
+ // Part 1: Validation
336
+ // 1.1 Check if RequestId is not null if null return error
337
+ if (!this.RequestId) {
338
+ throw new ClassError(
339
+ 'HirerChangeRequest',
340
+ 'HirerChangeRequestErrMsg06',
341
+ 'RequestId are required.',
342
+ );
343
+ }
344
+
345
+ // Part 2: Update Hirer Change Request Hirer Records
346
+ // 2.1 Set entityValueBefore
347
+ const entityValueBefore: IRentalHirerChangeRequestAttr = {
348
+ RequestId: this.RequestId,
349
+ RentalId: this.RentalId,
350
+ Type: this.Type,
351
+ Status: this.Status,
352
+ RequestedById: this.RequestedById,
353
+ RequestedAt: this.RequestedAt,
354
+ RequestingHirerId: this.RequestingHirerId,
355
+ RequestingHirerType: this.RequestingHirerType,
356
+ CancelRemarks: this.CancelRemarks,
357
+ UpdatedById: this.UpdatedById,
358
+ UpdatedAt: this.UpdatedAt,
359
+ };
360
+
361
+ // 2.2 Check if jointHirerId, customerId is null or not
362
+ let { jointHirerId, customerId } = params;
363
+ if (!customerId && !jointHirerId) {
364
+ throw new ClassError(
365
+ 'HirerChangeRequest',
366
+ 'HirerChangeRequestErrMsg07',
367
+ 'At least one of customerId or jointHirerId must be provided.',
368
+ );
369
+ }
370
+
371
+ // 2.3 Get list hirer related to request then find hirer with same CustomerId, if found update the hirer record (make it signed)
372
+ const listHirer = await this.getSignatures(dbTransaction);
373
+
374
+ if (listHirer?.length > 0) {
375
+ let foundHirer = listHirer?.find((record) => {
376
+ record.CustomerId === customerId;
377
+ });
378
+
379
+ let hirer = await HirerChangeRequestSignature.init(
380
+ foundHirer.SignatureId,
381
+ dbTransaction,
382
+ );
383
+
384
+ await hirer.update(loginUser, dbTransaction);
385
+ }
386
+
387
+ // 2.4 Check if all hirer in request is signed, if yes check request Type:
388
+ // Type = "Add" = update Request status to “AwaitingNewHirer”
389
+ // Type = "Remove" = flag the joint hirer record status to "Inactive" & update Request status to "Completed"
390
+ const isSigned = await this.checkIsAllSigned(dbTransaction);
391
+
392
+ if (isSigned) {
393
+ if (this.Type === HirerChangeRequestTypeEnum.ADD) {
394
+ this.Status = HirerChangeRequestStatusEnum.AWAITINGNEWHIRER;
395
+ } else if (this.Type === HirerChangeRequestTypeEnum.REMOVE) {
396
+ // TODO: Currently JointHirer dont have status props
397
+
398
+ await RentalHirerChangeRequest._JointHirerRepository.update(
399
+ { Status: 'Inactive' },
400
+ {
401
+ where: {
402
+ HirerId: this.RequestId,
403
+ },
404
+ transaction: dbTransaction,
405
+ },
406
+ );
407
+ this.Status = HirerChangeRequestStatusEnum.COMPLETED;
408
+ }
409
+ }
410
+
411
+ // 2.5 Set entityValueAfter
412
+ const entityValueAfter: IRentalHirerChangeRequestAttr = {
413
+ RequestId: this.RequestId,
414
+ RentalId: this.RentalId,
415
+ Type: this.Type,
416
+ Status: this.Status,
417
+ RequestedById: this.RequestedById,
418
+ RequestedAt: this.RequestedAt,
419
+ RequestingHirerId: this.RequestingHirerId,
420
+ RequestingHirerType: this.RequestingHirerType,
421
+ CancelRemarks: this.CancelRemarks,
422
+ UpdatedById: this.UpdatedById,
423
+ UpdatedAt: this.UpdatedAt,
424
+ };
425
+
426
+ // 2.6 Call repo class update method by passing the class attributes and db transaction.
427
+ await RentalHirerChangeRequest._Repository.update(entityValueAfter, {
428
+ where: {
429
+ RequestId: this.RequestId,
430
+ },
431
+ transaction: dbTransaction,
432
+ });
433
+
434
+ //Part 3: Record Create Hirer Change Request Activity
435
+ const activity = new Activity();
436
+ activity.ObjectId = this._createId();
437
+ activity.Action = ActionEnum.UPDATE;
438
+ activity.Description = 'Update Hirer Change Request';
439
+ activity.EntityId = this.ObjectId;
440
+ activity.EntityType = this.ObjectType;
441
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
442
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
443
+
444
+ await activity.create(loginUser.ObjectId, dbTransaction);
445
+
446
+ return this;
447
+ } catch (error) {
448
+ throw error;
449
+ }
450
+ }
451
+
452
+ public async generateSignatures(loginUser: LoginUser, dbTransaction?: any) {
453
+ try {
454
+ if (!this.RentalId) {
455
+ throw new ClassError(
456
+ 'HirerChangeRequest',
457
+ 'HirerChangeRequestErrMsg09',
458
+ 'RentalId is required to create signatures.',
459
+ );
460
+ }
461
+
462
+ // 1. If this.Rental is null, set it to Rental instantiation using this.RentalId
463
+ if (!this.Rental) {
464
+ this.Rental = await Rental.init(dbTransaction, this.RentalId);
465
+ }
466
+
467
+ // 2. Prepare all signature data (main + joint) in one go
468
+ const jointHirers = await this.Rental.getJointHirers(dbTransaction);
469
+
470
+ // Main hirer signature
471
+ const mainSignature = await HirerChangeRequestSignature.init();
472
+ mainSignature.RequestId = this.RequestId;
473
+ mainSignature.CustomerId = this.Rental.CustomerId;
474
+ mainSignature.HirerType = HirerChangeRequestHirerRoleEnum.MAIN;
475
+ mainSignature.Method = 'Upload';
476
+
477
+ // Joint hirer signatures
478
+ const jointSignaturePromises = jointHirers.map(async (jointHirer) => {
479
+ const signature = await HirerChangeRequestSignature.init();
480
+ signature.RequestId = this.RequestId;
481
+ signature.JointHirerId = jointHirer.HirerId;
482
+ signature.HirerType = HirerChangeRequestHirerRoleEnum.JOINT;
483
+ signature.Method = 'Upload';
484
+ return signature;
485
+ });
486
+
487
+ const jointSignatures = await Promise.all(jointSignaturePromises);
488
+
489
+ // 3. Create all signatures in parallel
490
+ await Promise.all([
491
+ mainSignature.create(loginUser, dbTransaction),
492
+ ...jointSignatures.map((sig) => sig.create(loginUser, dbTransaction)),
493
+ ]);
494
+
495
+ this.Signatures = [mainSignature, ...jointSignatures];
496
+ } catch (error) {
497
+ console.error('Error creating signatures:', error);
498
+ throw error;
499
+ }
500
+ }
501
+
502
+ public async createAddRequest(
503
+ newHirer: {
504
+ FullName: string;
505
+ NRIC: string;
506
+ Email?: string;
507
+ ContactNo: string;
508
+ Relationship: string;
509
+ Address: string;
510
+ City: string;
511
+ State: string;
512
+ Postcode: string;
513
+ Country: string;
514
+ },
515
+ loginUser: LoginUser,
516
+ dbTransaction?: any,
517
+ ): Promise<{
518
+ hirerChangeRequest: RentalHirerChangeRequest;
519
+ NewHirer: HirerChangeRequestNewHirer;
520
+ }> {
521
+ try {
522
+ // Validations
523
+ // 1. Make sure newHirer required fields cannot be null.
524
+ if (
525
+ !newHirer.FullName ||
526
+ !newHirer.NRIC ||
527
+ !newHirer.ContactNo ||
528
+ !newHirer.Address ||
529
+ !newHirer.City ||
530
+ !newHirer.State ||
531
+ !newHirer.Postcode ||
532
+ !newHirer.Country
533
+ ) {
534
+ throw new ClassError(
535
+ 'HirerChangeRequest',
536
+ 'HirerChangeRequestErrMsg10',
537
+ 'FullName, NRIC, and ContactNo are required.',
538
+ );
539
+ }
540
+
541
+ // 2. Set this.Type to "Add".
542
+ this.Type = HirerChangeRequestTypeEnum.ADD;
543
+
544
+ // 3. Call this.create by passing:
545
+ // loginUser, dbTransaction
546
+ await this.create(loginUser, dbTransaction);
547
+
548
+ // 4. Insert into hirerChangeRequest_NewHirer
549
+ const nh = await HirerChangeRequestNewHirer.init();
550
+ nh.RequestId = this.RequestId;
551
+ nh.FullName = newHirer.FullName;
552
+ nh.Email = newHirer.Email;
553
+ nh.IdType = 'NRIC';
554
+ nh.IdNo = newHirer.NRIC;
555
+ nh.ContactNo = newHirer.ContactNo;
556
+ nh.Relationship = newHirer.Relationship;
557
+ nh.Address = newHirer.Address;
558
+ nh.City = newHirer.City;
559
+ nh.State = newHirer.State;
560
+ nh.Postcode = newHirer.Postcode;
561
+ nh.Country = newHirer.Country;
562
+
563
+ await nh.create(loginUser, dbTransaction);
564
+
565
+ // 5. Return HirerChangeRequest instance and NewHirer instance.
566
+ return {
567
+ hirerChangeRequest: this,
568
+ NewHirer: nh,
569
+ };
570
+ } catch (error) {
571
+ console.error('Error creating add request:', error);
572
+ throw error;
573
+ }
574
+ }
575
+
576
+ public async createRemoveRequest(
577
+ loginUser: LoginUser,
578
+ dbTransaction: any,
579
+ targetHirerId: string,
580
+ ): Promise<{
581
+ hirerChangeRequest: RentalHirerChangeRequest;
582
+ RemoveHirer: HirerChangeRequestRemoveHirer;
583
+ }> {
584
+ try {
585
+ // 1. Make sure targetHirerId can be instantiated into JointHirer class (existing joint hirer)
586
+ await JointHirer.init(targetHirerId, dbTransaction);
587
+
588
+ // 2. Set this.Type to "Remove"
589
+ this.Type = HirerChangeRequestTypeEnum.REMOVE;
590
+
591
+ // 3. Call this.create() method by passing loginUser, dbTransaction
592
+ await this.create(loginUser, dbTransaction);
593
+
594
+ // 4. Insert into hirerChangeRequest_RemoveHirer
595
+ const removeHirer = await HirerChangeRequestRemoveHirer.init();
596
+ removeHirer.RemoveHirerId = this.createId();
597
+ removeHirer.RequestId = this.RequestId;
598
+ removeHirer.TargetHirerId = targetHirerId;
599
+
600
+ await removeHirer.create(loginUser, dbTransaction);
601
+
602
+ // 5. Return the returned HirerChangeRequest instance and RemoveHirer instance.
603
+ return {
604
+ hirerChangeRequest: this,
605
+ RemoveHirer: removeHirer,
606
+ };
607
+ } catch (error) {
608
+ console.error('Error creating remove request:', error);
609
+ throw error;
610
+ }
611
+ }
612
+
613
+ public static async findAll(
614
+ loginUser: LoginUser,
615
+ dbTransaction: any,
616
+ rentalId: string,
617
+ ): Promise<RentalHirerChangeRequest[]> {
618
+ try {
619
+ // 1. Make sure user got "HIRER_CHANGE_REQUEST" privilege.
620
+ const systemCode =
621
+ ApplicationConfig.getComponentConfigValue('system-code');
622
+ const isPrivileged = await loginUser.checkPrivileges(
623
+ systemCode,
624
+ 'HIRER_CHANGE_REQUEST',
625
+ );
626
+ if (!isPrivileged) {
627
+ throw new ClassError(
628
+ 'HirerChangeRequest',
629
+ 'HirerChangeRequestErrMsg01',
630
+ "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
631
+ );
632
+ }
633
+
634
+ // 2. Make sure rentalId not null.
635
+ if (!rentalId) {
636
+ throw new ClassError(
637
+ 'HirerChangeRequest',
638
+ 'HirerChangeRequestErrMsg12',
639
+ 'rentalId is required.',
640
+ );
641
+ }
642
+
643
+ // 3. Instantiate rental (to check rental id valid)
644
+ await Rental.init(dbTransaction, rentalId);
645
+
646
+ // 4. Call Repo.findAll({ where: { RentalId: rentalId }, transaction: dbTransaction })
647
+ const records = await RentalHirerChangeRequest._Repository.findAll({
648
+ where: { RentalId: rentalId },
649
+ transaction: dbTransaction,
650
+ });
651
+
652
+ // 5. Instantiate the returned items
653
+ const result = (records || []).map(
654
+ (rec) => new RentalHirerChangeRequest(rec.get({ plain: true })),
655
+ );
656
+
657
+ // 6. Return the instance array
658
+ return result;
659
+ } catch (error) {
660
+ console.error('Error finding all RentalHirerChangeRequest:', error);
661
+ throw error;
662
+ }
663
+ }
664
+
665
+ public async getNewHirer(
666
+ dbTransaction: any,
667
+ ): Promise<HirerChangeRequestNewHirer> {
668
+ try {
669
+ // 1. Make sure this.RequestId got value.
670
+ if (!this.RequestId) {
671
+ throw new ClassError(
672
+ 'HirerChangeRequest',
673
+ 'HirerChangeRequestErrMsg13',
674
+ 'RequestId is required to get new hirer.',
675
+ );
676
+ }
677
+
678
+ // 2. Call HirerChangeRequestNewHirer.findAll()
679
+ const newHirerRecords = await HirerChangeRequestNewHirer.findAll(
680
+ {
681
+ RequestId: this.RequestId,
682
+ },
683
+ dbTransaction,
684
+ );
685
+
686
+ // 3. Return the newHirer instance
687
+ return newHirerRecords[0] || null;
688
+ } catch (error) {
689
+ console.error('Error getting new hirer:', error);
690
+ throw error;
691
+ }
692
+ }
693
+
694
+ public async getRemoveHirer(
695
+ dbTransaction: any,
696
+ ): Promise<HirerChangeRequestRemoveHirer> {
697
+ try {
698
+ // 1. Make sure this.RequestId got value.
699
+ if (!this.RequestId) {
700
+ throw new ClassError(
701
+ 'HirerChangeRequest',
702
+ 'HirerChangeRequestErrMsg13',
703
+ 'RequestId is required to get remove hirer.',
704
+ );
705
+ }
706
+
707
+ // 2. Call HirerChangeRequestRemoveHirer.findAll()
708
+ const removeHirerRecords = await HirerChangeRequestRemoveHirer.findAll(
709
+ {
710
+ RequestId: this.RequestId,
711
+ },
712
+ dbTransaction,
713
+ );
714
+
715
+ // 3. Return the removeHirer instance
716
+ return removeHirerRecords[0] || null;
717
+ } catch (error) {
718
+ console.error('Error getting remove hirer:', error);
719
+ throw error;
720
+ }
721
+ }
722
+
723
+ public async sign(
724
+ loginUser: LoginUser,
725
+ dbTransaction: any,
726
+ signatureId: string,
727
+ ): Promise<HirerChangeRequestSignature> {
728
+ try {
729
+ // 1. Make sure this.RequestId got value.
730
+ if (!this.RequestId) {
731
+ throw new ClassError(
732
+ 'HirerChangeRequest',
733
+ 'HirerChangeRequestErrMsg13',
734
+ 'RequestId is required to sign signature.',
735
+ );
736
+ }
737
+
738
+ // Validate signatureId
739
+ const hcrSignature = await HirerChangeRequestSignature.init(
740
+ signatureId,
741
+ dbTransaction,
742
+ );
743
+
744
+ //Call hcrSignature.markSigned() method by passing loginUser and dbTransaction
745
+ await hcrSignature.markSigned(loginUser, dbTransaction);
746
+
747
+ // const isSigned = await this.checkIsAllSigned(dbTransaction);
748
+
749
+ // if (isSigned && this.Type === HirerChangeRequestTypeEnum.ADD) {
750
+ // this.Status = HirerChangeRequestStatusEnum.AWAITINGNEWHIRER;
751
+ // this.UpdatedAt = new Date();
752
+ // this.UpdatedById = loginUser.ObjectId;
753
+ // await RentalHirerChangeRequest._Repository.update(this.toJSON(), {
754
+ // where: { RequestId: this.RequestId },
755
+ // transaction: dbTransaction,
756
+ // });
757
+ // }
758
+ return hcrSignature;
759
+ } catch (error) {
760
+ throw error;
761
+ }
762
+ }
763
+
764
+ public async complete(
765
+ loginUser: LoginUser,
766
+ dbTransaction: any,
767
+ customerId?: string,
768
+ customerType?: string,
769
+ ): Promise<RentalHirerChangeRequest> {
770
+ // 1. Validations
771
+ // a. Make sure user got "HIRER_CHANGE_REQUEST" privilege.
772
+ const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
773
+ const isPrivileged = await loginUser.checkPrivileges(
774
+ systemCode,
775
+ 'HIRER_CHANGE_REQUEST',
776
+ );
777
+ if (!isPrivileged) {
778
+ throw new ClassError(
779
+ 'HirerChangeRequest',
780
+ 'HirerChangeRequestErrMsg01',
781
+ "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
782
+ );
783
+ }
784
+
785
+ // b. Make sure this.RequestId not null.
786
+ if (!this.RequestId) {
787
+ throw new ClassError(
788
+ 'HirerChangeRequest',
789
+ 'HirerChangeRequestErrMsg06',
790
+ 'RequestId is required.',
791
+ );
792
+ }
793
+
794
+ // c. Make sure all signatures are signed.
795
+ const signatures = await this.getSignatures(dbTransaction);
796
+ if (!signatures.length || !signatures.every((sig) => sig.SignedAt)) {
797
+ throw new ClassError(
798
+ 'HirerChangeRequest',
799
+ 'HirerChangeRequestErrMsg14',
800
+ 'All signatures must be signed before completing the request.',
801
+ );
802
+ }
803
+
804
+ // 2. Create/Remove Hirer
805
+ if (this.Type === HirerChangeRequestTypeEnum.ADD) {
806
+ //Make sure CustomerId is set, if not throw error
807
+ if (!customerId || !customerType) {
808
+ throw new ClassError(
809
+ 'HirerChangeRequest',
810
+ 'HirerChangeRequestErrMsg15',
811
+ 'CustomerId and CustomerType are required for new hirer.',
812
+ );
813
+ }
814
+ // c. Create joint hirer using JointHirer.create()
815
+ const jointHirer = await JointHirer.init();
816
+ jointHirer.RentalId = this.RentalId;
817
+ jointHirer.CustomerId = customerId;
818
+ jointHirer.CustomerType = customerType;
819
+ await jointHirer.create(loginUser, dbTransaction);
820
+ } else if (this.Type === HirerChangeRequestTypeEnum.REMOVE) {
821
+ // a. Get joint hirer from remove hirer request
822
+ const removeHirer = await HirerChangeRequestRemoveHirer.init(
823
+ this.RequestId,
824
+ dbTransaction,
825
+ );
826
+ const jointHirer = await JointHirer.init(
827
+ removeHirer.TargetHirerId,
828
+ dbTransaction,
829
+ );
830
+
831
+ //Call jointHirer.remove() method by passing loginUser and dbTransaction
832
+ await jointHirer.remove(loginUser, dbTransaction);
833
+ }
834
+
835
+ // 3. Call _Repo update method
836
+ const now = new Date();
837
+ const entityValueBefore: IRentalHirerChangeRequestAttr = this.toJSON();
838
+ this.Status = HirerChangeRequestStatusEnum.COMPLETED;
839
+ this.UpdatedAt = new Date();
840
+ this.UpdatedById = loginUser.ObjectId;
841
+ const entityValueAfter: IRentalHirerChangeRequestAttr = this.toJSON();
842
+ await RentalHirerChangeRequest._Repository.update(entityValueAfter, {
843
+ where: { RequestId: this.RequestId },
844
+ transaction: dbTransaction,
845
+ });
846
+
847
+ // 4. Record activity history
848
+ const activity = new Activity();
849
+ activity.ObjectId = this._createId();
850
+ activity.Action = ActionEnum.UPDATE;
851
+ activity.Description = `Mark request as completed.`;
852
+ activity.EntityId = this.RequestId;
853
+ activity.EntityType = this.ObjectType;
854
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
855
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
856
+
857
+ await activity.create(loginUser.ObjectId, dbTransaction);
858
+
859
+ // 5. Return updated instance
860
+ return this;
861
+ }
862
+
863
+ public async cancel(
864
+ loginUser: LoginUser,
865
+ dbTransaction: any,
866
+ cancelRemarks: string,
867
+ ): Promise<RentalHirerChangeRequest> {
868
+ // 1. Make sure user got "HIRER_CHANGE_REQUEST" privilege.
869
+ const systemCode = ApplicationConfig.getComponentConfigValue('system-code');
870
+ const isPrivileged = await loginUser.checkPrivileges(
871
+ systemCode,
872
+ 'HIRER_CHANGE_REQUEST',
873
+ );
874
+ if (!isPrivileged) {
875
+ throw new ClassError(
876
+ 'HirerChangeRequest',
877
+ 'HirerChangeRequestErrMsg01',
878
+ "You do not have 'HIRER_CHANGE_REQUEST' privilege.",
879
+ );
880
+ }
881
+
882
+ // 2. Make sure this.RequestId not null.
883
+ if (!this.RequestId) {
884
+ throw new ClassError(
885
+ 'HirerChangeRequest',
886
+ 'HirerChangeRequestErrMsg06',
887
+ 'RequestId is required.',
888
+ );
889
+ }
890
+
891
+ // 3. Call _Repo.update() method
892
+ const entityValueBefore: IRentalHirerChangeRequestAttr = this.toJSON();
893
+ this.Status = HirerChangeRequestStatusEnum.CANCELLED;
894
+ this.CancelRemarks = cancelRemarks;
895
+ this.UpdatedAt = new Date();
896
+ this.UpdatedById = loginUser.ObjectId;
897
+ const entityValueAfter: IRentalHirerChangeRequestAttr = this.toJSON();
898
+
899
+ await RentalHirerChangeRequest._Repository.update(entityValueAfter, {
900
+ where: { RequestId: this.RequestId },
901
+ transaction: dbTransaction,
902
+ });
903
+
904
+ // 4. Record activity history
905
+ const activity = new Activity();
906
+ activity.ObjectId = this._createId();
907
+ activity.Action = ActionEnum.UPDATE;
908
+ activity.Description = `Cancel hirer change request.`;
909
+ activity.EntityId = this.RequestId;
910
+ activity.EntityType = this.ObjectType;
911
+ activity.EntityValueBefore = JSON.stringify(entityValueBefore);
912
+ activity.EntityValueAfter = JSON.stringify(entityValueAfter);
913
+
914
+ await activity.create(loginUser.ObjectId, dbTransaction);
915
+
916
+ // 5. Return updated instance
917
+ return this;
918
+ }
919
+ }