payservedb 9.0.6 → 9.0.8

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/index.js CHANGED
@@ -259,6 +259,7 @@ const models = {
259
259
  MoveinLandlord: require("./src/models/movein_landlord"),
260
260
  MoveinUser: require("./src/models/movein_user"),
261
261
  InvoiceWithholdingTax: require("./src/models/InvoiceWithholdingTax"),
262
+ InvoiceCreditAdjustment: require("./src/models/invoiceCreditAdjustment"),
262
263
  // Customer Obsession admin-managed config
263
264
  EmailCcConfig: require("./src/models/email_cc_config"),
264
265
  AutoReplyRule: require("./src/models/auto_reply_rule"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payservedb",
3
- "version": "9.0.6",
3
+ "version": "9.0.8",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,46 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const invoiceCreditAdjustmentSchema = new mongoose.Schema(
4
+ {
5
+ invoice: {
6
+ id: { type: mongoose.Schema.Types.ObjectId, ref: "Invoice", required: true },
7
+ invoiceNumber: { type: String, required: true },
8
+ },
9
+ facility: {
10
+ id: { type: mongoose.Schema.Types.ObjectId, ref: "Facility", required: true },
11
+ name: { type: String, required: true },
12
+ },
13
+ client: {
14
+ clientId: { type: mongoose.Schema.Types.ObjectId, ref: "Customer", required: true },
15
+ firstName: { type: String, required: true },
16
+ lastName: { type: String, required: true },
17
+ },
18
+ adjustmentType: {
19
+ type: String,
20
+ enum: ["credit", "debit", "opening-balance"],
21
+ required: true,
22
+ },
23
+ previousBalance: { type: Number, required: true }, // balanceBroughtForward before change
24
+ adjustmentAmount: { type: Number, required: true }, // the actual change (can be negative)
25
+ newBalance: { type: Number, required: true }, // balanceBroughtForward after change
26
+ reason: { type: String, required: true },
27
+ madeBy: {
28
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
29
+ name: { type: String, required: true },
30
+ role: { type: String, required: true },
31
+ },
32
+ },
33
+ { timestamps: true }
34
+ );
35
+
36
+ invoiceCreditAdjustmentSchema.index({ "invoice.id": 1 });
37
+ invoiceCreditAdjustmentSchema.index({ "client.clientId": 1 });
38
+ invoiceCreditAdjustmentSchema.index({ "facility.id": 1 });
39
+ invoiceCreditAdjustmentSchema.index({ adjustmentType: 1 });
40
+
41
+ const InvoiceCreditAdjustment = mongoose.model(
42
+ "InvoiceCreditAdjustment",
43
+ invoiceCreditAdjustmentSchema
44
+ );
45
+
46
+ module.exports = InvoiceCreditAdjustment;
@@ -5,7 +5,7 @@ const powerMeterSettingsSchema = new mongoose.Schema({
5
5
  type: mongoose.Schema.Types.ObjectId,
6
6
  ref: 'Facility',
7
7
  required: true,
8
- unique: true // Ensure one settings record per facility
8
+ unique: true
9
9
  },
10
10
 
11
11
  // Billing Amounts
@@ -79,33 +79,72 @@ const powerMeterSettingsSchema = new mongoose.Schema({
79
79
  min: 0
80
80
  },
81
81
 
82
- // GL Account Configuration (following your sample pattern)
82
+ // Biller Address stores the selected BillerAddress document ID
83
+ billerAddressId: {
84
+ type: mongoose.Schema.Types.ObjectId,
85
+ ref: 'BillerAddress',
86
+ required: false,
87
+ default: null
88
+ },
89
+
90
+ // Payment Methods — mirrors the water meter settings pattern
91
+ paymentMethods: {
92
+ mobilePayment: {
93
+ status: {
94
+ type: Boolean,
95
+ default: false
96
+ },
97
+ paymentId: {
98
+ type: mongoose.Schema.Types.ObjectId,
99
+ ref: 'FacilityPaymentDetails',
100
+ default: null
101
+ }
102
+ },
103
+ bankPayment: {
104
+ status: {
105
+ type: Boolean,
106
+ default: false
107
+ },
108
+ paymentId: {
109
+ type: mongoose.Schema.Types.ObjectId,
110
+ ref: 'BankDetails',
111
+ default: null
112
+ }
113
+ }
114
+ },
115
+
116
+ // GL Account Configuration
83
117
  glAccounts: {
84
118
  invoice: {
85
119
  debit: {
86
120
  type: mongoose.Schema.Types.ObjectId,
87
- ref: "GLAccount",
121
+ ref: 'GLAccount',
88
122
  required: false,
123
+ default: null
89
124
  },
90
125
  credit: {
91
126
  type: mongoose.Schema.Types.ObjectId,
92
- ref: "GLAccount",
127
+ ref: 'GLAccount',
93
128
  required: false,
129
+ default: null
94
130
  }
95
131
  },
96
132
  payment: {
97
133
  debit: {
98
134
  type: mongoose.Schema.Types.ObjectId,
99
- ref: "GLAccount",
135
+ ref: 'GLAccount',
100
136
  required: false,
137
+ default: null
101
138
  },
102
139
  credit: {
103
140
  type: mongoose.Schema.Types.ObjectId,
104
- ref: "GLAccount",
141
+ ref: 'GLAccount',
105
142
  required: false,
143
+ default: null
106
144
  }
107
145
  }
108
146
  },
147
+
109
148
  // Discounts
110
149
  discounts: [{
111
150
  name: {
@@ -128,12 +167,14 @@ const powerMeterSettingsSchema = new mongoose.Schema({
128
167
  required: true
129
168
  }
130
169
  }],
131
- // Other Charges
170
+
171
+ // Other Charges (KPLC)
132
172
  otherCharges: {
133
173
  type: String,
134
174
  enum: ['yes', 'no', ''],
135
175
  default: ''
136
176
  },
177
+
137
178
  // Notifications
138
179
  notifications: {
139
180
  type: Boolean,
@@ -59,6 +59,14 @@ const powerMeterSchema = new mongoose.Schema({
59
59
  type: String,
60
60
  required: true
61
61
  },
62
+
63
+ meterCategory: {
64
+ type: String,
65
+ required: true,
66
+ enum: ['unit', 'common_area', 'bulk'],
67
+ default: 'unit'
68
+ },
69
+
62
70
  unitId: {
63
71
  type: mongoose.Schema.Types.ObjectId,
64
72
  ref: 'Unit',
@@ -68,6 +76,13 @@ const powerMeterSchema = new mongoose.Schema({
68
76
  type: String,
69
77
  trim: true
70
78
  },
79
+
80
+ // ─── Common Area / Bulk Meter Fields ──────────────────────────────────────
81
+ description: {
82
+ type: String,
83
+ trim: true
84
+ },
85
+
71
86
  type: {
72
87
  type: String,
73
88
  required: true,
@@ -111,6 +126,25 @@ const powerMeterSchema = new mongoose.Schema({
111
126
  timestamps: true
112
127
  });
113
128
 
129
+ powerMeterSchema.virtual('displayName').get(function () {
130
+ return this.meterCategory === 'unit'
131
+ ? this.unitName
132
+ : this.description;
133
+ });
134
+ powerMeterSchema.pre('save', function (next) {
135
+ if (this.meterCategory === 'unit') {
136
+ if (!this.unitId || !this.unitName) {
137
+ return next(new Error('Unit meters require both unitId and unitName.'));
138
+ }
139
+ } else {
140
+ // common_area or bulk
141
+ if (!this.description) {
142
+ return next(new Error(`${this.meterCategory} meters require a description.`));
143
+ }
144
+ }
145
+ next();
146
+ });
147
+
114
148
  const PowerMeters = mongoose.model('PowerMeters', powerMeterSchema);
115
149
 
116
- module.exports = PowerMeters;
150
+ module.exports = PowerMeters;
@@ -16,6 +16,12 @@ const powerInvoiceSchema = new mongoose.Schema(
16
16
  type: String,
17
17
  required: true,
18
18
  },
19
+ // Unit reference — stored directly from PowerMeterAccount.unitId / PowerMeters.unitId
20
+ unitId: {
21
+ type: mongoose.Schema.Types.ObjectId,
22
+ ref: 'Unit',
23
+ default: null,
24
+ },
19
25
  yearMonth: {
20
26
  type: String,
21
27
  required: true,
@@ -40,42 +46,14 @@ const powerInvoiceSchema = new mongoose.Schema(
40
46
  // Positive = arrears carried forward; Negative = credit from overpayment
41
47
  },
42
48
 
43
- // ── Payment Methods ────────────────────────────────────────────────────────
44
- paymentMethods: {
45
- mobilePayment: {
46
- status: {
47
- type: Boolean,
48
- default: false,
49
- },
50
- paymentId: {
51
- type: mongoose.Schema.Types.ObjectId,
52
- ref: 'FacilityPaymentDetails',
53
- },
54
- },
55
- bankPayment: {
56
- status: {
57
- type: Boolean,
58
- default: false,
59
- },
60
- paymentId: {
61
- type: mongoose.Schema.Types.ObjectId,
62
- ref: 'BankDetails',
63
- },
64
- },
65
- cashPayment: {
66
- status: {
67
- type: Boolean,
68
- default: false,
69
- },
70
- },
71
- },
72
- paymentMethod: {
73
- type: String,
74
- enum: ['mobile', 'bank', 'cash', 'mixed'],
49
+ // ── Biller Address ─────────────────────────────────────────────────────────
50
+ // Reference to the BillerAddress document configured in PowerMeterSettings.
51
+ // The sub-document below is a denormalised snapshot captured at invoice time.
52
+ billerAddressId: {
53
+ type: mongoose.Schema.Types.ObjectId,
54
+ ref: 'BillerAddress',
75
55
  default: null,
76
56
  },
77
-
78
- // ── Biller Address ─────────────────────────────────────────────────────────
79
57
  billerAddress: {
80
58
  name: {
81
59
  type: String,
@@ -110,6 +88,49 @@ const powerInvoiceSchema = new mongoose.Schema(
110
88
  },
111
89
  },
112
90
 
91
+ // ── Payment Methods ────────────────────────────────────────────────────────
92
+ // paymentMethods stores the IDs configured in PowerMeterSettings so the
93
+ // invoice knows which accounts/paybills are available for payment.
94
+ // paymentMethod (singular) records how this invoice was actually settled.
95
+ paymentMethods: {
96
+ mobilePayment: {
97
+ status: {
98
+ type: Boolean,
99
+ default: false,
100
+ },
101
+ // References the FacilityPaymentDetails (M-Pesa paybill) configured in settings
102
+ paymentId: {
103
+ type: mongoose.Schema.Types.ObjectId,
104
+ ref: 'FacilityPaymentDetails',
105
+ default: null,
106
+ },
107
+ },
108
+ bankPayment: {
109
+ status: {
110
+ type: Boolean,
111
+ default: false,
112
+ },
113
+ // References the BankDetails configured in settings
114
+ paymentId: {
115
+ type: mongoose.Schema.Types.ObjectId,
116
+ ref: 'BankDetails',
117
+ default: null,
118
+ },
119
+ },
120
+ cashPayment: {
121
+ status: {
122
+ type: Boolean,
123
+ default: false,
124
+ },
125
+ },
126
+ },
127
+ // How this invoice was actually paid (set during reconciliation)
128
+ paymentMethod: {
129
+ type: String,
130
+ enum: ['mobile', 'bank', 'cash', 'mixed'],
131
+ default: null,
132
+ },
133
+
113
134
  // ── Dates ──────────────────────────────────────────────────────────────────
114
135
  dateIssued: {
115
136
  type: Date,
@@ -167,89 +188,21 @@ const powerInvoiceSchema = new mongoose.Schema(
167
188
  },
168
189
 
169
190
  // ── Power Charges ──────────────────────────────────────────────────────────
170
- // All monetary values are in the invoice currency (default KES).
171
191
  powerCharges: {
172
- tariff: {
173
- type: String,
174
- // Mirror of the top-level tariff field; stored here for invoice snapshot
175
- },
176
- yearMonth: {
177
- type: String,
178
- // Mirror of the top-level yearMonth; stored for historical rate lookups
179
- },
180
-
181
- // Core energy charge based on units consumed and applicable tariff block
182
- energyCharge: {
183
- type: Number,
184
- required: true,
185
- default: 0,
186
- },
187
-
188
- // Fuel Cost Charge (FCC) – passed through from EPRA-approved fuel levy
189
- fuelCostCharge: {
190
- type: Number,
191
- default: 0,
192
- },
193
-
194
- // Foreign Exchange Fluctuation Adjustment
195
- forexAdjustment: {
196
- type: Number,
197
- default: 0,
198
- },
199
-
200
- // Inflation Adjustment Charge
201
- inflationAdjustment: {
202
- type: Number,
203
- default: 0,
204
- },
205
-
206
- // Water Resource Management Levy (WRML)
207
- waterResourceManagementLevy: {
208
- type: Number,
209
- default: 0,
210
- },
211
-
212
- // Energy Regulatory Levy (ERL)
213
- energyRegulatoryLevy: {
214
- type: Number,
215
- default: 0,
216
- },
217
-
218
- // Rural Electrification & Renewable Energy Levy (REL)
219
- ruralElectrificationLevy: {
220
- type: Number,
221
- default: 0,
222
- },
223
-
224
- // Power Factor Surcharge (applied to commercial/industrial customers only)
225
- powerFactorSurcharge: {
226
- type: Number,
227
- default: 0,
228
- },
229
-
230
- // Value Added Tax amount
231
- valueAddedTax: {
232
- type: Number,
233
- default: 0,
234
- },
235
-
236
- // VAT rate applied (%) – captured at time of billing; typically 16
237
- vatPercentage: {
238
- type: Number,
239
- default: 16,
240
- },
241
-
242
- // Rural Electrification Programme levy percentage (%) – typically 5
243
- repPercentage: {
244
- type: Number,
245
- default: 5,
246
- },
247
-
248
- // Grand total = sum of all charges above
249
- totalCharge: {
250
- type: Number,
251
- required: true,
252
- },
192
+ tariff: { type: String },
193
+ yearMonth: { type: String },
194
+ energyCharge: { type: Number, required: true, default: 0 },
195
+ fuelCostCharge: { type: Number, default: 0 },
196
+ forexAdjustment: { type: Number, default: 0 },
197
+ inflationAdjustment: { type: Number, default: 0 },
198
+ waterResourceManagementLevy: { type: Number, default: 0 },
199
+ energyRegulatoryLevy: { type: Number, default: 0 },
200
+ ruralElectrificationLevy: { type: Number, default: 0 },
201
+ powerFactorSurcharge: { type: Number, default: 0 },
202
+ valueAddedTax: { type: Number, default: 0 },
203
+ vatPercentage: { type: Number, default: 16 },
204
+ repPercentage: { type: Number, default: 5 },
205
+ totalCharge: { type: Number, required: true },
253
206
  },
254
207
 
255
208
  // ── Payment & Reconciliation ───────────────────────────────────────────────
@@ -270,60 +223,28 @@ const powerInvoiceSchema = new mongoose.Schema(
270
223
  default: 'Unpaid',
271
224
  },
272
225
 
273
- // ── View / Read Status
226
+ // ── View / Read Status ─────────────────────────────────────────────────────
274
227
  viewStatus: {
275
- isOpened: {
276
- type: Boolean,
277
- default: false,
278
- },
279
- openedAt: {
280
- type: Date,
281
- default: null,
282
- },
228
+ isOpened: { type: Boolean, default: false },
229
+ openedAt: { type: Date, default: null },
283
230
  },
284
231
 
285
- // ── Currency
232
+ // ── Currency ───────────────────────────────────────────────────────────────
286
233
  currency: {
287
- id: {
288
- type: mongoose.Schema.Types.ObjectId,
289
- },
290
- name: {
291
- type: String,
292
- default: 'Kenyan Shilling',
293
- },
294
- code: {
295
- type: String,
296
- default: 'KES',
297
- },
298
- symbol: {
299
- type: String,
300
- default: 'KSh',
301
- },
234
+ id: { type: mongoose.Schema.Types.ObjectId },
235
+ name: { type: String, default: 'Kenyan Shilling' },
236
+ code: { type: String, default: 'KES' },
237
+ symbol: { type: String, default: 'KSh' },
302
238
  },
303
239
 
304
- // ── Notification Tracking
240
+ // ── Notification Tracking ──────────────────────────────────────────────────
305
241
  notificationsSent: {
306
242
  type: {
307
- sms: {
308
- type: Boolean,
309
- default: false,
310
- },
311
- email: {
312
- type: Boolean,
313
- default: false,
314
- },
315
- sentAt: {
316
- type: Date,
317
- default: null,
318
- },
319
- lastAttempt: {
320
- type: Date,
321
- default: null,
322
- },
323
- attempts: {
324
- type: Number,
325
- default: 0,
326
- },
243
+ sms: { type: Boolean, default: false },
244
+ email: { type: Boolean, default: false },
245
+ sentAt: { type: Date, default: null },
246
+ lastAttempt: { type: Date, default: null },
247
+ attempts: { type: Number, default: 0 },
327
248
  smsDetails: {
328
249
  type: {
329
250
  success: Boolean,
@@ -354,41 +275,18 @@ const powerInvoiceSchema = new mongoose.Schema(
354
275
  },
355
276
  },
356
277
 
357
- // Reconciliation History
278
+ // ── Reconciliation History ─────────────────────────────────────────────────
358
279
  reconciliationHistory: [
359
280
  {
360
- date: {
361
- type: Date,
362
- default: Date.now,
363
- },
364
- amount: {
365
- type: Number,
366
- required: true,
367
- },
368
- type: {
369
- type: String,
370
- default: 'Manual',
371
- // e.g. 'Manual', 'M-Pesa', 'Bank Transfer', 'Cash'
372
- },
373
- paymentReference: {
374
- type: String,
375
- },
376
- paymentCompletion: {
377
- type: String,
378
- default: 'Completed',
379
- },
380
- sourceInvoice: {
381
- type: String,
382
- },
383
- destinationInvoice: {
384
- type: String,
385
- },
386
- notes: {
387
- type: String,
388
- },
389
- remainingBalance: {
390
- type: Number,
391
- },
281
+ date: { type: Date, default: Date.now },
282
+ amount: { type: Number, required: true },
283
+ type: { type: String, default: 'Manual' },
284
+ paymentReference: { type: String },
285
+ paymentCompletion: { type: String, default: 'Completed' },
286
+ sourceInvoice: { type: String },
287
+ destinationInvoice: { type: String },
288
+ notes: { type: String },
289
+ remainingBalance: { type: Number },
392
290
  },
393
291
  ],
394
292
  },
@@ -397,7 +295,7 @@ const powerInvoiceSchema = new mongoose.Schema(
397
295
  }
398
296
  );
399
297
 
400
- // Pre-save middleware
298
+ // ── Pre-save middleware ────────────────────────────────────────────────────────
401
299
 
402
300
  powerInvoiceSchema.pre('save', function (next) {
403
301
  // 1. Auto-mark overdue
@@ -409,15 +307,11 @@ powerInvoiceSchema.pre('save', function (next) {
409
307
  }
410
308
 
411
309
  // 2. Sync powerCharges snapshot fields with top-level fields
412
- if (this.isNew || this.isModified('tariff')) {
413
- if (this.powerCharges && !this.powerCharges.tariff) {
414
- this.powerCharges.tariff = this.tariff;
415
- }
310
+ if ((this.isNew || this.isModified('tariff')) && this.powerCharges && !this.powerCharges.tariff) {
311
+ this.powerCharges.tariff = this.tariff;
416
312
  }
417
- if (this.isNew || this.isModified('yearMonth')) {
418
- if (this.powerCharges && !this.powerCharges.yearMonth) {
419
- this.powerCharges.yearMonth = this.yearMonth;
420
- }
313
+ if ((this.isNew || this.isModified('yearMonth')) && this.powerCharges && !this.powerCharges.yearMonth) {
314
+ this.powerCharges.yearMonth = this.yearMonth;
421
315
  }
422
316
 
423
317
  // 3. Update payment method flags from reconciliation history
@@ -434,12 +328,7 @@ powerInvoiceSchema.pre('save', function (next) {
434
328
  };
435
329
  }
436
330
 
437
- if (
438
- paymentType &&
439
- (paymentType.includes('mobile') ||
440
- paymentType.includes('mpesa') ||
441
- paymentType.includes('m-pesa'))
442
- ) {
331
+ if (paymentType && (paymentType.includes('mobile') || paymentType.includes('mpesa') || paymentType.includes('m-pesa'))) {
443
332
  this.paymentMethods.mobilePayment.status = true;
444
333
  this.paymentMethod = 'mobile';
445
334
  } else if (paymentType && paymentType.includes('bank')) {
@@ -449,7 +338,6 @@ powerInvoiceSchema.pre('save', function (next) {
449
338
  this.paymentMethods.cashPayment.status = true;
450
339
  this.paymentMethod = 'cash';
451
340
  } else {
452
- // Default: treat unspecified / manual payments as cash
453
341
  this.paymentMethods.cashPayment.status = true;
454
342
  this.paymentMethod = 'cash';
455
343
  }
@@ -459,12 +347,13 @@ powerInvoiceSchema.pre('save', function (next) {
459
347
  next();
460
348
  });
461
349
 
462
- // Indexes
350
+ // ── Indexes ────────────────────────────────────────────────────────────────────
463
351
  powerInvoiceSchema.index({ facilityId: 1, yearMonth: -1 });
464
352
  powerInvoiceSchema.index({ customerId: 1 });
465
353
  powerInvoiceSchema.index({ status: 1 });
466
354
  powerInvoiceSchema.index({ meterNumber: 1 });
467
355
  powerInvoiceSchema.index({ accountNumber: 1 });
356
+ powerInvoiceSchema.index({ unitId: 1 });
468
357
 
469
358
  const PowerInvoice = mongoose.model('PowerInvoice', powerInvoiceSchema);
470
359
 
@@ -37,6 +37,11 @@ const supplierSchema = new mongoose.Schema({
37
37
  trim: true
38
38
  }
39
39
  },
40
+ category: {
41
+ type: String,
42
+ trim: true,
43
+ index: true
44
+ },
40
45
  taxIdentificationNumber: {
41
46
  type: String,
42
47
  trim: true