payservedb 6.0.9 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -6,45 +6,48 @@ const powerMeterPowerChargeSchema = new mongoose.Schema({
|
|
|
6
6
|
ref: 'Facility',
|
|
7
7
|
required: true
|
|
8
8
|
},
|
|
9
|
-
|
|
9
|
+
yearMonth: {
|
|
10
10
|
type: String,
|
|
11
11
|
required: true
|
|
12
12
|
},
|
|
13
|
-
|
|
13
|
+
// KPLC Power Charges
|
|
14
|
+
fuelCostCharge: {
|
|
14
15
|
type: Number,
|
|
15
|
-
required:
|
|
16
|
-
|
|
16
|
+
required: true
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
type:
|
|
18
|
+
forexAdjustment: {
|
|
19
|
+
type: Number,
|
|
20
20
|
required: true
|
|
21
|
-
|
|
22
21
|
},
|
|
23
|
-
|
|
22
|
+
inflationAdjustment: {
|
|
24
23
|
type: Number,
|
|
25
|
-
required: true
|
|
26
|
-
|
|
24
|
+
required: true
|
|
27
25
|
},
|
|
28
|
-
|
|
29
|
-
type:
|
|
26
|
+
waterResourceManagementLevy: {
|
|
27
|
+
type: Number,
|
|
30
28
|
required: true
|
|
31
|
-
|
|
32
29
|
},
|
|
33
|
-
|
|
34
|
-
type:
|
|
30
|
+
energyRegulatoryLevy: {
|
|
31
|
+
type: Number,
|
|
35
32
|
required: true
|
|
36
33
|
},
|
|
37
|
-
|
|
38
|
-
type:
|
|
39
|
-
required: true
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
ruralElectrificationLevy: {
|
|
35
|
+
type: Number,
|
|
36
|
+
required: true
|
|
37
|
+
},
|
|
38
|
+
valueAddedTax: {
|
|
39
|
+
type: Number,
|
|
40
|
+
required: true
|
|
41
|
+
},
|
|
42
|
+
totalCharge: {
|
|
43
|
+
type: Number,
|
|
44
|
+
required: true
|
|
43
45
|
}
|
|
46
|
+
|
|
44
47
|
}, {
|
|
45
48
|
timestamps: true
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
const PowerMeterPowerCharge = mongoose.model('PowerMeterPowerCharge', powerMeterPowerChargeSchema);
|
|
49
52
|
|
|
50
|
-
module.exports = PowerMeterPowerCharge;
|
|
53
|
+
module.exports = PowerMeterPowerCharge;
|
|
@@ -67,6 +67,10 @@ const PropertyManagerContractSchema = new Schema(
|
|
|
67
67
|
type: Boolean,
|
|
68
68
|
default: false
|
|
69
69
|
},
|
|
70
|
+
balanceBroughtForward: {
|
|
71
|
+
type: Number,
|
|
72
|
+
default: 0
|
|
73
|
+
},
|
|
70
74
|
// Management fee
|
|
71
75
|
managementFee: {
|
|
72
76
|
type: {
|
|
@@ -90,7 +94,7 @@ const PropertyManagerContractSchema = new Schema(
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
},
|
|
93
|
-
// GL Account configurations
|
|
97
|
+
// GL Account configurations - updated to match sample data structure
|
|
94
98
|
invoiceDoubleEntryAccount: {
|
|
95
99
|
type: mongoose.Schema.Types.ObjectId,
|
|
96
100
|
ref: 'GLAccountDoubleEntries'
|
|
@@ -130,10 +134,26 @@ const PropertyManagerContractSchema = new Schema(
|
|
|
130
134
|
createdBy: {
|
|
131
135
|
type: mongoose.Schema.Types.ObjectId,
|
|
132
136
|
ref: 'User' // References global User collection
|
|
137
|
+
},
|
|
138
|
+
// Additional fields for contract lifecycle management
|
|
139
|
+
suspensionReason: {
|
|
140
|
+
type: String
|
|
141
|
+
},
|
|
142
|
+
terminationReason: {
|
|
143
|
+
type: String
|
|
144
|
+
},
|
|
145
|
+
terminationDate: {
|
|
146
|
+
type: Date
|
|
147
|
+
},
|
|
148
|
+
completionDate: {
|
|
149
|
+
type: Date
|
|
133
150
|
}
|
|
134
151
|
},
|
|
135
152
|
{
|
|
136
|
-
timestamps: true
|
|
153
|
+
timestamps: true,
|
|
154
|
+
// Ensure virtual fields are included in JSON output
|
|
155
|
+
toJSON: { virtuals: true },
|
|
156
|
+
toObject: { virtuals: true }
|
|
137
157
|
}
|
|
138
158
|
);
|
|
139
159
|
|
|
@@ -202,10 +222,11 @@ PropertyManagerContractSchema.pre('save', function (next) {
|
|
|
202
222
|
PropertyManagerContractSchema.methods.syncWithLeaseData = async function(leaseData) {
|
|
203
223
|
if (!leaseData) return;
|
|
204
224
|
|
|
205
|
-
// Update contract fields from lease data
|
|
225
|
+
// Update contract fields from lease data
|
|
206
226
|
this.startDate = leaseData.leaseTerms?.startDate || this.startDate;
|
|
207
227
|
this.endDate = leaseData.leaseTerms?.endDate || this.endDate;
|
|
208
228
|
this.paymentDueDate = leaseData.financialTerms?.paymentDueDate || this.paymentDueDate;
|
|
229
|
+
this.balanceBroughtForward = leaseData.financialTerms?.balanceBroughtForward || this.balanceBroughtForward;
|
|
209
230
|
this.frequency = leaseData.billingCycle?.frequency || this.frequency;
|
|
210
231
|
this.autoSend = leaseData.billingCycle?.autoSend !== undefined ? leaseData.billingCycle.autoSend : this.autoSend;
|
|
211
232
|
this.nextInvoiceDate = leaseData.billingCycle?.nextInvoiceDate || this.nextInvoiceDate;
|
|
@@ -241,6 +262,7 @@ PropertyManagerContractSchema.virtual('durationInDays').get(function() {
|
|
|
241
262
|
const diffTime = Math.abs(this.endDate - this.startDate);
|
|
242
263
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
243
264
|
});
|
|
265
|
+
|
|
244
266
|
// Virtual for contract remaining days
|
|
245
267
|
PropertyManagerContractSchema.virtual('remainingDays').get(function() {
|
|
246
268
|
if (!this.endDate) return null;
|
|
@@ -250,6 +272,17 @@ PropertyManagerContractSchema.virtual('remainingDays').get(function() {
|
|
|
250
272
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
251
273
|
});
|
|
252
274
|
|
|
275
|
+
// Virtual for management fee display
|
|
276
|
+
PropertyManagerContractSchema.virtual('managementFeeDisplay').get(function() {
|
|
277
|
+
if (!this.managementFee) return null;
|
|
278
|
+
|
|
279
|
+
if (this.managementFee.type === 'percentage') {
|
|
280
|
+
return `${this.managementFee.value}%`;
|
|
281
|
+
} else {
|
|
282
|
+
return `$${this.managementFee.value.toLocaleString()}`;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
253
286
|
// Indexes for better query performance
|
|
254
287
|
PropertyManagerContractSchema.index({ customerId: 1, status: 1 });
|
|
255
288
|
PropertyManagerContractSchema.index({ facilityId: 1, status: 1 });
|
|
@@ -330,6 +363,32 @@ PropertyManagerContractSchema.statics.findContractsRequiringInvoicing = function
|
|
|
330
363
|
.populate('customerId', 'firstName lastName email');
|
|
331
364
|
};
|
|
332
365
|
|
|
366
|
+
PropertyManagerContractSchema.statics.findContractsByFeeType = function(facilityId, feeType) {
|
|
367
|
+
return this.find({
|
|
368
|
+
facilityId: facilityId,
|
|
369
|
+
'managementFee.type': feeType
|
|
370
|
+
}).populate('propertyManager', 'fullName email')
|
|
371
|
+
.populate('units', 'name unitType')
|
|
372
|
+
.populate('customerId', 'firstName lastName email')
|
|
373
|
+
.sort({ 'managementFee.value': -1 });
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
PropertyManagerContractSchema.statics.getContractStatistics = function(facilityId) {
|
|
377
|
+
return this.aggregate([
|
|
378
|
+
{
|
|
379
|
+
$match: { facilityId: mongoose.Types.ObjectId(facilityId) }
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
$group: {
|
|
383
|
+
_id: '$status',
|
|
384
|
+
count: { $sum: 1 },
|
|
385
|
+
totalFeeValue: { $sum: '$managementFee.value' },
|
|
386
|
+
avgFeeValue: { $avg: '$managementFee.value' }
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
]);
|
|
390
|
+
};
|
|
391
|
+
|
|
333
392
|
// Instance methods for contract management
|
|
334
393
|
PropertyManagerContractSchema.methods.calculateNextInvoiceDate = function() {
|
|
335
394
|
if (!this.lastInvoiceDate && !this.startDate) return null;
|
|
@@ -401,6 +460,32 @@ PropertyManagerContractSchema.methods.completeContract = async function() {
|
|
|
401
460
|
return this.save();
|
|
402
461
|
};
|
|
403
462
|
|
|
463
|
+
// Method to calculate management fee for a given amount
|
|
464
|
+
PropertyManagerContractSchema.methods.calculateManagementFee = function(baseAmount) {
|
|
465
|
+
if (!this.managementFee) return 0;
|
|
466
|
+
|
|
467
|
+
if (this.managementFee.type === 'percentage') {
|
|
468
|
+
return (baseAmount * this.managementFee.value) / 100;
|
|
469
|
+
} else {
|
|
470
|
+
return this.managementFee.value;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Method to get contract summary
|
|
475
|
+
PropertyManagerContractSchema.methods.getContractSummary = function() {
|
|
476
|
+
return {
|
|
477
|
+
id: this._id,
|
|
478
|
+
contractName: this.contractName,
|
|
479
|
+
status: this.status,
|
|
480
|
+
duration: this.durationInDays,
|
|
481
|
+
remainingDays: this.remainingDays,
|
|
482
|
+
managementFee: this.managementFeeDisplay,
|
|
483
|
+
frequency: this.frequency,
|
|
484
|
+
nextInvoiceDate: this.nextInvoiceDate,
|
|
485
|
+
isComplete: this.isComplete
|
|
486
|
+
};
|
|
487
|
+
};
|
|
488
|
+
|
|
404
489
|
// Pre-findOneAndUpdate middleware to maintain data integrity
|
|
405
490
|
PropertyManagerContractSchema.pre(['findOneAndUpdate', 'updateOne', 'updateMany'], function() {
|
|
406
491
|
const update = this.getUpdate();
|
|
@@ -431,6 +516,11 @@ PropertyManagerContractSchema.post('save', function(doc) {
|
|
|
431
516
|
console.log(`Contract status changed to: ${doc.status}`);
|
|
432
517
|
// Add notification logic here
|
|
433
518
|
}
|
|
519
|
+
|
|
520
|
+
// Log invoice date updates
|
|
521
|
+
if (this.isModified('nextInvoiceDate') || this.isModified('lastInvoiceDate')) {
|
|
522
|
+
console.log(`Invoice dates updated for contract: ${doc.contractName}`);
|
|
523
|
+
}
|
|
434
524
|
});
|
|
435
525
|
|
|
436
526
|
// Error handling for validation errors
|
|
@@ -453,6 +543,8 @@ PropertyManagerContractSchema.methods.toJSON = function() {
|
|
|
453
543
|
contract.missingRequiredFields = this.getMissingRequiredFields();
|
|
454
544
|
contract.durationInDays = this.durationInDays;
|
|
455
545
|
contract.remainingDays = this.remainingDays;
|
|
546
|
+
contract.managementFeeDisplay = this.managementFeeDisplay;
|
|
547
|
+
contract.contractSummary = this.getContractSummary();
|
|
456
548
|
|
|
457
549
|
return contract;
|
|
458
550
|
};
|
|
@@ -461,4 +553,4 @@ PropertyManagerContractSchema.methods.toJSON = function() {
|
|
|
461
553
|
module.exports = {
|
|
462
554
|
schema: PropertyManagerContractSchema,
|
|
463
555
|
name: 'PropertyManagerContract'
|
|
464
|
-
};
|
|
556
|
+
};
|
|
@@ -10,12 +10,14 @@ const propertyManagerRevenue = new mongoose.Schema({
|
|
|
10
10
|
calculationDate: {
|
|
11
11
|
type: Date,
|
|
12
12
|
required: true,
|
|
13
|
-
index: true
|
|
13
|
+
index: true,
|
|
14
|
+
default: Date.now
|
|
14
15
|
},
|
|
15
16
|
dateRange: {
|
|
16
17
|
startDate: Date,
|
|
17
18
|
endDate: Date
|
|
18
19
|
},
|
|
20
|
+
// Direct revenue amounts - no calculations needed
|
|
19
21
|
totalRevenue: {
|
|
20
22
|
type: Number,
|
|
21
23
|
required: true,
|
|
@@ -23,14 +25,14 @@ const propertyManagerRevenue = new mongoose.Schema({
|
|
|
23
25
|
},
|
|
24
26
|
totalOwnerAmount: {
|
|
25
27
|
type: Number,
|
|
26
|
-
|
|
27
|
-
default: 0
|
|
28
|
+
default: 0 // Usually 0 for direct PM invoices
|
|
28
29
|
},
|
|
29
30
|
totalPaidAmount: {
|
|
30
31
|
type: Number,
|
|
31
32
|
required: true,
|
|
32
33
|
default: 0
|
|
33
34
|
},
|
|
35
|
+
// Processing statistics
|
|
34
36
|
unitsProcessed: {
|
|
35
37
|
type: Number,
|
|
36
38
|
default: 0
|
|
@@ -39,63 +41,156 @@ const propertyManagerRevenue = new mongoose.Schema({
|
|
|
39
41
|
type: Number,
|
|
40
42
|
default: 0
|
|
41
43
|
},
|
|
44
|
+
contractsProcessed: {
|
|
45
|
+
type: Number,
|
|
46
|
+
default: 0
|
|
47
|
+
},
|
|
48
|
+
// CRITICAL: Track processed invoice numbers to prevent duplicates
|
|
49
|
+
processedInvoiceNumbers: [{
|
|
50
|
+
type: String,
|
|
51
|
+
required: false
|
|
52
|
+
}],
|
|
53
|
+
// Unit breakdown - simplified
|
|
42
54
|
unitBreakdown: [{
|
|
43
55
|
unit: {
|
|
44
56
|
type: mongoose.Schema.Types.ObjectId,
|
|
45
57
|
ref: 'Unit',
|
|
46
58
|
required: true
|
|
47
59
|
},
|
|
48
|
-
unitName: String,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
unitName: String,
|
|
61
|
+
contractId: {
|
|
62
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
63
|
+
ref: 'PropertyManagerContract'
|
|
64
|
+
},
|
|
65
|
+
contractName: String,
|
|
66
|
+
propertyManager: {
|
|
67
|
+
id: {
|
|
68
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
69
|
+
ref: 'User'
|
|
70
|
+
},
|
|
71
|
+
fullName: String,
|
|
72
|
+
email: String,
|
|
73
|
+
phoneNumber: String
|
|
74
|
+
},
|
|
75
|
+
// Revenue amounts for this unit
|
|
76
|
+
totalPaid: {
|
|
77
|
+
type: Number,
|
|
78
|
+
default: 0
|
|
79
|
+
},
|
|
80
|
+
managerRevenue: {
|
|
81
|
+
type: Number,
|
|
82
|
+
default: 0
|
|
83
|
+
},
|
|
84
|
+
ownerAmount: {
|
|
85
|
+
type: Number,
|
|
86
|
+
default: 0
|
|
87
|
+
},
|
|
88
|
+
invoiceCount: {
|
|
89
|
+
type: Number,
|
|
90
|
+
default: 0
|
|
91
|
+
},
|
|
92
|
+
// Invoice details
|
|
54
93
|
invoices: [{
|
|
55
94
|
invoice: {
|
|
56
95
|
type: mongoose.Schema.Types.ObjectId,
|
|
57
96
|
ref: 'Invoice',
|
|
58
97
|
required: true
|
|
59
98
|
},
|
|
60
|
-
invoiceNumber:
|
|
99
|
+
invoiceNumber: {
|
|
100
|
+
type: String,
|
|
101
|
+
required: true,
|
|
102
|
+
index: true
|
|
103
|
+
},
|
|
61
104
|
invoiceType: String,
|
|
62
105
|
totalAmount: Number,
|
|
63
106
|
paidAmount: Number,
|
|
64
107
|
managerCommission: Number,
|
|
65
108
|
ownerAmount: Number,
|
|
66
109
|
paymentDate: Date,
|
|
67
|
-
|
|
110
|
+
contractId: {
|
|
111
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
112
|
+
ref: 'PropertyManagerContract'
|
|
113
|
+
},
|
|
114
|
+
contractName: String,
|
|
115
|
+
revenueSource: {
|
|
116
|
+
type: String,
|
|
117
|
+
enum: ['DirectPropertyManagerInvoice', 'CalculatedFromTenantInvoice', 'PropertyManagementInvoice'],
|
|
118
|
+
default: 'PropertyManagementInvoice'
|
|
119
|
+
}
|
|
68
120
|
}]
|
|
69
121
|
}],
|
|
70
122
|
summary: {
|
|
71
123
|
totalUnits: Number,
|
|
72
124
|
totalInvoicesProcessed: Number,
|
|
73
|
-
|
|
125
|
+
contractsProcessed: Number,
|
|
74
126
|
calculationNote: String
|
|
75
127
|
},
|
|
76
128
|
status: {
|
|
77
129
|
type: String,
|
|
78
|
-
enum: ['calculated', 'paid', '
|
|
130
|
+
enum: ['calculated', 'paid', 'cancelled'],
|
|
79
131
|
default: 'calculated'
|
|
80
132
|
},
|
|
81
133
|
notes: String,
|
|
82
134
|
createdBy: {
|
|
83
135
|
type: mongoose.Schema.Types.ObjectId,
|
|
84
|
-
ref: 'User'
|
|
136
|
+
ref: 'User'
|
|
85
137
|
},
|
|
86
|
-
|
|
87
|
-
type:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
138
|
+
calculationType: {
|
|
139
|
+
type: String,
|
|
140
|
+
enum: [
|
|
141
|
+
'direct_revenue',
|
|
142
|
+
'bulk_direct_revenue',
|
|
143
|
+
'retroactive_direct',
|
|
144
|
+
'revenue_extraction',
|
|
145
|
+
'bulk_revenue_extraction'
|
|
146
|
+
],
|
|
147
|
+
default: 'direct_revenue'
|
|
94
148
|
}
|
|
95
149
|
}, {
|
|
96
150
|
timestamps: true
|
|
97
151
|
});
|
|
98
152
|
|
|
153
|
+
// Enhanced indexes for better performance and duplicate prevention
|
|
154
|
+
propertyManagerRevenue.index({ facility: 1, calculationDate: -1 });
|
|
155
|
+
propertyManagerRevenue.index({ facility: 1, 'unitBreakdown.unit': 1 });
|
|
156
|
+
propertyManagerRevenue.index({ facility: 1, calculationType: 1 });
|
|
157
|
+
propertyManagerRevenue.index({ createdAt: -1 });
|
|
158
|
+
|
|
159
|
+
// CRITICAL: Compound index for fast duplicate checking by invoice number
|
|
160
|
+
propertyManagerRevenue.index({
|
|
161
|
+
facility: 1,
|
|
162
|
+
'unitBreakdown.invoices.invoiceNumber': 1
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// CRITICAL: Index for processed invoice numbers array
|
|
166
|
+
propertyManagerRevenue.index({
|
|
167
|
+
facility: 1,
|
|
168
|
+
processedInvoiceNumbers: 1
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Simple pre-save middleware to auto-populate processedInvoiceNumbers
|
|
172
|
+
propertyManagerRevenue.pre('save', function(next) {
|
|
173
|
+
try {
|
|
174
|
+
// Auto-populate processedInvoiceNumbers from unitBreakdown if not set
|
|
175
|
+
if (!this.processedInvoiceNumbers || this.processedInvoiceNumbers.length === 0) {
|
|
176
|
+
const invoiceNumbers = [];
|
|
177
|
+
this.unitBreakdown.forEach(unit => {
|
|
178
|
+
if (unit.invoices && Array.isArray(unit.invoices)) {
|
|
179
|
+
unit.invoices.forEach(invoice => {
|
|
180
|
+
if (invoice.invoiceNumber) {
|
|
181
|
+
invoiceNumbers.push(invoice.invoiceNumber);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
this.processedInvoiceNumbers = [...new Set(invoiceNumbers)];
|
|
187
|
+
}
|
|
188
|
+
next();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
next(error);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
99
194
|
const PropertyManagerRevenue = mongoose.model('PropertyManagerRevenue', propertyManagerRevenue);
|
|
100
195
|
|
|
101
196
|
module.exports = PropertyManagerRevenue;
|