payservedb 5.6.7 → 5.6.9
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.
- package/.env +2 -2
- package/index.js +195 -193
- package/package.json +17 -17
- package/src/models/account.js +35 -35
- package/src/models/apilog.js +18 -18
- package/src/models/approvalsWorkflows.js +49 -49
- package/src/models/archivedapilog.js +18 -18
- package/src/models/archivedauditlog.js +83 -83
- package/src/models/asset.js +34 -34
- package/src/models/auditlog.js +83 -83
- package/src/models/bankdetails.js +40 -40
- package/src/models/billerAddress.js +120 -0
- package/src/models/booking_invoice.js +151 -151
- package/src/models/bookinganalytics.js +63 -63
- package/src/models/bookingconfig.js +45 -45
- package/src/models/bookingproperty.js +112 -112
- package/src/models/bookingreservation.js +192 -192
- package/src/models/bookingrevenuerecord.js +84 -84
- package/src/models/budget.js +91 -91
- package/src/models/budgetCategory.js +19 -19
- package/src/models/campaigns.js +72 -72
- package/src/models/cashpayment.js +262 -262
- package/src/models/combinedUnits.js +62 -62
- package/src/models/common_area_electricity.js +38 -38
- package/src/models/common_area_generator.js +41 -41
- package/src/models/common_area_utility_alert.js +37 -37
- package/src/models/common_area_water.js +39 -39
- package/src/models/company.js +53 -53
- package/src/models/country_tax.js +42 -42
- package/src/models/currency_settings.js +39 -39
- package/src/models/customer.js +200 -200
- package/src/models/default_payment_details.js +17 -17
- package/src/models/dutyroster.js +107 -107
- package/src/models/email.js +37 -37
- package/src/models/entry_exit.js +53 -53
- package/src/models/expense.js +99 -99
- package/src/models/expense_category.js +45 -19
- package/src/models/facility.js +61 -61
- package/src/models/facility_payment_details.js +20 -20
- package/src/models/facilityasset.js +25 -25
- package/src/models/faq.js +18 -18
- package/src/models/gl_account_double_entries.js +25 -25
- package/src/models/gl_accounts.js +56 -56
- package/src/models/gl_entries.js +49 -49
- package/src/models/guard.js +47 -47
- package/src/models/handover.js +246 -246
- package/src/models/invoice.js +336 -336
- package/src/models/item_inspection.js +67 -67
- package/src/models/leaseagreement.js +221 -221
- package/src/models/leasetemplate.js +17 -17
- package/src/models/levy.js +152 -152
- package/src/models/levy_invoice_settings.js +26 -26
- package/src/models/levycontract.js +117 -117
- package/src/models/levytype.js +23 -23
- package/src/models/maintenance_service_vendor.js +38 -38
- package/src/models/maintenance_services.js +17 -17
- package/src/models/maintenancerequisition.js +31 -31
- package/src/models/message.js +38 -38
- package/src/models/module.js +21 -21
- package/src/models/notification.js +44 -44
- package/src/models/penalty.js +76 -76
- package/src/models/pendingCredentials.js +32 -32
- package/src/models/propertyManagerContract.js +358 -358
- package/src/models/propertyManagerRevenue.js +100 -100
- package/src/models/purchase_order.js +194 -194
- package/src/models/purchase_request.js +110 -110
- package/src/models/refresh_token.js +23 -23
- package/src/models/reminder.js +197 -197
- package/src/models/report.js +13 -13
- package/src/models/resident.js +121 -121
- package/src/models/rfq_details.js +131 -131
- package/src/models/rfq_response.js +105 -105
- package/src/models/service_charge_invoice_upload.js +42 -42
- package/src/models/service_charge_payments.js +27 -27
- package/src/models/servicerequest.js +55 -55
- package/src/models/settings.js +62 -62
- package/src/models/smart_meter_daily_consumption.js +44 -44
- package/src/models/sms_africastalking.js +20 -20
- package/src/models/sms_meliora.js +20 -20
- package/src/models/staff.js +36 -36
- package/src/models/stocksandspare.js +34 -34
- package/src/models/suppliers.js +74 -74
- package/src/models/tickets.js +109 -109
- package/src/models/unitManagementTemplate.js +44 -44
- package/src/models/unitasset.js +25 -25
- package/src/models/units.js +112 -112
- package/src/models/user.js +187 -187
- package/src/models/valueaddedservices.js +79 -79
- package/src/models/vas_invoices_upload.js +50 -50
- package/src/models/vas_payments.js +24 -24
- package/src/models/vasinvoice.js +192 -192
- package/src/models/vasvendor.js +57 -57
- package/src/models/visitLog.js +86 -86
- package/src/models/visitor.js +67 -67
- package/src/models/waitlist.js +45 -45
- package/src/models/water_invoice.js +193 -193
- package/src/models/water_meter_account.js +78 -78
- package/src/models/water_meter_billing.js +58 -58
- package/src/models/water_meter_communication.js +17 -17
- package/src/models/water_meter_communication_logs.js +30 -30
- package/src/models/water_meter_concentrator.js +59 -59
- package/src/models/water_meter_daily_history.js +32 -32
- package/src/models/water_meter_iot_cards.js +34 -34
- package/src/models/water_meter_manufacturer.js +35 -35
- package/src/models/water_meter_monthly_history.js +36 -36
- package/src/models/water_meter_settings.js +114 -88
- package/src/models/water_meter_single_day_history.js +34 -34
- package/src/models/water_meter_size.js +15 -15
- package/src/models/water_meters.js +117 -117
- package/src/models/water_meters_delivery.js +76 -76
- package/src/models/water_prepaid_credit.js +43 -43
- package/src/models/water_prepaid_debit.js +50 -50
- package/src/models/workorder.js +49 -49
|
@@ -1,359 +1,359 @@
|
|
|
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',
|
|
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',
|
|
23
|
-
required: [true, 'Customer ID is required']
|
|
24
|
-
},
|
|
25
|
-
// Optional fields - only required when status is "Active"
|
|
26
|
-
startDate: {
|
|
27
|
-
type: Date,
|
|
28
|
-
required: function() {
|
|
29
|
-
return this.status === 'Active';
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
endDate: {
|
|
33
|
-
type: Date,
|
|
34
|
-
required: function() {
|
|
35
|
-
return this.status === 'Active';
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
invoiceDay: {
|
|
39
|
-
type: Number,
|
|
40
|
-
required: function() {
|
|
41
|
-
return this.status === 'Active';
|
|
42
|
-
},
|
|
43
|
-
min: [1, 'Invoice day must be between 1 and 31'],
|
|
44
|
-
max: [31, 'Invoice day must be between 1 and 31']
|
|
45
|
-
},
|
|
46
|
-
dueDate: {
|
|
47
|
-
type: String, // Store as string like "5th", "10th", etc.
|
|
48
|
-
required: function() {
|
|
49
|
-
return this.status === 'Active';
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
balanceBroughtForward: {
|
|
53
|
-
type: Number,
|
|
54
|
-
default: 0
|
|
55
|
-
},
|
|
56
|
-
collectionFrequency: {
|
|
57
|
-
type: String,
|
|
58
|
-
enum: ['Daily', 'Weekly', 'Bi-Weekly', 'Monthly', 'Quarterly', 'Semi-Annually', 'Annually'],
|
|
59
|
-
default: 'Monthly',
|
|
60
|
-
required: function() {
|
|
61
|
-
return this.status === 'Active';
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
// Always required fields regardless of status
|
|
65
|
-
managementFee: {
|
|
66
|
-
type: {
|
|
67
|
-
type: String,
|
|
68
|
-
enum: ['percentage', 'amount'],
|
|
69
|
-
required: [true, 'Management fee type is required']
|
|
70
|
-
},
|
|
71
|
-
value: {
|
|
72
|
-
type: Number,
|
|
73
|
-
required: [true, 'Management fee value is required'],
|
|
74
|
-
min: [0, 'Management fee value cannot be negative']
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
// GL Account configurations - matching LeaseAgreement pattern
|
|
78
|
-
invoiceDoubleEntryAccount: {
|
|
79
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
80
|
-
ref: 'GLAccountDoubleEntries'
|
|
81
|
-
},
|
|
82
|
-
paymentDoubleEntryAccount: {
|
|
83
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
84
|
-
ref: 'GLAccountDoubleEntries'
|
|
85
|
-
},
|
|
86
|
-
// GL Account direct configurations (used when creating double entry records)
|
|
87
|
-
glAccounts: {
|
|
88
|
-
invoice: {
|
|
89
|
-
debit: {
|
|
90
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
91
|
-
ref: 'GLAccount'
|
|
92
|
-
},
|
|
93
|
-
credit: {
|
|
94
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
95
|
-
ref: 'GLAccount'
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
payment: {
|
|
99
|
-
debit: {
|
|
100
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
101
|
-
ref: 'GLAccount'
|
|
102
|
-
},
|
|
103
|
-
credit: {
|
|
104
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
105
|
-
ref: 'GLAccount'
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
status: {
|
|
110
|
-
type: String,
|
|
111
|
-
enum: ['Active', 'Inactive', 'Completed', 'Suspended', 'Terminated'],
|
|
112
|
-
default: 'Inactive',
|
|
113
|
-
required: [true, 'Status is required']
|
|
114
|
-
},
|
|
115
|
-
facilityId: {
|
|
116
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
117
|
-
ref: 'Facility',
|
|
118
|
-
required: [true, 'Facility ID is required']
|
|
119
|
-
},
|
|
120
|
-
lastInvoiceDate: {
|
|
121
|
-
type: Date
|
|
122
|
-
},
|
|
123
|
-
nextInvoiceDate: {
|
|
124
|
-
type: Date
|
|
125
|
-
},
|
|
126
|
-
autoSend: {
|
|
127
|
-
type: Boolean,
|
|
128
|
-
default: false
|
|
129
|
-
},
|
|
130
|
-
// Track data source and sync information
|
|
131
|
-
leaseDataSource: {
|
|
132
|
-
type: String,
|
|
133
|
-
enum: ['lease', 'manual'],
|
|
134
|
-
default: 'manual'
|
|
135
|
-
},
|
|
136
|
-
lastSyncedAt: {
|
|
137
|
-
type: Date
|
|
138
|
-
},
|
|
139
|
-
// Track which units have lease agreements
|
|
140
|
-
unitsWithLeases: [{
|
|
141
|
-
unitId: {
|
|
142
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
143
|
-
ref: 'Unit'
|
|
144
|
-
},
|
|
145
|
-
leaseId: {
|
|
146
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
147
|
-
ref: 'LeaseAgreement'
|
|
148
|
-
},
|
|
149
|
-
syncedAt: {
|
|
150
|
-
type: Date,
|
|
151
|
-
default: Date.now
|
|
152
|
-
}
|
|
153
|
-
}],
|
|
154
|
-
createdBy: {
|
|
155
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
156
|
-
ref: 'User'
|
|
157
|
-
},
|
|
158
|
-
updatedBy: {
|
|
159
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
160
|
-
ref: 'User'
|
|
161
|
-
},
|
|
162
|
-
// Track contract edit history
|
|
163
|
-
editHistory: [{
|
|
164
|
-
editedBy: {
|
|
165
|
-
type: mongoose.Schema.Types.Mixed,
|
|
166
|
-
ref: 'User'
|
|
167
|
-
},
|
|
168
|
-
editedAt: {
|
|
169
|
-
type: Date,
|
|
170
|
-
default: Date.now
|
|
171
|
-
},
|
|
172
|
-
reason: {
|
|
173
|
-
type: String,
|
|
174
|
-
required: true
|
|
175
|
-
},
|
|
176
|
-
changes: {
|
|
177
|
-
type: Object
|
|
178
|
-
}
|
|
179
|
-
}]
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
timestamps: true,
|
|
183
|
-
toJSON: { virtuals: true },
|
|
184
|
-
toObject: { virtuals: true }
|
|
185
|
-
}
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
// Virtual populate for Property Manager details
|
|
189
|
-
PropertyManagerContractSchema.virtual('propertyManagerDetails', {
|
|
190
|
-
ref: 'User',
|
|
191
|
-
localField: 'propertyManager',
|
|
192
|
-
foreignField: '_id',
|
|
193
|
-
justOne: true
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
// Virtual populate for Customer details
|
|
197
|
-
PropertyManagerContractSchema.virtual('customer', {
|
|
198
|
-
ref: 'Customer',
|
|
199
|
-
localField: 'customerId',
|
|
200
|
-
foreignField: '_id',
|
|
201
|
-
justOne: true
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// Virtual populate for Units details
|
|
205
|
-
PropertyManagerContractSchema.virtual('unitDetails', {
|
|
206
|
-
ref: 'Unit',
|
|
207
|
-
localField: 'units',
|
|
208
|
-
foreignField: '_id'
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// Virtual populate for invoice double entry account
|
|
212
|
-
PropertyManagerContractSchema.virtual('invoiceDoubleEntry', {
|
|
213
|
-
ref: 'GLAccountDoubleEntries',
|
|
214
|
-
localField: 'invoiceDoubleEntryAccount',
|
|
215
|
-
foreignField: '_id',
|
|
216
|
-
justOne: true
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// Virtual populate for payment double entry account
|
|
220
|
-
PropertyManagerContractSchema.virtual('paymentDoubleEntry', {
|
|
221
|
-
ref: 'GLAccountDoubleEntries',
|
|
222
|
-
localField: 'paymentDoubleEntryAccount',
|
|
223
|
-
foreignField: '_id',
|
|
224
|
-
justOne: true
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Virtual populate for Facility details
|
|
228
|
-
PropertyManagerContractSchema.virtual('facility', {
|
|
229
|
-
ref: 'Facility',
|
|
230
|
-
localField: 'facilityId',
|
|
231
|
-
foreignField: '_id',
|
|
232
|
-
justOne: true
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// Virtual to check if contract is complete (has all lease-dependent fields)
|
|
236
|
-
PropertyManagerContractSchema.virtual('isComplete').get(function() {
|
|
237
|
-
return this.startDate && this.endDate && this.invoiceDay && this.dueDate && this.collectionFrequency;
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// Virtual to get completion percentage
|
|
241
|
-
PropertyManagerContractSchema.virtual('completionPercentage').get(function() {
|
|
242
|
-
const requiredFields = ['startDate', 'endDate', 'invoiceDay', 'dueDate', 'collectionFrequency'];
|
|
243
|
-
const completedFields = requiredFields.filter(field => this[field] != null).length;
|
|
244
|
-
return Math.round((completedFields / requiredFields.length) * 100);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// Pre-save middleware to ensure endDate is after startDate (only when both exist)
|
|
248
|
-
PropertyManagerContractSchema.pre('save', function (next) {
|
|
249
|
-
if (this.startDate && this.endDate && this.startDate >= this.endDate) {
|
|
250
|
-
next(new Error('End date must be after start date'));
|
|
251
|
-
} else {
|
|
252
|
-
next();
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Pre-save middleware to validate units array is not empty
|
|
257
|
-
PropertyManagerContractSchema.pre('save', function (next) {
|
|
258
|
-
if (!this.units || this.units.length === 0) {
|
|
259
|
-
next(new Error('At least one unit must be specified'));
|
|
260
|
-
} else {
|
|
261
|
-
next();
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Pre-save middleware to validate management fee
|
|
266
|
-
PropertyManagerContractSchema.pre('save', function (next) {
|
|
267
|
-
if (this.managementFee.type === 'percentage' && this.managementFee.value > 100) {
|
|
268
|
-
next(new Error('Management fee percentage cannot exceed 100%'));
|
|
269
|
-
} else {
|
|
270
|
-
next();
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// Pre-save middleware to auto-update status based on completion
|
|
275
|
-
PropertyManagerContractSchema.pre('save', function (next) {
|
|
276
|
-
// If contract has all required lease fields and is currently Inactive, make it Active
|
|
277
|
-
if (this.status === 'Inactive' && this.isComplete) {
|
|
278
|
-
this.status = 'Active';
|
|
279
|
-
this.leaseDataSource = 'lease';
|
|
280
|
-
this.lastSyncedAt = new Date();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// If contract is missing required fields and is currently Active, make it Inactive
|
|
284
|
-
if (this.status === 'Active' && !this.isComplete) {
|
|
285
|
-
this.status = 'Inactive';
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
next();
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// Pre-save middleware to set next invoice date based on collection frequency (only for active contracts)
|
|
292
|
-
PropertyManagerContractSchema.pre('save', function (next) {
|
|
293
|
-
if (this.isNew && this.status === 'Active' && this.startDate && this.invoiceDay && !this.nextInvoiceDate) {
|
|
294
|
-
const baseDate = new Date(this.startDate);
|
|
295
|
-
baseDate.setDate(this.invoiceDay);
|
|
296
|
-
|
|
297
|
-
// If the invoice day has passed this month, set for next month
|
|
298
|
-
if (baseDate < this.startDate) {
|
|
299
|
-
baseDate.setMonth(baseDate.getMonth() + 1);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
this.nextInvoiceDate = baseDate;
|
|
303
|
-
}
|
|
304
|
-
next();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Method to sync with lease data
|
|
308
|
-
PropertyManagerContractSchema.methods.syncWithLeaseData = async function(leaseData) {
|
|
309
|
-
if (!leaseData) return;
|
|
310
|
-
|
|
311
|
-
this.startDate = leaseData.leaseTerms?.startDate || this.startDate;
|
|
312
|
-
this.endDate = leaseData.leaseTerms?.endDate || this.endDate;
|
|
313
|
-
this.invoiceDay = leaseData.financialTerms?.paymentDueDate || this.invoiceDay;
|
|
314
|
-
this.balanceBroughtForward = leaseData.financialTerms?.balanceBroughtForward || this.balanceBroughtForward;
|
|
315
|
-
this.collectionFrequency = leaseData.billingCycle?.frequency || this.collectionFrequency;
|
|
316
|
-
this.autoSend = leaseData.billingCycle?.autoSend !== undefined ? leaseData.billingCycle.autoSend : this.autoSend;
|
|
317
|
-
|
|
318
|
-
// Copy GL accounts if available
|
|
319
|
-
if (leaseData.glAccounts) {
|
|
320
|
-
this.glAccounts = leaseData.glAccounts;
|
|
321
|
-
}
|
|
322
|
-
if (leaseData.invoiceDoubleEntryAccount) {
|
|
323
|
-
this.invoiceDoubleEntryAccount = leaseData.invoiceDoubleEntryAccount;
|
|
324
|
-
}
|
|
325
|
-
if (leaseData.paymentDoubleEntryAccount) {
|
|
326
|
-
this.paymentDoubleEntryAccount = leaseData.paymentDoubleEntryAccount;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
this.leaseDataSource = 'lease';
|
|
330
|
-
this.lastSyncedAt = new Date();
|
|
331
|
-
|
|
332
|
-
// Auto-calculate due date from payment due date if not set
|
|
333
|
-
if (this.invoiceDay && !this.dueDate) {
|
|
334
|
-
const dueDateMap = {
|
|
335
|
-
1: "1st", 5: "5th", 10: "10th", 15: "15th"
|
|
336
|
-
};
|
|
337
|
-
this.dueDate = dueDateMap[this.invoiceDay] || `${this.invoiceDay}th`;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return this.save();
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
// Indexes for efficient queries
|
|
344
|
-
PropertyManagerContractSchema.index({ customerId: 1, status: 1 });
|
|
345
|
-
PropertyManagerContractSchema.index({ facilityId: 1 });
|
|
346
|
-
PropertyManagerContractSchema.index({ units: 1 });
|
|
347
|
-
PropertyManagerContractSchema.index({ propertyManager: 1 }); // New index for property manager
|
|
348
|
-
PropertyManagerContractSchema.index({ invoiceDoubleEntryAccount: 1 });
|
|
349
|
-
PropertyManagerContractSchema.index({ paymentDoubleEntryAccount: 1 });
|
|
350
|
-
PropertyManagerContractSchema.index({ startDate: 1, endDate: 1 });
|
|
351
|
-
PropertyManagerContractSchema.index({ nextInvoiceDate: 1, status: 1 });
|
|
352
|
-
PropertyManagerContractSchema.index({ leaseDataSource: 1 });
|
|
353
|
-
PropertyManagerContractSchema.index({ 'unitsWithLeases.unitId': 1 });
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
// Compile the model from the schema
|
|
357
|
-
const PropertyManagerContract = mongoose.model("PropertyManagerContract", PropertyManagerContractSchema);
|
|
358
|
-
|
|
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',
|
|
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',
|
|
23
|
+
required: [true, 'Customer ID is required']
|
|
24
|
+
},
|
|
25
|
+
// Optional fields - only required when status is "Active"
|
|
26
|
+
startDate: {
|
|
27
|
+
type: Date,
|
|
28
|
+
required: function() {
|
|
29
|
+
return this.status === 'Active';
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
endDate: {
|
|
33
|
+
type: Date,
|
|
34
|
+
required: function() {
|
|
35
|
+
return this.status === 'Active';
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
invoiceDay: {
|
|
39
|
+
type: Number,
|
|
40
|
+
required: function() {
|
|
41
|
+
return this.status === 'Active';
|
|
42
|
+
},
|
|
43
|
+
min: [1, 'Invoice day must be between 1 and 31'],
|
|
44
|
+
max: [31, 'Invoice day must be between 1 and 31']
|
|
45
|
+
},
|
|
46
|
+
dueDate: {
|
|
47
|
+
type: String, // Store as string like "5th", "10th", etc.
|
|
48
|
+
required: function() {
|
|
49
|
+
return this.status === 'Active';
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
balanceBroughtForward: {
|
|
53
|
+
type: Number,
|
|
54
|
+
default: 0
|
|
55
|
+
},
|
|
56
|
+
collectionFrequency: {
|
|
57
|
+
type: String,
|
|
58
|
+
enum: ['Daily', 'Weekly', 'Bi-Weekly', 'Monthly', 'Quarterly', 'Semi-Annually', 'Annually'],
|
|
59
|
+
default: 'Monthly',
|
|
60
|
+
required: function() {
|
|
61
|
+
return this.status === 'Active';
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
// Always required fields regardless of status
|
|
65
|
+
managementFee: {
|
|
66
|
+
type: {
|
|
67
|
+
type: String,
|
|
68
|
+
enum: ['percentage', 'amount'],
|
|
69
|
+
required: [true, 'Management fee type is required']
|
|
70
|
+
},
|
|
71
|
+
value: {
|
|
72
|
+
type: Number,
|
|
73
|
+
required: [true, 'Management fee value is required'],
|
|
74
|
+
min: [0, 'Management fee value cannot be negative']
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
// GL Account configurations - matching LeaseAgreement pattern
|
|
78
|
+
invoiceDoubleEntryAccount: {
|
|
79
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
80
|
+
ref: 'GLAccountDoubleEntries'
|
|
81
|
+
},
|
|
82
|
+
paymentDoubleEntryAccount: {
|
|
83
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
84
|
+
ref: 'GLAccountDoubleEntries'
|
|
85
|
+
},
|
|
86
|
+
// GL Account direct configurations (used when creating double entry records)
|
|
87
|
+
glAccounts: {
|
|
88
|
+
invoice: {
|
|
89
|
+
debit: {
|
|
90
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
91
|
+
ref: 'GLAccount'
|
|
92
|
+
},
|
|
93
|
+
credit: {
|
|
94
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
95
|
+
ref: 'GLAccount'
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
payment: {
|
|
99
|
+
debit: {
|
|
100
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
101
|
+
ref: 'GLAccount'
|
|
102
|
+
},
|
|
103
|
+
credit: {
|
|
104
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
105
|
+
ref: 'GLAccount'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
status: {
|
|
110
|
+
type: String,
|
|
111
|
+
enum: ['Active', 'Inactive', 'Completed', 'Suspended', 'Terminated'],
|
|
112
|
+
default: 'Inactive',
|
|
113
|
+
required: [true, 'Status is required']
|
|
114
|
+
},
|
|
115
|
+
facilityId: {
|
|
116
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
117
|
+
ref: 'Facility',
|
|
118
|
+
required: [true, 'Facility ID is required']
|
|
119
|
+
},
|
|
120
|
+
lastInvoiceDate: {
|
|
121
|
+
type: Date
|
|
122
|
+
},
|
|
123
|
+
nextInvoiceDate: {
|
|
124
|
+
type: Date
|
|
125
|
+
},
|
|
126
|
+
autoSend: {
|
|
127
|
+
type: Boolean,
|
|
128
|
+
default: false
|
|
129
|
+
},
|
|
130
|
+
// Track data source and sync information
|
|
131
|
+
leaseDataSource: {
|
|
132
|
+
type: String,
|
|
133
|
+
enum: ['lease', 'manual'],
|
|
134
|
+
default: 'manual'
|
|
135
|
+
},
|
|
136
|
+
lastSyncedAt: {
|
|
137
|
+
type: Date
|
|
138
|
+
},
|
|
139
|
+
// Track which units have lease agreements
|
|
140
|
+
unitsWithLeases: [{
|
|
141
|
+
unitId: {
|
|
142
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
143
|
+
ref: 'Unit'
|
|
144
|
+
},
|
|
145
|
+
leaseId: {
|
|
146
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
147
|
+
ref: 'LeaseAgreement'
|
|
148
|
+
},
|
|
149
|
+
syncedAt: {
|
|
150
|
+
type: Date,
|
|
151
|
+
default: Date.now
|
|
152
|
+
}
|
|
153
|
+
}],
|
|
154
|
+
createdBy: {
|
|
155
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
156
|
+
ref: 'User'
|
|
157
|
+
},
|
|
158
|
+
updatedBy: {
|
|
159
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
160
|
+
ref: 'User'
|
|
161
|
+
},
|
|
162
|
+
// Track contract edit history
|
|
163
|
+
editHistory: [{
|
|
164
|
+
editedBy: {
|
|
165
|
+
type: mongoose.Schema.Types.Mixed,
|
|
166
|
+
ref: 'User'
|
|
167
|
+
},
|
|
168
|
+
editedAt: {
|
|
169
|
+
type: Date,
|
|
170
|
+
default: Date.now
|
|
171
|
+
},
|
|
172
|
+
reason: {
|
|
173
|
+
type: String,
|
|
174
|
+
required: true
|
|
175
|
+
},
|
|
176
|
+
changes: {
|
|
177
|
+
type: Object
|
|
178
|
+
}
|
|
179
|
+
}]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
timestamps: true,
|
|
183
|
+
toJSON: { virtuals: true },
|
|
184
|
+
toObject: { virtuals: true }
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Virtual populate for Property Manager details
|
|
189
|
+
PropertyManagerContractSchema.virtual('propertyManagerDetails', {
|
|
190
|
+
ref: 'User',
|
|
191
|
+
localField: 'propertyManager',
|
|
192
|
+
foreignField: '_id',
|
|
193
|
+
justOne: true
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Virtual populate for Customer details
|
|
197
|
+
PropertyManagerContractSchema.virtual('customer', {
|
|
198
|
+
ref: 'Customer',
|
|
199
|
+
localField: 'customerId',
|
|
200
|
+
foreignField: '_id',
|
|
201
|
+
justOne: true
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Virtual populate for Units details
|
|
205
|
+
PropertyManagerContractSchema.virtual('unitDetails', {
|
|
206
|
+
ref: 'Unit',
|
|
207
|
+
localField: 'units',
|
|
208
|
+
foreignField: '_id'
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Virtual populate for invoice double entry account
|
|
212
|
+
PropertyManagerContractSchema.virtual('invoiceDoubleEntry', {
|
|
213
|
+
ref: 'GLAccountDoubleEntries',
|
|
214
|
+
localField: 'invoiceDoubleEntryAccount',
|
|
215
|
+
foreignField: '_id',
|
|
216
|
+
justOne: true
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Virtual populate for payment double entry account
|
|
220
|
+
PropertyManagerContractSchema.virtual('paymentDoubleEntry', {
|
|
221
|
+
ref: 'GLAccountDoubleEntries',
|
|
222
|
+
localField: 'paymentDoubleEntryAccount',
|
|
223
|
+
foreignField: '_id',
|
|
224
|
+
justOne: true
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Virtual populate for Facility details
|
|
228
|
+
PropertyManagerContractSchema.virtual('facility', {
|
|
229
|
+
ref: 'Facility',
|
|
230
|
+
localField: 'facilityId',
|
|
231
|
+
foreignField: '_id',
|
|
232
|
+
justOne: true
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Virtual to check if contract is complete (has all lease-dependent fields)
|
|
236
|
+
PropertyManagerContractSchema.virtual('isComplete').get(function() {
|
|
237
|
+
return this.startDate && this.endDate && this.invoiceDay && this.dueDate && this.collectionFrequency;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Virtual to get completion percentage
|
|
241
|
+
PropertyManagerContractSchema.virtual('completionPercentage').get(function() {
|
|
242
|
+
const requiredFields = ['startDate', 'endDate', 'invoiceDay', 'dueDate', 'collectionFrequency'];
|
|
243
|
+
const completedFields = requiredFields.filter(field => this[field] != null).length;
|
|
244
|
+
return Math.round((completedFields / requiredFields.length) * 100);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Pre-save middleware to ensure endDate is after startDate (only when both exist)
|
|
248
|
+
PropertyManagerContractSchema.pre('save', function (next) {
|
|
249
|
+
if (this.startDate && this.endDate && this.startDate >= this.endDate) {
|
|
250
|
+
next(new Error('End date must be after start date'));
|
|
251
|
+
} else {
|
|
252
|
+
next();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Pre-save middleware to validate units array is not empty
|
|
257
|
+
PropertyManagerContractSchema.pre('save', function (next) {
|
|
258
|
+
if (!this.units || this.units.length === 0) {
|
|
259
|
+
next(new Error('At least one unit must be specified'));
|
|
260
|
+
} else {
|
|
261
|
+
next();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Pre-save middleware to validate management fee
|
|
266
|
+
PropertyManagerContractSchema.pre('save', function (next) {
|
|
267
|
+
if (this.managementFee.type === 'percentage' && this.managementFee.value > 100) {
|
|
268
|
+
next(new Error('Management fee percentage cannot exceed 100%'));
|
|
269
|
+
} else {
|
|
270
|
+
next();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Pre-save middleware to auto-update status based on completion
|
|
275
|
+
PropertyManagerContractSchema.pre('save', function (next) {
|
|
276
|
+
// If contract has all required lease fields and is currently Inactive, make it Active
|
|
277
|
+
if (this.status === 'Inactive' && this.isComplete) {
|
|
278
|
+
this.status = 'Active';
|
|
279
|
+
this.leaseDataSource = 'lease';
|
|
280
|
+
this.lastSyncedAt = new Date();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// If contract is missing required fields and is currently Active, make it Inactive
|
|
284
|
+
if (this.status === 'Active' && !this.isComplete) {
|
|
285
|
+
this.status = 'Inactive';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
next();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Pre-save middleware to set next invoice date based on collection frequency (only for active contracts)
|
|
292
|
+
PropertyManagerContractSchema.pre('save', function (next) {
|
|
293
|
+
if (this.isNew && this.status === 'Active' && this.startDate && this.invoiceDay && !this.nextInvoiceDate) {
|
|
294
|
+
const baseDate = new Date(this.startDate);
|
|
295
|
+
baseDate.setDate(this.invoiceDay);
|
|
296
|
+
|
|
297
|
+
// If the invoice day has passed this month, set for next month
|
|
298
|
+
if (baseDate < this.startDate) {
|
|
299
|
+
baseDate.setMonth(baseDate.getMonth() + 1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.nextInvoiceDate = baseDate;
|
|
303
|
+
}
|
|
304
|
+
next();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Method to sync with lease data
|
|
308
|
+
PropertyManagerContractSchema.methods.syncWithLeaseData = async function(leaseData) {
|
|
309
|
+
if (!leaseData) return;
|
|
310
|
+
|
|
311
|
+
this.startDate = leaseData.leaseTerms?.startDate || this.startDate;
|
|
312
|
+
this.endDate = leaseData.leaseTerms?.endDate || this.endDate;
|
|
313
|
+
this.invoiceDay = leaseData.financialTerms?.paymentDueDate || this.invoiceDay;
|
|
314
|
+
this.balanceBroughtForward = leaseData.financialTerms?.balanceBroughtForward || this.balanceBroughtForward;
|
|
315
|
+
this.collectionFrequency = leaseData.billingCycle?.frequency || this.collectionFrequency;
|
|
316
|
+
this.autoSend = leaseData.billingCycle?.autoSend !== undefined ? leaseData.billingCycle.autoSend : this.autoSend;
|
|
317
|
+
|
|
318
|
+
// Copy GL accounts if available
|
|
319
|
+
if (leaseData.glAccounts) {
|
|
320
|
+
this.glAccounts = leaseData.glAccounts;
|
|
321
|
+
}
|
|
322
|
+
if (leaseData.invoiceDoubleEntryAccount) {
|
|
323
|
+
this.invoiceDoubleEntryAccount = leaseData.invoiceDoubleEntryAccount;
|
|
324
|
+
}
|
|
325
|
+
if (leaseData.paymentDoubleEntryAccount) {
|
|
326
|
+
this.paymentDoubleEntryAccount = leaseData.paymentDoubleEntryAccount;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.leaseDataSource = 'lease';
|
|
330
|
+
this.lastSyncedAt = new Date();
|
|
331
|
+
|
|
332
|
+
// Auto-calculate due date from payment due date if not set
|
|
333
|
+
if (this.invoiceDay && !this.dueDate) {
|
|
334
|
+
const dueDateMap = {
|
|
335
|
+
1: "1st", 5: "5th", 10: "10th", 15: "15th"
|
|
336
|
+
};
|
|
337
|
+
this.dueDate = dueDateMap[this.invoiceDay] || `${this.invoiceDay}th`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return this.save();
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Indexes for efficient queries
|
|
344
|
+
PropertyManagerContractSchema.index({ customerId: 1, status: 1 });
|
|
345
|
+
PropertyManagerContractSchema.index({ facilityId: 1 });
|
|
346
|
+
PropertyManagerContractSchema.index({ units: 1 });
|
|
347
|
+
PropertyManagerContractSchema.index({ propertyManager: 1 }); // New index for property manager
|
|
348
|
+
PropertyManagerContractSchema.index({ invoiceDoubleEntryAccount: 1 });
|
|
349
|
+
PropertyManagerContractSchema.index({ paymentDoubleEntryAccount: 1 });
|
|
350
|
+
PropertyManagerContractSchema.index({ startDate: 1, endDate: 1 });
|
|
351
|
+
PropertyManagerContractSchema.index({ nextInvoiceDate: 1, status: 1 });
|
|
352
|
+
PropertyManagerContractSchema.index({ leaseDataSource: 1 });
|
|
353
|
+
PropertyManagerContractSchema.index({ 'unitsWithLeases.unitId': 1 });
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
// Compile the model from the schema
|
|
357
|
+
const PropertyManagerContract = mongoose.model("PropertyManagerContract", PropertyManagerContractSchema);
|
|
358
|
+
|
|
359
359
|
module.exports = PropertyManagerContract;
|