payservedb 6.0.8 → 6.1.0

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