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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payservedb",
3
- "version": "6.0.9",
3
+ "version": "6.1.1",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -6,45 +6,48 @@ const powerMeterPowerChargeSchema = new mongoose.Schema({
6
6
  ref: 'Facility',
7
7
  required: true
8
8
  },
9
- meterSerialNumber: {
9
+ yearMonth: {
10
10
  type: String,
11
11
  required: true
12
12
  },
13
- deviceId: {
13
+ // KPLC Power Charges
14
+ fuelCostCharge: {
14
15
  type: Number,
15
- required: false
16
-
16
+ required: true
17
17
  },
18
- gatewayId: {
19
- type: String,
18
+ forexAdjustment: {
19
+ type: Number,
20
20
  required: true
21
-
22
21
  },
23
- meterReading: {
22
+ inflationAdjustment: {
24
23
  type: Number,
25
- required: true,
26
-
24
+ required: true
27
25
  },
28
- lastUpdated: {
29
- type: Date,
26
+ waterResourceManagementLevy: {
27
+ type: Number,
30
28
  required: true
31
-
32
29
  },
33
- manufacturer: {
34
- type: String,
30
+ energyRegulatoryLevy: {
31
+ type: Number,
35
32
  required: true
36
33
  },
37
- type: {
38
- type: String,
39
- required: true,
40
- enum: ['2 phase', '3 phase'],
41
- default: '2 phase'
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;
@@ -40,6 +40,10 @@ const powerMeterSchema = new mongoose.Schema({
40
40
  enum: ['2 phase', '3 phase'],
41
41
  default: '2 phase'
42
42
 
43
+ },
44
+ status: {
45
+ type: String,
46
+ required: true
43
47
  }
44
48
  }, {
45
49
  timestamps: true
@@ -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 (removed balanceBroughtForward)
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
- required: true,
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, // Denormalized for easier access
49
- propertyManagementFee: Number,
50
- totalPaid: Number,
51
- managerRevenue: Number,
52
- ownerAmount: Number,
53
- invoiceCount: Number,
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: String, // Denormalized for easier access
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
- managementFeePercent: Number
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
- includesHistoricalRevenue: Boolean,
125
+ contractsProcessed: Number,
74
126
  calculationNote: String
75
127
  },
76
128
  status: {
77
129
  type: String,
78
- enum: ['calculated', 'paid', 'partially_paid', 'cancelled'],
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' // Reference to User who created this record
136
+ ref: 'User'
85
137
  },
86
- createdAt: {
87
- type: Date,
88
- default: Date.now,
89
- index: true
90
- },
91
- updatedAt: {
92
- type: Date,
93
- default: Date.now
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;