payservedb 8.3.5 → 8.3.6

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