payservedb 8.3.4 → 8.3.5

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 (177) hide show
  1. package/.env +2 -2
  2. package/index.js +306 -306
  3. package/package.json +17 -17
  4. package/src/models/account.js +52 -52
  5. package/src/models/agent_departments.js +59 -59
  6. package/src/models/agent_notifications.js +53 -53
  7. package/src/models/agent_performance.js +127 -127
  8. package/src/models/agent_roles.js +77 -77
  9. package/src/models/agents.js +154 -154
  10. package/src/models/apilog.js +18 -18
  11. package/src/models/approvalsWorkflows.js +49 -49
  12. package/src/models/archivedapilog.js +18 -18
  13. package/src/models/asset.js +92 -92
  14. package/src/models/assetsAssignment.js +64 -64
  15. package/src/models/auditTrail.js +346 -346
  16. package/src/models/bankdetails.js +43 -47
  17. package/src/models/billerAddress.js +124 -124
  18. package/src/models/booking_invoice.js +151 -151
  19. package/src/models/bookinganalytics.js +63 -63
  20. package/src/models/bookingconfig.js +45 -45
  21. package/src/models/bookingproperty.js +122 -122
  22. package/src/models/bookingreservation.js +192 -192
  23. package/src/models/bookingrevenuerecord.js +84 -84
  24. package/src/models/budget.js +95 -95
  25. package/src/models/budgetCategory.js +19 -19
  26. package/src/models/campaigns.js +108 -108
  27. package/src/models/cashpayment.js +264 -264
  28. package/src/models/combinedUnits.js +62 -62
  29. package/src/models/common_area_electricity.js +38 -38
  30. package/src/models/common_area_generator.js +41 -41
  31. package/src/models/common_area_utility_alert.js +37 -37
  32. package/src/models/common_area_water.js +39 -39
  33. package/src/models/communication_status.js +33 -33
  34. package/src/models/communication_user_opt.js +32 -32
  35. package/src/models/company.js +53 -53
  36. package/src/models/coreBaseSettings.js +16 -16
  37. package/src/models/coreInvoiceSettings.js +100 -100
  38. package/src/models/counter_schema.js +21 -21
  39. package/src/models/country_tax.js +42 -42
  40. package/src/models/currency_settings.js +39 -39
  41. package/src/models/customer.js +210 -210
  42. package/src/models/customer_satisfaction_survey.js +278 -278
  43. package/src/models/customer_surveys.js +139 -139
  44. package/src/models/customer_tickets.js +239 -239
  45. package/src/models/dailyChecklist.js +312 -312
  46. package/src/models/default_payment_details.js +17 -17
  47. package/src/models/deliveryTimeMarks.js +18 -18
  48. package/src/models/document_type.js +19 -19
  49. package/src/models/dutyRosterChecklist.js +250 -250
  50. package/src/models/dutyroster.js +136 -136
  51. package/src/models/email.js +37 -37
  52. package/src/models/email_sms_queue.js +61 -61
  53. package/src/models/entry_exit.js +53 -53
  54. package/src/models/expense.js +99 -99
  55. package/src/models/expense_category.js +45 -45
  56. package/src/models/facility.js +62 -62
  57. package/src/models/facilityBillingPrices.js +29 -29
  58. package/src/models/facilityInvoice.js +240 -223
  59. package/src/models/facilityInvoicePayment.js +52 -47
  60. package/src/models/facilityInvoiceRecipient.js +32 -32
  61. package/src/models/facilityWalletTransactionsMetadata.js +236 -236
  62. package/src/models/facility_departements.js +20 -20
  63. package/src/models/facility_payment_details.js +20 -20
  64. package/src/models/facilityasset.js +25 -25
  65. package/src/models/faq.js +18 -18
  66. package/src/models/gl_account_double_entries.js +25 -25
  67. package/src/models/gl_accounts.js +56 -56
  68. package/src/models/gl_entries.js +49 -49
  69. package/src/models/goodsReceivedNotes.js +115 -115
  70. package/src/models/guard.js +47 -47
  71. package/src/models/handover.js +247 -247
  72. package/src/models/inspection_category.js +38 -38
  73. package/src/models/invoice.js +387 -387
  74. package/src/models/invoicing_schedule.js +36 -36
  75. package/src/models/item_inspection.js +96 -96
  76. package/src/models/knowledge_base.js +109 -109
  77. package/src/models/knowledge_base_rating.js +44 -44
  78. package/src/models/leaseagreement.js +243 -236
  79. package/src/models/leasetemplate.js +17 -17
  80. package/src/models/levy.js +223 -223
  81. package/src/models/levy_invoice_settings.js +26 -26
  82. package/src/models/levycontract.js +173 -173
  83. package/src/models/levytype.js +23 -23
  84. package/src/models/maintenance_service_vendor.js +38 -38
  85. package/src/models/maintenance_services.js +17 -17
  86. package/src/models/maintenancerequisition.js +31 -31
  87. package/src/models/master_workplan.js +32 -32
  88. package/src/models/master_workplan_child.js +34 -34
  89. package/src/models/message.js +38 -38
  90. package/src/models/module.js +21 -21
  91. package/src/models/notification.js +44 -44
  92. package/src/models/paymentTermsMarks.js +19 -19
  93. package/src/models/penalty.js +76 -76
  94. package/src/models/pendingCredentials.js +32 -32
  95. package/src/models/powerMeterCommunicationProtocol.js +17 -17
  96. package/src/models/powerMeterCustomerAccount.js +78 -78
  97. package/src/models/powerMeterCustomerBand.js +14 -14
  98. package/src/models/powerMeterDailyReading.js +30 -30
  99. package/src/models/powerMeterGateways.js +40 -40
  100. package/src/models/powerMeterMonthlyReading.js +34 -34
  101. package/src/models/powerMeterPowerCharges.js +85 -85
  102. package/src/models/powerMeterSettings.js +159 -159
  103. package/src/models/powerMeterSingleDayReading.js +32 -32
  104. package/src/models/powerMeters.js +116 -116
  105. package/src/models/powerMetersManufacturer.js +14 -14
  106. package/src/models/power_meter_account.js +81 -81
  107. package/src/models/power_meter_command_logs.js +30 -30
  108. package/src/models/power_meter_command_queue.js +33 -33
  109. package/src/models/power_meter_negative_balance.js +44 -44
  110. package/src/models/power_prepaid_credits.js +47 -47
  111. package/src/models/power_prepaid_debits.js +53 -53
  112. package/src/models/power_prepaid_orders.js +78 -78
  113. package/src/models/power_sms_notification.js +26 -26
  114. package/src/models/propertyManagerContract.js +556 -556
  115. package/src/models/propertyManagerRevenue.js +195 -195
  116. package/src/models/purchaseOrderInvoice.js +74 -74
  117. package/src/models/purchase_order.js +213 -213
  118. package/src/models/purchase_request.js +110 -110
  119. package/src/models/refresh_token.js +23 -23
  120. package/src/models/reminder.js +197 -197
  121. package/src/models/report.js +13 -13
  122. package/src/models/resident.js +121 -121
  123. package/src/models/rfq_details.js +131 -131
  124. package/src/models/rfq_response.js +153 -153
  125. package/src/models/service_charge_invoice_upload.js +42 -42
  126. package/src/models/service_charge_payments.js +27 -27
  127. package/src/models/servicerequest.js +55 -55
  128. package/src/models/settings.js +62 -62
  129. package/src/models/short_urls.js +21 -21
  130. package/src/models/smart_meter_daily_consumption.js +44 -44
  131. package/src/models/sms_africastalking.js +20 -20
  132. package/src/models/sms_balance_notification.js +26 -26
  133. package/src/models/sms_meliora.js +20 -20
  134. package/src/models/staff.js +36 -36
  135. package/src/models/stocksandspare.js +161 -161
  136. package/src/models/suppliers.js +74 -74
  137. package/src/models/tickets.js +173 -173
  138. package/src/models/tickets_category.js +72 -72
  139. package/src/models/unitManagementTemplate.js +44 -44
  140. package/src/models/unitasset.js +25 -25
  141. package/src/models/units.js +118 -118
  142. package/src/models/user.js +186 -186
  143. package/src/models/valueaddedservices.js +79 -79
  144. package/src/models/vas_invoices_upload.js +50 -50
  145. package/src/models/vas_payments.js +24 -24
  146. package/src/models/vasinvoice.js +192 -192
  147. package/src/models/vasvendor.js +57 -57
  148. package/src/models/visitLog.js +95 -95
  149. package/src/models/visitor.js +67 -67
  150. package/src/models/waitlist.js +45 -45
  151. package/src/models/wallet.js +44 -44
  152. package/src/models/wallet_transactions.js +50 -50
  153. package/src/models/water_invoice.js +351 -351
  154. package/src/models/water_meter_Command_Queue.js +33 -33
  155. package/src/models/water_meter_account.js +82 -82
  156. package/src/models/water_meter_billing.js +58 -58
  157. package/src/models/water_meter_communication.js +17 -17
  158. package/src/models/water_meter_communication_logs.js +39 -39
  159. package/src/models/water_meter_concentrator.js +70 -70
  160. package/src/models/water_meter_daily_history.js +32 -32
  161. package/src/models/water_meter_high_risk.js +36 -36
  162. package/src/models/water_meter_iot_cards.js +34 -34
  163. package/src/models/water_meter_manufacturer.js +35 -35
  164. package/src/models/water_meter_monthly_history.js +36 -36
  165. package/src/models/water_meter_negative_amounts.js +44 -44
  166. package/src/models/water_meter_settings.js +276 -283
  167. package/src/models/water_meter_single_day_history.js +34 -34
  168. package/src/models/water_meter_size.js +15 -15
  169. package/src/models/water_meters.js +133 -133
  170. package/src/models/water_meters_delivery.js +76 -76
  171. package/src/models/water_prepaid_credit.js +47 -47
  172. package/src/models/water_prepaid_debit.js +50 -50
  173. package/src/models/workorder.js +49 -49
  174. package/.idea/material_theme_project_new.xml +0 -12
  175. package/.idea/modules.xml +0 -8
  176. package/.idea/psdb.iml +0 -12
  177. package/.idea/vcs.xml +0 -6
@@ -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
+ };