payservedb 8.3.9 → 8.4.1

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