payservedb 7.9.1 → 7.9.3

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 (160) hide show
  1. package/.env +2 -2
  2. package/.idea/material_theme_project_new.xml +11 -11
  3. package/.idea/modules.xml +7 -7
  4. package/.idea/psdb.iml +11 -11
  5. package/.idea/vcs.xml +5 -5
  6. package/index.js +289 -287
  7. package/package.json +17 -17
  8. package/src/models/account.js +35 -35
  9. package/src/models/apilog.js +18 -18
  10. package/src/models/approvalsWorkflows.js +49 -49
  11. package/src/models/archivedapilog.js +18 -18
  12. package/src/models/asset.js +92 -92
  13. package/src/models/assetsAssignment.js +64 -64
  14. package/src/models/auditTrail.js +346 -346
  15. package/src/models/bankdetails.js +43 -43
  16. package/src/models/billerAddress.js +124 -124
  17. package/src/models/booking_invoice.js +151 -151
  18. package/src/models/bookinganalytics.js +63 -63
  19. package/src/models/bookingconfig.js +45 -45
  20. package/src/models/bookingproperty.js +122 -122
  21. package/src/models/bookingreservation.js +192 -192
  22. package/src/models/bookingrevenuerecord.js +84 -84
  23. package/src/models/budget.js +95 -95
  24. package/src/models/budgetCategory.js +19 -19
  25. package/src/models/campaigns.js +94 -94
  26. package/src/models/cashpayment.js +264 -264
  27. package/src/models/combinedUnits.js +62 -62
  28. package/src/models/common_area_electricity.js +38 -38
  29. package/src/models/common_area_generator.js +41 -41
  30. package/src/models/common_area_utility_alert.js +37 -37
  31. package/src/models/common_area_water.js +39 -39
  32. package/src/models/communication_status.js +33 -33
  33. package/src/models/company.js +53 -53
  34. package/src/models/coreBaseSettings.js +16 -16
  35. package/src/models/coreInvoiceSettings.js +100 -100
  36. package/src/models/country_tax.js +42 -42
  37. package/src/models/currency_settings.js +39 -39
  38. package/src/models/customer.js +208 -208
  39. package/src/models/dailyChecklist.js +312 -312
  40. package/src/models/default_payment_details.js +17 -17
  41. package/src/models/deliveryTimeMarks.js +18 -18
  42. package/src/models/dutyRosterChecklist.js +250 -250
  43. package/src/models/dutyroster.js +136 -136
  44. package/src/models/email.js +37 -37
  45. package/src/models/email_sms_queue.js +61 -61
  46. package/src/models/entry_exit.js +53 -53
  47. package/src/models/expense.js +99 -99
  48. package/src/models/expense_category.js +45 -45
  49. package/src/models/facility.js +62 -62
  50. package/src/models/facilityBillingPrices.js +30 -0
  51. package/src/models/facilityInvoice.js +223 -223
  52. package/src/models/facilityInvoicePayment.js +47 -0
  53. package/src/models/facilityInvoiceRecipient.js +32 -32
  54. package/src/models/facilityWalletTransactionsMetadata.js +236 -236
  55. package/src/models/facility_departements.js +20 -20
  56. package/src/models/facility_payment_details.js +20 -20
  57. package/src/models/facilityasset.js +25 -25
  58. package/src/models/faq.js +18 -18
  59. package/src/models/gl_account_double_entries.js +25 -25
  60. package/src/models/gl_accounts.js +56 -56
  61. package/src/models/gl_entries.js +49 -49
  62. package/src/models/goodsReceivedNotes.js +115 -115
  63. package/src/models/guard.js +47 -47
  64. package/src/models/handover.js +246 -246
  65. package/src/models/invoice.js +336 -336
  66. package/src/models/item_inspection.js +67 -67
  67. package/src/models/leaseagreement.js +226 -226
  68. package/src/models/leasetemplate.js +17 -17
  69. package/src/models/levy.js +206 -206
  70. package/src/models/levy_invoice_settings.js +26 -26
  71. package/src/models/levycontract.js +168 -168
  72. package/src/models/levytype.js +23 -23
  73. package/src/models/maintenance_service_vendor.js +38 -38
  74. package/src/models/maintenance_services.js +17 -17
  75. package/src/models/maintenancerequisition.js +31 -31
  76. package/src/models/master_workplan.js +32 -32
  77. package/src/models/master_workplan_child.js +34 -34
  78. package/src/models/message.js +38 -38
  79. package/src/models/module.js +21 -21
  80. package/src/models/notification.js +44 -44
  81. package/src/models/paymentTermsMarks.js +19 -19
  82. package/src/models/penalty.js +76 -76
  83. package/src/models/pendingCredentials.js +32 -32
  84. package/src/models/powerMeterCommunicationProtocol.js +17 -17
  85. package/src/models/powerMeterCustomerAccount.js +78 -78
  86. package/src/models/powerMeterCustomerBand.js +14 -14
  87. package/src/models/powerMeterDailyReading.js +30 -30
  88. package/src/models/powerMeterGateways.js +40 -40
  89. package/src/models/powerMeterMonthlyReading.js +34 -34
  90. package/src/models/powerMeterPowerCharges.js +85 -85
  91. package/src/models/powerMeterSettings.js +159 -159
  92. package/src/models/powerMeterSingleDayReading.js +32 -32
  93. package/src/models/powerMeters.js +116 -116
  94. package/src/models/powerMetersManufacturer.js +14 -14
  95. package/src/models/power_meter_account.js +81 -81
  96. package/src/models/power_meter_command_logs.js +30 -30
  97. package/src/models/power_meter_command_queue.js +33 -33
  98. package/src/models/power_meter_negative_balance.js +44 -44
  99. package/src/models/power_prepaid_credits.js +47 -47
  100. package/src/models/power_prepaid_debits.js +50 -50
  101. package/src/models/power_prepaid_orders.js +78 -78
  102. package/src/models/power_sms_notification.js +26 -26
  103. package/src/models/propertyManagerContract.js +556 -556
  104. package/src/models/propertyManagerRevenue.js +195 -195
  105. package/src/models/purchaseOrderInvoice.js +74 -74
  106. package/src/models/purchase_order.js +213 -213
  107. package/src/models/purchase_request.js +110 -110
  108. package/src/models/refresh_token.js +23 -23
  109. package/src/models/reminder.js +197 -197
  110. package/src/models/report.js +13 -13
  111. package/src/models/resident.js +121 -121
  112. package/src/models/rfq_details.js +131 -131
  113. package/src/models/rfq_response.js +153 -153
  114. package/src/models/service_charge_invoice_upload.js +42 -42
  115. package/src/models/service_charge_payments.js +27 -27
  116. package/src/models/servicerequest.js +55 -55
  117. package/src/models/settings.js +62 -62
  118. package/src/models/smart_meter_daily_consumption.js +44 -44
  119. package/src/models/sms_africastalking.js +20 -20
  120. package/src/models/sms_balance_notification.js +26 -26
  121. package/src/models/sms_meliora.js +20 -20
  122. package/src/models/staff.js +36 -36
  123. package/src/models/stocksandspare.js +161 -161
  124. package/src/models/suppliers.js +74 -74
  125. package/src/models/tickets.js +121 -121
  126. package/src/models/unitManagementTemplate.js +44 -44
  127. package/src/models/unitasset.js +25 -25
  128. package/src/models/units.js +117 -117
  129. package/src/models/user.js +186 -186
  130. package/src/models/valueaddedservices.js +79 -79
  131. package/src/models/vas_invoices_upload.js +50 -50
  132. package/src/models/vas_payments.js +24 -24
  133. package/src/models/vasinvoice.js +192 -192
  134. package/src/models/vasvendor.js +57 -57
  135. package/src/models/visitLog.js +95 -95
  136. package/src/models/visitor.js +67 -67
  137. package/src/models/waitlist.js +45 -45
  138. package/src/models/wallet.js +44 -44
  139. package/src/models/wallet_transactions.js +50 -50
  140. package/src/models/water_invoice.js +351 -351
  141. package/src/models/water_meter_Command_Queue.js +33 -33
  142. package/src/models/water_meter_account.js +82 -82
  143. package/src/models/water_meter_billing.js +58 -58
  144. package/src/models/water_meter_communication.js +17 -17
  145. package/src/models/water_meter_communication_logs.js +39 -39
  146. package/src/models/water_meter_concentrator.js +66 -66
  147. package/src/models/water_meter_daily_history.js +32 -32
  148. package/src/models/water_meter_high_risk.js +36 -36
  149. package/src/models/water_meter_iot_cards.js +34 -34
  150. package/src/models/water_meter_manufacturer.js +35 -35
  151. package/src/models/water_meter_monthly_history.js +36 -36
  152. package/src/models/water_meter_negative_amounts.js +44 -44
  153. package/src/models/water_meter_settings.js +261 -261
  154. package/src/models/water_meter_single_day_history.js +34 -34
  155. package/src/models/water_meter_size.js +15 -15
  156. package/src/models/water_meters.js +133 -133
  157. package/src/models/water_meters_delivery.js +76 -76
  158. package/src/models/water_prepaid_credit.js +47 -47
  159. package/src/models/water_prepaid_debit.js +50 -50
  160. package/src/models/workorder.js +49 -49
@@ -1,556 +1,556 @@
1
- const mongoose = require('mongoose');
2
- const Schema = mongoose.Schema;
3
-
4
- const PropertyManagerContractSchema = new Schema(
5
- {
6
- contractName: {
7
- type: String,
8
- required: [true, 'Contract name is required']
9
- },
10
- propertyManager: {
11
- type: mongoose.Schema.Types.ObjectId,
12
- ref: 'User', // References global User collection
13
- required: [true, 'Property manager is required']
14
- },
15
- units: [{
16
- type: mongoose.Schema.Types.ObjectId,
17
- ref: 'Unit',
18
- required: [true, 'At least one unit is required']
19
- }],
20
- customerId: {
21
- type: mongoose.Schema.Types.ObjectId,
22
- ref: 'Customer', // Can reference global or facility-specific Customer
23
- required: [true, 'Customer ID is required']
24
- },
25
- facilityId: {
26
- type: mongoose.Schema.Types.ObjectId,
27
- ref: 'Facility',
28
- required: [true, 'Facility ID is required']
29
- },
30
- // Lease term details
31
- startDate: {
32
- type: Date,
33
- required: function() {
34
- return this.status === 'Active';
35
- }
36
- },
37
- endDate: {
38
- type: Date,
39
- required: function() {
40
- return this.status === 'Active';
41
- }
42
- },
43
- // Payment details - aligned with LeaseAgreement
44
- paymentDueDate: {
45
- type: Number,
46
- required: function() {
47
- return this.status === 'Active';
48
- },
49
- min: [1, 'Payment due date must be between 1 and 31'],
50
- max: [31, 'Payment due date must be between 1 and 31']
51
- },
52
- frequency: {
53
- type: String,
54
- enum: ['Monthly', 'Quarterly', 'Annually'],
55
- default: 'Monthly',
56
- required: function() {
57
- return this.status === 'Active';
58
- }
59
- },
60
- nextInvoiceDate: {
61
- type: Date
62
- },
63
- lastInvoiceDate: {
64
- type: Date
65
- },
66
- autoSend: {
67
- type: Boolean,
68
- default: false
69
- },
70
- balanceBroughtForward: {
71
- type: Number,
72
- default: 0
73
- },
74
- // Management fee
75
- managementFee: {
76
- type: {
77
- type: String,
78
- enum: ['percentage', 'amount'],
79
- required: [true, 'Management fee type is required']
80
- },
81
- value: {
82
- type: Number,
83
- required: [true, 'Management fee value is required'],
84
- min: [0, 'Management fee value cannot be negative'],
85
- validate: {
86
- validator: function(value) {
87
- // If type is percentage, value should not exceed 100
88
- if (this.managementFee.type === 'percentage') {
89
- return value <= 100;
90
- }
91
- return true;
92
- },
93
- message: 'Management fee percentage cannot exceed 100%'
94
- }
95
- }
96
- },
97
- // GL Account configurations - updated to match sample data structure
98
- invoiceDoubleEntryAccount: {
99
- type: mongoose.Schema.Types.ObjectId,
100
- ref: 'GLAccountDoubleEntries'
101
- },
102
- paymentDoubleEntryAccount: {
103
- type: mongoose.Schema.Types.ObjectId,
104
- ref: 'GLAccountDoubleEntries'
105
- },
106
- glAccounts: {
107
- invoice: {
108
- debit: {
109
- type: mongoose.Schema.Types.ObjectId,
110
- ref: 'GLAccount'
111
- },
112
- credit: {
113
- type: mongoose.Schema.Types.ObjectId,
114
- ref: 'GLAccount'
115
- }
116
- },
117
- payment: {
118
- debit: {
119
- type: mongoose.Schema.Types.ObjectId,
120
- ref: 'GLAccount'
121
- },
122
- credit: {
123
- type: mongoose.Schema.Types.ObjectId,
124
- ref: 'GLAccount'
125
- }
126
- }
127
- },
128
- status: {
129
- type: String,
130
- enum: ['Active', 'Inactive', 'Completed', 'Suspended', 'Terminated'],
131
- default: 'Inactive',
132
- required: [true, 'Status is required']
133
- },
134
- createdBy: {
135
- type: mongoose.Schema.Types.ObjectId,
136
- ref: 'User' // References global User collection
137
- },
138
- // Additional fields for contract lifecycle management
139
- suspensionReason: {
140
- type: String
141
- },
142
- terminationReason: {
143
- type: String
144
- },
145
- terminationDate: {
146
- type: Date
147
- },
148
- completionDate: {
149
- type: Date
150
- }
151
- },
152
- {
153
- timestamps: true,
154
- // Ensure virtual fields are included in JSON output
155
- toJSON: { virtuals: true },
156
- toObject: { virtuals: true }
157
- }
158
- );
159
-
160
- // Enhanced pre-save validation
161
- PropertyManagerContractSchema.pre('save', function (next) {
162
- // Validate date range
163
- if (this.startDate && this.endDate && this.startDate >= this.endDate) {
164
- next(new Error('End date must be after start date'));
165
- return;
166
- }
167
-
168
- // Validate units array
169
- if (!this.units || this.units.length === 0) {
170
- next(new Error('At least one unit must be specified'));
171
- return;
172
- }
173
-
174
- // Validate management fee
175
- if (!this.managementFee || !this.managementFee.type || this.managementFee.value === undefined) {
176
- next(new Error('Management fee type and value are required'));
177
- return;
178
- }
179
-
180
- // Validate management fee percentage
181
- if (this.managementFee.type === 'percentage' && this.managementFee.value > 100) {
182
- next(new Error('Management fee percentage cannot exceed 100%'));
183
- return;
184
- }
185
-
186
- // Validate management fee value is non-negative
187
- if (this.managementFee.value < 0) {
188
- next(new Error('Management fee value cannot be negative'));
189
- return;
190
- }
191
-
192
- // Validate payment due date range
193
- if (this.paymentDueDate && (this.paymentDueDate < 1 || this.paymentDueDate > 31)) {
194
- next(new Error('Payment due date must be between 1 and 31'));
195
- return;
196
- }
197
-
198
- // Validate required fields for Active status
199
- if (this.status === 'Active') {
200
- if (!this.startDate) {
201
- next(new Error('Start date is required for Active contracts'));
202
- return;
203
- }
204
- if (!this.endDate) {
205
- next(new Error('End date is required for Active contracts'));
206
- return;
207
- }
208
- if (!this.paymentDueDate) {
209
- next(new Error('Payment due date is required for Active contracts'));
210
- return;
211
- }
212
- if (!this.frequency) {
213
- next(new Error('Payment frequency is required for Active contracts'));
214
- return;
215
- }
216
- }
217
-
218
- next();
219
- });
220
-
221
- // Method to sync with lease data
222
- PropertyManagerContractSchema.methods.syncWithLeaseData = async function(leaseData) {
223
- if (!leaseData) return;
224
-
225
- // Update contract fields from lease data
226
- this.startDate = leaseData.leaseTerms?.startDate || this.startDate;
227
- this.endDate = leaseData.leaseTerms?.endDate || this.endDate;
228
- this.paymentDueDate = leaseData.financialTerms?.paymentDueDate || this.paymentDueDate;
229
- this.balanceBroughtForward = leaseData.financialTerms?.balanceBroughtForward || this.balanceBroughtForward;
230
- this.frequency = leaseData.billingCycle?.frequency || this.frequency;
231
- this.autoSend = leaseData.billingCycle?.autoSend !== undefined ? leaseData.billingCycle.autoSend : this.autoSend;
232
- this.nextInvoiceDate = leaseData.billingCycle?.nextInvoiceDate || this.nextInvoiceDate;
233
- this.lastInvoiceDate = leaseData.billingCycle?.lastInvoiceDate || this.lastInvoiceDate;
234
-
235
- // Automatically activate contract if all required fields are present
236
- if (this.startDate && this.endDate && this.paymentDueDate && this.frequency && this.status === 'Inactive') {
237
- this.status = 'Active';
238
- }
239
-
240
- return this.save();
241
- };
242
-
243
- // Method to check if contract can be activated
244
- PropertyManagerContractSchema.methods.canBeActivated = function() {
245
- return !!(this.startDate && this.endDate && this.paymentDueDate && this.frequency);
246
- };
247
-
248
- // Method to get missing required fields
249
- PropertyManagerContractSchema.methods.getMissingRequiredFields = function() {
250
- const requiredFields = ['startDate', 'endDate', 'paymentDueDate', 'frequency'];
251
- return requiredFields.filter(field => !this[field]);
252
- };
253
-
254
- // Virtual for contract completion status
255
- PropertyManagerContractSchema.virtual('isComplete').get(function() {
256
- return this.canBeActivated();
257
- });
258
-
259
- // Virtual for contract duration in days
260
- PropertyManagerContractSchema.virtual('durationInDays').get(function() {
261
- if (!this.startDate || !this.endDate) return null;
262
- const diffTime = Math.abs(this.endDate - this.startDate);
263
- return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
264
- });
265
-
266
- // Virtual for contract remaining days
267
- PropertyManagerContractSchema.virtual('remainingDays').get(function() {
268
- if (!this.endDate) return null;
269
- const now = new Date();
270
- if (now > this.endDate) return 0;
271
- const diffTime = Math.abs(this.endDate - now);
272
- return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
273
- });
274
-
275
- // Virtual for management fee display
276
- PropertyManagerContractSchema.virtual('managementFeeDisplay').get(function() {
277
- if (!this.managementFee) return null;
278
-
279
- if (this.managementFee.type === 'percentage') {
280
- return `${this.managementFee.value}%`;
281
- } else {
282
- return `$${this.managementFee.value.toLocaleString()}`;
283
- }
284
- });
285
-
286
- // Indexes for better query performance
287
- PropertyManagerContractSchema.index({ customerId: 1, status: 1 });
288
- PropertyManagerContractSchema.index({ facilityId: 1, status: 1 });
289
- PropertyManagerContractSchema.index({ propertyManager: 1, facilityId: 1 });
290
- PropertyManagerContractSchema.index({ units: 1, status: 1 });
291
- PropertyManagerContractSchema.index({ nextInvoiceDate: 1, status: 1 });
292
- PropertyManagerContractSchema.index({ endDate: 1, status: 1 }); // For contract expiration queries
293
- PropertyManagerContractSchema.index({ 'managementFee.type': 1, 'managementFee.value': 1 }); // For fee analysis
294
- PropertyManagerContractSchema.index({ createdAt: -1 }); // For recent contracts
295
- PropertyManagerContractSchema.index({ startDate: 1, endDate: 1 }); // For date range queries
296
-
297
- // Compound indexes for common query patterns
298
- PropertyManagerContractSchema.index({
299
- facilityId: 1,
300
- propertyManager: 1,
301
- status: 1
302
- });
303
-
304
- PropertyManagerContractSchema.index({
305
- facilityId: 1,
306
- customerId: 1,
307
- status: 1
308
- });
309
-
310
- // Text index for searching contract names
311
- PropertyManagerContractSchema.index({
312
- contractName: 'text'
313
- });
314
-
315
- // Static methods for common queries
316
- PropertyManagerContractSchema.statics.findActiveContractsByFacility = function(facilityId) {
317
- return this.find({
318
- facilityId: facilityId,
319
- status: 'Active'
320
- }).populate('propertyManager', 'fullName email')
321
- .populate('units', 'name unitType')
322
- .populate('customerId', 'firstName lastName email');
323
- };
324
-
325
- PropertyManagerContractSchema.statics.findContractsByPropertyManager = function(propertyManagerId, facilityId) {
326
- return this.find({
327
- propertyManager: propertyManagerId,
328
- facilityId: facilityId
329
- }).populate('units', 'name unitType')
330
- .populate('customerId', 'firstName lastName email')
331
- .sort({ createdAt: -1 });
332
- };
333
-
334
- PropertyManagerContractSchema.statics.findExpiringContracts = function(facilityId, daysAhead = 30) {
335
- const futureDate = new Date();
336
- futureDate.setDate(futureDate.getDate() + daysAhead);
337
-
338
- return this.find({
339
- facilityId: facilityId,
340
- status: 'Active',
341
- endDate: {
342
- $gte: new Date(),
343
- $lte: futureDate
344
- }
345
- }).populate('propertyManager', 'fullName email')
346
- .populate('units', 'name unitType')
347
- .populate('customerId', 'firstName lastName email')
348
- .sort({ endDate: 1 });
349
- };
350
-
351
- PropertyManagerContractSchema.statics.findContractsRequiringInvoicing = function(facilityId) {
352
- const today = new Date();
353
- today.setHours(0, 0, 0, 0);
354
-
355
- return this.find({
356
- facilityId: facilityId,
357
- status: 'Active',
358
- nextInvoiceDate: {
359
- $lte: today
360
- }
361
- }).populate('propertyManager', 'fullName email')
362
- .populate('units', 'name unitType')
363
- .populate('customerId', 'firstName lastName email');
364
- };
365
-
366
- PropertyManagerContractSchema.statics.findContractsByFeeType = function(facilityId, feeType) {
367
- return this.find({
368
- facilityId: facilityId,
369
- 'managementFee.type': feeType
370
- }).populate('propertyManager', 'fullName email')
371
- .populate('units', 'name unitType')
372
- .populate('customerId', 'firstName lastName email')
373
- .sort({ 'managementFee.value': -1 });
374
- };
375
-
376
- PropertyManagerContractSchema.statics.getContractStatistics = function(facilityId) {
377
- return this.aggregate([
378
- {
379
- $match: { facilityId: mongoose.Types.ObjectId(facilityId) }
380
- },
381
- {
382
- $group: {
383
- _id: '$status',
384
- count: { $sum: 1 },
385
- totalFeeValue: { $sum: '$managementFee.value' },
386
- avgFeeValue: { $avg: '$managementFee.value' }
387
- }
388
- }
389
- ]);
390
- };
391
-
392
- // Instance methods for contract management
393
- PropertyManagerContractSchema.methods.calculateNextInvoiceDate = function() {
394
- if (!this.lastInvoiceDate && !this.startDate) return null;
395
-
396
- const baseDate = this.lastInvoiceDate || this.startDate;
397
- const nextDate = new Date(baseDate);
398
-
399
- switch (this.frequency) {
400
- case 'Monthly':
401
- nextDate.setMonth(nextDate.getMonth() + 1);
402
- break;
403
- case 'Quarterly':
404
- nextDate.setMonth(nextDate.getMonth() + 3);
405
- break;
406
- case 'Annually':
407
- nextDate.setFullYear(nextDate.getFullYear() + 1);
408
- break;
409
- default:
410
- nextDate.setMonth(nextDate.getMonth() + 1); // Default to monthly
411
- }
412
-
413
- // Set to the payment due date of the month
414
- if (this.paymentDueDate) {
415
- const lastDayOfMonth = new Date(nextDate.getFullYear(), nextDate.getMonth() + 1, 0).getDate();
416
- const dueDate = Math.min(this.paymentDueDate, lastDayOfMonth);
417
- nextDate.setDate(dueDate);
418
- }
419
-
420
- return nextDate;
421
- };
422
-
423
- PropertyManagerContractSchema.methods.updateInvoiceDates = async function(invoiceDate = new Date()) {
424
- this.lastInvoiceDate = invoiceDate;
425
- this.nextInvoiceDate = this.calculateNextInvoiceDate();
426
- return this.save();
427
- };
428
-
429
- PropertyManagerContractSchema.methods.activateContract = async function() {
430
- if (!this.canBeActivated()) {
431
- throw new Error(`Cannot activate contract: missing required fields - ${this.getMissingRequiredFields().join(', ')}`);
432
- }
433
-
434
- this.status = 'Active';
435
-
436
- // Set next invoice date if not already set
437
- if (!this.nextInvoiceDate) {
438
- this.nextInvoiceDate = this.calculateNextInvoiceDate();
439
- }
440
-
441
- return this.save();
442
- };
443
-
444
- PropertyManagerContractSchema.methods.suspendContract = async function(reason) {
445
- this.status = 'Suspended';
446
- this.suspensionReason = reason;
447
- return this.save();
448
- };
449
-
450
- PropertyManagerContractSchema.methods.terminateContract = async function(reason, terminationDate = new Date()) {
451
- this.status = 'Terminated';
452
- this.terminationReason = reason;
453
- this.terminationDate = terminationDate;
454
- return this.save();
455
- };
456
-
457
- PropertyManagerContractSchema.methods.completeContract = async function() {
458
- this.status = 'Completed';
459
- this.completionDate = new Date();
460
- return this.save();
461
- };
462
-
463
- // Method to calculate management fee for a given amount
464
- PropertyManagerContractSchema.methods.calculateManagementFee = function(baseAmount) {
465
- if (!this.managementFee) return 0;
466
-
467
- if (this.managementFee.type === 'percentage') {
468
- return (baseAmount * this.managementFee.value) / 100;
469
- } else {
470
- return this.managementFee.value;
471
- }
472
- };
473
-
474
- // Method to get contract summary
475
- PropertyManagerContractSchema.methods.getContractSummary = function() {
476
- return {
477
- id: this._id,
478
- contractName: this.contractName,
479
- status: this.status,
480
- duration: this.durationInDays,
481
- remainingDays: this.remainingDays,
482
- managementFee: this.managementFeeDisplay,
483
- frequency: this.frequency,
484
- nextInvoiceDate: this.nextInvoiceDate,
485
- isComplete: this.isComplete
486
- };
487
- };
488
-
489
- // Pre-findOneAndUpdate middleware to maintain data integrity
490
- PropertyManagerContractSchema.pre(['findOneAndUpdate', 'updateOne', 'updateMany'], function() {
491
- const update = this.getUpdate();
492
-
493
- // If status is being changed to Active, ensure required fields are present
494
- if (update.$set && update.$set.status === 'Active') {
495
- const requiredFields = ['startDate', 'endDate', 'paymentDueDate', 'frequency'];
496
- // Note: We can't validate here because we don't have access to the full document
497
- // Validation should be done in the application logic before calling update
498
- }
499
-
500
- // Update the updatedAt field
501
- if (update.$set) {
502
- update.$set.updatedAt = new Date();
503
- } else {
504
- this.set({ updatedAt: new Date() });
505
- }
506
- });
507
-
508
- // Post-save middleware for logging and notifications
509
- PropertyManagerContractSchema.post('save', function(doc) {
510
- // Log contract creation/updates
511
- console.log(`Property Manager Contract ${doc.isNew ? 'created' : 'updated'}: ${doc.contractName} (${doc._id})`);
512
-
513
- // Here you could add webhook calls, email notifications, etc.
514
- // Example: notify property manager of contract status changes
515
- if (this.isModified('status')) {
516
- console.log(`Contract status changed to: ${doc.status}`);
517
- // Add notification logic here
518
- }
519
-
520
- // Log invoice date updates
521
- if (this.isModified('nextInvoiceDate') || this.isModified('lastInvoiceDate')) {
522
- console.log(`Invoice dates updated for contract: ${doc.contractName}`);
523
- }
524
- });
525
-
526
- // Error handling for validation errors
527
- PropertyManagerContractSchema.post('save', function(error, doc, next) {
528
- if (error.name === 'ValidationError') {
529
- const messages = Object.values(error.errors).map(err => err.message);
530
- next(new Error(`Contract validation failed: ${messages.join(', ')}`));
531
- } else {
532
- next(error);
533
- }
534
- });
535
-
536
- // JSON transformation to remove sensitive data when returning to client
537
- PropertyManagerContractSchema.methods.toJSON = function() {
538
- const contract = this.toObject();
539
-
540
- // Add computed fields
541
- contract.isComplete = this.isComplete;
542
- contract.canBeActivated = this.canBeActivated();
543
- contract.missingRequiredFields = this.getMissingRequiredFields();
544
- contract.durationInDays = this.durationInDays;
545
- contract.remainingDays = this.remainingDays;
546
- contract.managementFeeDisplay = this.managementFeeDisplay;
547
- contract.contractSummary = this.getContractSummary();
548
-
549
- return contract;
550
- };
551
-
552
- // Export the schema
553
- module.exports = {
554
- schema: PropertyManagerContractSchema,
555
- name: 'PropertyManagerContract'
556
- };
1
+ const mongoose = require('mongoose');
2
+ const Schema = mongoose.Schema;
3
+
4
+ const PropertyManagerContractSchema = new Schema(
5
+ {
6
+ contractName: {
7
+ type: String,
8
+ required: [true, 'Contract name is required']
9
+ },
10
+ propertyManager: {
11
+ type: mongoose.Schema.Types.ObjectId,
12
+ ref: 'User', // References global User collection
13
+ required: [true, 'Property manager is required']
14
+ },
15
+ units: [{
16
+ type: mongoose.Schema.Types.ObjectId,
17
+ ref: 'Unit',
18
+ required: [true, 'At least one unit is required']
19
+ }],
20
+ customerId: {
21
+ type: mongoose.Schema.Types.ObjectId,
22
+ ref: 'Customer', // Can reference global or facility-specific Customer
23
+ required: [true, 'Customer ID is required']
24
+ },
25
+ facilityId: {
26
+ type: mongoose.Schema.Types.ObjectId,
27
+ ref: 'Facility',
28
+ required: [true, 'Facility ID is required']
29
+ },
30
+ // Lease term details
31
+ startDate: {
32
+ type: Date,
33
+ required: function() {
34
+ return this.status === 'Active';
35
+ }
36
+ },
37
+ endDate: {
38
+ type: Date,
39
+ required: function() {
40
+ return this.status === 'Active';
41
+ }
42
+ },
43
+ // Payment details - aligned with LeaseAgreement
44
+ paymentDueDate: {
45
+ type: Number,
46
+ required: function() {
47
+ return this.status === 'Active';
48
+ },
49
+ min: [1, 'Payment due date must be between 1 and 31'],
50
+ max: [31, 'Payment due date must be between 1 and 31']
51
+ },
52
+ frequency: {
53
+ type: String,
54
+ enum: ['Monthly', 'Quarterly', 'Annually'],
55
+ default: 'Monthly',
56
+ required: function() {
57
+ return this.status === 'Active';
58
+ }
59
+ },
60
+ nextInvoiceDate: {
61
+ type: Date
62
+ },
63
+ lastInvoiceDate: {
64
+ type: Date
65
+ },
66
+ autoSend: {
67
+ type: Boolean,
68
+ default: false
69
+ },
70
+ balanceBroughtForward: {
71
+ type: Number,
72
+ default: 0
73
+ },
74
+ // Management fee
75
+ managementFee: {
76
+ type: {
77
+ type: String,
78
+ enum: ['percentage', 'amount'],
79
+ required: [true, 'Management fee type is required']
80
+ },
81
+ value: {
82
+ type: Number,
83
+ required: [true, 'Management fee value is required'],
84
+ min: [0, 'Management fee value cannot be negative'],
85
+ validate: {
86
+ validator: function(value) {
87
+ // If type is percentage, value should not exceed 100
88
+ if (this.managementFee.type === 'percentage') {
89
+ return value <= 100;
90
+ }
91
+ return true;
92
+ },
93
+ message: 'Management fee percentage cannot exceed 100%'
94
+ }
95
+ }
96
+ },
97
+ // GL Account configurations - updated to match sample data structure
98
+ invoiceDoubleEntryAccount: {
99
+ type: mongoose.Schema.Types.ObjectId,
100
+ ref: 'GLAccountDoubleEntries'
101
+ },
102
+ paymentDoubleEntryAccount: {
103
+ type: mongoose.Schema.Types.ObjectId,
104
+ ref: 'GLAccountDoubleEntries'
105
+ },
106
+ glAccounts: {
107
+ invoice: {
108
+ debit: {
109
+ type: mongoose.Schema.Types.ObjectId,
110
+ ref: 'GLAccount'
111
+ },
112
+ credit: {
113
+ type: mongoose.Schema.Types.ObjectId,
114
+ ref: 'GLAccount'
115
+ }
116
+ },
117
+ payment: {
118
+ debit: {
119
+ type: mongoose.Schema.Types.ObjectId,
120
+ ref: 'GLAccount'
121
+ },
122
+ credit: {
123
+ type: mongoose.Schema.Types.ObjectId,
124
+ ref: 'GLAccount'
125
+ }
126
+ }
127
+ },
128
+ status: {
129
+ type: String,
130
+ enum: ['Active', 'Inactive', 'Completed', 'Suspended', 'Terminated'],
131
+ default: 'Inactive',
132
+ required: [true, 'Status is required']
133
+ },
134
+ createdBy: {
135
+ type: mongoose.Schema.Types.ObjectId,
136
+ ref: 'User' // References global User collection
137
+ },
138
+ // Additional fields for contract lifecycle management
139
+ suspensionReason: {
140
+ type: String
141
+ },
142
+ terminationReason: {
143
+ type: String
144
+ },
145
+ terminationDate: {
146
+ type: Date
147
+ },
148
+ completionDate: {
149
+ type: Date
150
+ }
151
+ },
152
+ {
153
+ timestamps: true,
154
+ // Ensure virtual fields are included in JSON output
155
+ toJSON: { virtuals: true },
156
+ toObject: { virtuals: true }
157
+ }
158
+ );
159
+
160
+ // Enhanced pre-save validation
161
+ PropertyManagerContractSchema.pre('save', function (next) {
162
+ // Validate date range
163
+ if (this.startDate && this.endDate && this.startDate >= this.endDate) {
164
+ next(new Error('End date must be after start date'));
165
+ return;
166
+ }
167
+
168
+ // Validate units array
169
+ if (!this.units || this.units.length === 0) {
170
+ next(new Error('At least one unit must be specified'));
171
+ return;
172
+ }
173
+
174
+ // Validate management fee
175
+ if (!this.managementFee || !this.managementFee.type || this.managementFee.value === undefined) {
176
+ next(new Error('Management fee type and value are required'));
177
+ return;
178
+ }
179
+
180
+ // Validate management fee percentage
181
+ if (this.managementFee.type === 'percentage' && this.managementFee.value > 100) {
182
+ next(new Error('Management fee percentage cannot exceed 100%'));
183
+ return;
184
+ }
185
+
186
+ // Validate management fee value is non-negative
187
+ if (this.managementFee.value < 0) {
188
+ next(new Error('Management fee value cannot be negative'));
189
+ return;
190
+ }
191
+
192
+ // Validate payment due date range
193
+ if (this.paymentDueDate && (this.paymentDueDate < 1 || this.paymentDueDate > 31)) {
194
+ next(new Error('Payment due date must be between 1 and 31'));
195
+ return;
196
+ }
197
+
198
+ // Validate required fields for Active status
199
+ if (this.status === 'Active') {
200
+ if (!this.startDate) {
201
+ next(new Error('Start date is required for Active contracts'));
202
+ return;
203
+ }
204
+ if (!this.endDate) {
205
+ next(new Error('End date is required for Active contracts'));
206
+ return;
207
+ }
208
+ if (!this.paymentDueDate) {
209
+ next(new Error('Payment due date is required for Active contracts'));
210
+ return;
211
+ }
212
+ if (!this.frequency) {
213
+ next(new Error('Payment frequency is required for Active contracts'));
214
+ return;
215
+ }
216
+ }
217
+
218
+ next();
219
+ });
220
+
221
+ // Method to sync with lease data
222
+ PropertyManagerContractSchema.methods.syncWithLeaseData = async function(leaseData) {
223
+ if (!leaseData) return;
224
+
225
+ // Update contract fields from lease data
226
+ this.startDate = leaseData.leaseTerms?.startDate || this.startDate;
227
+ this.endDate = leaseData.leaseTerms?.endDate || this.endDate;
228
+ this.paymentDueDate = leaseData.financialTerms?.paymentDueDate || this.paymentDueDate;
229
+ this.balanceBroughtForward = leaseData.financialTerms?.balanceBroughtForward || this.balanceBroughtForward;
230
+ this.frequency = leaseData.billingCycle?.frequency || this.frequency;
231
+ this.autoSend = leaseData.billingCycle?.autoSend !== undefined ? leaseData.billingCycle.autoSend : this.autoSend;
232
+ this.nextInvoiceDate = leaseData.billingCycle?.nextInvoiceDate || this.nextInvoiceDate;
233
+ this.lastInvoiceDate = leaseData.billingCycle?.lastInvoiceDate || this.lastInvoiceDate;
234
+
235
+ // Automatically activate contract if all required fields are present
236
+ if (this.startDate && this.endDate && this.paymentDueDate && this.frequency && this.status === 'Inactive') {
237
+ this.status = 'Active';
238
+ }
239
+
240
+ return this.save();
241
+ };
242
+
243
+ // Method to check if contract can be activated
244
+ PropertyManagerContractSchema.methods.canBeActivated = function() {
245
+ return !!(this.startDate && this.endDate && this.paymentDueDate && this.frequency);
246
+ };
247
+
248
+ // Method to get missing required fields
249
+ PropertyManagerContractSchema.methods.getMissingRequiredFields = function() {
250
+ const requiredFields = ['startDate', 'endDate', 'paymentDueDate', 'frequency'];
251
+ return requiredFields.filter(field => !this[field]);
252
+ };
253
+
254
+ // Virtual for contract completion status
255
+ PropertyManagerContractSchema.virtual('isComplete').get(function() {
256
+ return this.canBeActivated();
257
+ });
258
+
259
+ // Virtual for contract duration in days
260
+ PropertyManagerContractSchema.virtual('durationInDays').get(function() {
261
+ if (!this.startDate || !this.endDate) return null;
262
+ const diffTime = Math.abs(this.endDate - this.startDate);
263
+ return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
264
+ });
265
+
266
+ // Virtual for contract remaining days
267
+ PropertyManagerContractSchema.virtual('remainingDays').get(function() {
268
+ if (!this.endDate) return null;
269
+ const now = new Date();
270
+ if (now > this.endDate) return 0;
271
+ const diffTime = Math.abs(this.endDate - now);
272
+ return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
273
+ });
274
+
275
+ // Virtual for management fee display
276
+ PropertyManagerContractSchema.virtual('managementFeeDisplay').get(function() {
277
+ if (!this.managementFee) return null;
278
+
279
+ if (this.managementFee.type === 'percentage') {
280
+ return `${this.managementFee.value}%`;
281
+ } else {
282
+ return `$${this.managementFee.value.toLocaleString()}`;
283
+ }
284
+ });
285
+
286
+ // Indexes for better query performance
287
+ PropertyManagerContractSchema.index({ customerId: 1, status: 1 });
288
+ PropertyManagerContractSchema.index({ facilityId: 1, status: 1 });
289
+ PropertyManagerContractSchema.index({ propertyManager: 1, facilityId: 1 });
290
+ PropertyManagerContractSchema.index({ units: 1, status: 1 });
291
+ PropertyManagerContractSchema.index({ nextInvoiceDate: 1, status: 1 });
292
+ PropertyManagerContractSchema.index({ endDate: 1, status: 1 }); // For contract expiration queries
293
+ PropertyManagerContractSchema.index({ 'managementFee.type': 1, 'managementFee.value': 1 }); // For fee analysis
294
+ PropertyManagerContractSchema.index({ createdAt: -1 }); // For recent contracts
295
+ PropertyManagerContractSchema.index({ startDate: 1, endDate: 1 }); // For date range queries
296
+
297
+ // Compound indexes for common query patterns
298
+ PropertyManagerContractSchema.index({
299
+ facilityId: 1,
300
+ propertyManager: 1,
301
+ status: 1
302
+ });
303
+
304
+ PropertyManagerContractSchema.index({
305
+ facilityId: 1,
306
+ customerId: 1,
307
+ status: 1
308
+ });
309
+
310
+ // Text index for searching contract names
311
+ PropertyManagerContractSchema.index({
312
+ contractName: 'text'
313
+ });
314
+
315
+ // Static methods for common queries
316
+ PropertyManagerContractSchema.statics.findActiveContractsByFacility = function(facilityId) {
317
+ return this.find({
318
+ facilityId: facilityId,
319
+ status: 'Active'
320
+ }).populate('propertyManager', 'fullName email')
321
+ .populate('units', 'name unitType')
322
+ .populate('customerId', 'firstName lastName email');
323
+ };
324
+
325
+ PropertyManagerContractSchema.statics.findContractsByPropertyManager = function(propertyManagerId, facilityId) {
326
+ return this.find({
327
+ propertyManager: propertyManagerId,
328
+ facilityId: facilityId
329
+ }).populate('units', 'name unitType')
330
+ .populate('customerId', 'firstName lastName email')
331
+ .sort({ createdAt: -1 });
332
+ };
333
+
334
+ PropertyManagerContractSchema.statics.findExpiringContracts = function(facilityId, daysAhead = 30) {
335
+ const futureDate = new Date();
336
+ futureDate.setDate(futureDate.getDate() + daysAhead);
337
+
338
+ return this.find({
339
+ facilityId: facilityId,
340
+ status: 'Active',
341
+ endDate: {
342
+ $gte: new Date(),
343
+ $lte: futureDate
344
+ }
345
+ }).populate('propertyManager', 'fullName email')
346
+ .populate('units', 'name unitType')
347
+ .populate('customerId', 'firstName lastName email')
348
+ .sort({ endDate: 1 });
349
+ };
350
+
351
+ PropertyManagerContractSchema.statics.findContractsRequiringInvoicing = function(facilityId) {
352
+ const today = new Date();
353
+ today.setHours(0, 0, 0, 0);
354
+
355
+ return this.find({
356
+ facilityId: facilityId,
357
+ status: 'Active',
358
+ nextInvoiceDate: {
359
+ $lte: today
360
+ }
361
+ }).populate('propertyManager', 'fullName email')
362
+ .populate('units', 'name unitType')
363
+ .populate('customerId', 'firstName lastName email');
364
+ };
365
+
366
+ PropertyManagerContractSchema.statics.findContractsByFeeType = function(facilityId, feeType) {
367
+ return this.find({
368
+ facilityId: facilityId,
369
+ 'managementFee.type': feeType
370
+ }).populate('propertyManager', 'fullName email')
371
+ .populate('units', 'name unitType')
372
+ .populate('customerId', 'firstName lastName email')
373
+ .sort({ 'managementFee.value': -1 });
374
+ };
375
+
376
+ PropertyManagerContractSchema.statics.getContractStatistics = function(facilityId) {
377
+ return this.aggregate([
378
+ {
379
+ $match: { facilityId: mongoose.Types.ObjectId(facilityId) }
380
+ },
381
+ {
382
+ $group: {
383
+ _id: '$status',
384
+ count: { $sum: 1 },
385
+ totalFeeValue: { $sum: '$managementFee.value' },
386
+ avgFeeValue: { $avg: '$managementFee.value' }
387
+ }
388
+ }
389
+ ]);
390
+ };
391
+
392
+ // Instance methods for contract management
393
+ PropertyManagerContractSchema.methods.calculateNextInvoiceDate = function() {
394
+ if (!this.lastInvoiceDate && !this.startDate) return null;
395
+
396
+ const baseDate = this.lastInvoiceDate || this.startDate;
397
+ const nextDate = new Date(baseDate);
398
+
399
+ switch (this.frequency) {
400
+ case 'Monthly':
401
+ nextDate.setMonth(nextDate.getMonth() + 1);
402
+ break;
403
+ case 'Quarterly':
404
+ nextDate.setMonth(nextDate.getMonth() + 3);
405
+ break;
406
+ case 'Annually':
407
+ nextDate.setFullYear(nextDate.getFullYear() + 1);
408
+ break;
409
+ default:
410
+ nextDate.setMonth(nextDate.getMonth() + 1); // Default to monthly
411
+ }
412
+
413
+ // Set to the payment due date of the month
414
+ if (this.paymentDueDate) {
415
+ const lastDayOfMonth = new Date(nextDate.getFullYear(), nextDate.getMonth() + 1, 0).getDate();
416
+ const dueDate = Math.min(this.paymentDueDate, lastDayOfMonth);
417
+ nextDate.setDate(dueDate);
418
+ }
419
+
420
+ return nextDate;
421
+ };
422
+
423
+ PropertyManagerContractSchema.methods.updateInvoiceDates = async function(invoiceDate = new Date()) {
424
+ this.lastInvoiceDate = invoiceDate;
425
+ this.nextInvoiceDate = this.calculateNextInvoiceDate();
426
+ return this.save();
427
+ };
428
+
429
+ PropertyManagerContractSchema.methods.activateContract = async function() {
430
+ if (!this.canBeActivated()) {
431
+ throw new Error(`Cannot activate contract: missing required fields - ${this.getMissingRequiredFields().join(', ')}`);
432
+ }
433
+
434
+ this.status = 'Active';
435
+
436
+ // Set next invoice date if not already set
437
+ if (!this.nextInvoiceDate) {
438
+ this.nextInvoiceDate = this.calculateNextInvoiceDate();
439
+ }
440
+
441
+ return this.save();
442
+ };
443
+
444
+ PropertyManagerContractSchema.methods.suspendContract = async function(reason) {
445
+ this.status = 'Suspended';
446
+ this.suspensionReason = reason;
447
+ return this.save();
448
+ };
449
+
450
+ PropertyManagerContractSchema.methods.terminateContract = async function(reason, terminationDate = new Date()) {
451
+ this.status = 'Terminated';
452
+ this.terminationReason = reason;
453
+ this.terminationDate = terminationDate;
454
+ return this.save();
455
+ };
456
+
457
+ PropertyManagerContractSchema.methods.completeContract = async function() {
458
+ this.status = 'Completed';
459
+ this.completionDate = new Date();
460
+ return this.save();
461
+ };
462
+
463
+ // Method to calculate management fee for a given amount
464
+ PropertyManagerContractSchema.methods.calculateManagementFee = function(baseAmount) {
465
+ if (!this.managementFee) return 0;
466
+
467
+ if (this.managementFee.type === 'percentage') {
468
+ return (baseAmount * this.managementFee.value) / 100;
469
+ } else {
470
+ return this.managementFee.value;
471
+ }
472
+ };
473
+
474
+ // Method to get contract summary
475
+ PropertyManagerContractSchema.methods.getContractSummary = function() {
476
+ return {
477
+ id: this._id,
478
+ contractName: this.contractName,
479
+ status: this.status,
480
+ duration: this.durationInDays,
481
+ remainingDays: this.remainingDays,
482
+ managementFee: this.managementFeeDisplay,
483
+ frequency: this.frequency,
484
+ nextInvoiceDate: this.nextInvoiceDate,
485
+ isComplete: this.isComplete
486
+ };
487
+ };
488
+
489
+ // Pre-findOneAndUpdate middleware to maintain data integrity
490
+ PropertyManagerContractSchema.pre(['findOneAndUpdate', 'updateOne', 'updateMany'], function() {
491
+ const update = this.getUpdate();
492
+
493
+ // If status is being changed to Active, ensure required fields are present
494
+ if (update.$set && update.$set.status === 'Active') {
495
+ const requiredFields = ['startDate', 'endDate', 'paymentDueDate', 'frequency'];
496
+ // Note: We can't validate here because we don't have access to the full document
497
+ // Validation should be done in the application logic before calling update
498
+ }
499
+
500
+ // Update the updatedAt field
501
+ if (update.$set) {
502
+ update.$set.updatedAt = new Date();
503
+ } else {
504
+ this.set({ updatedAt: new Date() });
505
+ }
506
+ });
507
+
508
+ // Post-save middleware for logging and notifications
509
+ PropertyManagerContractSchema.post('save', function(doc) {
510
+ // Log contract creation/updates
511
+ console.log(`Property Manager Contract ${doc.isNew ? 'created' : 'updated'}: ${doc.contractName} (${doc._id})`);
512
+
513
+ // Here you could add webhook calls, email notifications, etc.
514
+ // Example: notify property manager of contract status changes
515
+ if (this.isModified('status')) {
516
+ console.log(`Contract status changed to: ${doc.status}`);
517
+ // Add notification logic here
518
+ }
519
+
520
+ // Log invoice date updates
521
+ if (this.isModified('nextInvoiceDate') || this.isModified('lastInvoiceDate')) {
522
+ console.log(`Invoice dates updated for contract: ${doc.contractName}`);
523
+ }
524
+ });
525
+
526
+ // Error handling for validation errors
527
+ PropertyManagerContractSchema.post('save', function(error, doc, next) {
528
+ if (error.name === 'ValidationError') {
529
+ const messages = Object.values(error.errors).map(err => err.message);
530
+ next(new Error(`Contract validation failed: ${messages.join(', ')}`));
531
+ } else {
532
+ next(error);
533
+ }
534
+ });
535
+
536
+ // JSON transformation to remove sensitive data when returning to client
537
+ PropertyManagerContractSchema.methods.toJSON = function() {
538
+ const contract = this.toObject();
539
+
540
+ // Add computed fields
541
+ contract.isComplete = this.isComplete;
542
+ contract.canBeActivated = this.canBeActivated();
543
+ contract.missingRequiredFields = this.getMissingRequiredFields();
544
+ contract.durationInDays = this.durationInDays;
545
+ contract.remainingDays = this.remainingDays;
546
+ contract.managementFeeDisplay = this.managementFeeDisplay;
547
+ contract.contractSummary = this.getContractSummary();
548
+
549
+ return contract;
550
+ };
551
+
552
+ // Export the schema
553
+ module.exports = {
554
+ schema: PropertyManagerContractSchema,
555
+ name: 'PropertyManagerContract'
556
+ };