payservedb 5.9.2 → 5.9.3

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