payservedb 8.5.7 → 8.5.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/index.js CHANGED
@@ -94,6 +94,7 @@ const models = {
94
94
  LevyType: require("./src/models/levytype"),
95
95
  LevyContract: require("./src/models/levycontract"),
96
96
  Invoice: require("./src/models/invoice"),
97
+ CombinedInvoice: require("./src/models/combined_invoice"),
97
98
  InvoiceGeneration: require("./src/models/invoice_generation_approval"),
98
99
  ShortUrl: require("./src/models/short_urls"),
99
100
  InvoicingSchedule: require("./src/models/invoicing_schedule"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payservedb",
3
- "version": "8.5.7",
3
+ "version": "8.5.9",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -90,7 +90,7 @@ const cashPaymentSchema = new mongoose.Schema(
90
90
  },
91
91
  paymentMethod: {
92
92
  type: String,
93
- enum: ['cash', 'bank-transfer', 'cheque'],
93
+ enum: ['cash', 'bank-transfer', 'cheque', 'mpesa'],
94
94
  default: 'cash'
95
95
  },
96
96
  exchangeRate: {
@@ -0,0 +1,391 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const combinedInvoiceSchema = new mongoose.Schema(
4
+ {
5
+ // Customer Information
6
+ customer: {
7
+ customerId: {
8
+ type: mongoose.Schema.Types.ObjectId,
9
+ ref: "Customer",
10
+ required: true,
11
+ },
12
+ firstName: {
13
+ type: String,
14
+ required: true,
15
+ },
16
+ lastName: {
17
+ type: String,
18
+ required: true,
19
+ },
20
+ accountNumber: {
21
+ type: String,
22
+ required: true,
23
+ },
24
+ },
25
+
26
+ // Unit Information
27
+ unit: {
28
+ id: {
29
+ type: mongoose.Schema.Types.ObjectId,
30
+ ref: "Unit",
31
+ required: true
32
+ },
33
+ name: {
34
+ type: String,
35
+ required: true
36
+ },
37
+ },
38
+
39
+ // Facility Information
40
+ facility: {
41
+ id: {
42
+ type: mongoose.Schema.Types.ObjectId,
43
+ ref: "Facility",
44
+ required: true,
45
+ },
46
+ name: {
47
+ type: String,
48
+ required: true,
49
+ },
50
+ },
51
+
52
+ // Currency Information
53
+ currency: {
54
+ id: {
55
+ type: mongoose.Schema.Types.ObjectId,
56
+ ref: "Currency",
57
+ required: true,
58
+ },
59
+ name: {
60
+ type: String,
61
+ required: true,
62
+ },
63
+ code: {
64
+ type: String,
65
+ required: true,
66
+ uppercase: true,
67
+ minlength: 3,
68
+ maxlength: 3,
69
+ },
70
+ },
71
+
72
+ // Billing Period (Year-Month format)
73
+ period: {
74
+ type: String,
75
+ required: true,
76
+ match: /^\d{4}-\d{2}$/, // Format: "2025-01"
77
+ index: true,
78
+ },
79
+
80
+ // Issue Date
81
+ issueDate: {
82
+ type: Date,
83
+ required: true,
84
+ },
85
+
86
+ // Due Date (can be based on the latest invoice due date)
87
+ dueDate: {
88
+ type: Date,
89
+ required: true,
90
+ },
91
+
92
+ // Overall Status
93
+ status: {
94
+ type: String,
95
+ required: true,
96
+ enum: [
97
+ "Unpaid",
98
+ "Pending",
99
+ "Paid",
100
+ "Overdue",
101
+ "Cancelled",
102
+ "Partially Paid",
103
+ "Void",
104
+ ],
105
+ default: "Unpaid",
106
+ },
107
+
108
+ // Total Amount (sum of all individual invoices)
109
+ totalAmount: {
110
+ type: Number,
111
+ required: true,
112
+ default: 0,
113
+ },
114
+
115
+ // Total Tax (sum of all individual invoice taxes)
116
+ totalTax: {
117
+ type: Number,
118
+ required: true,
119
+ default: 0,
120
+ },
121
+
122
+ // Total Amount Paid
123
+ amountPaid: {
124
+ type: Number,
125
+ default: 0,
126
+ min: 0,
127
+ },
128
+
129
+ // Total Balance Brought Forward
130
+ totalBalanceBroughtForward: {
131
+ type: Number,
132
+ default: 0,
133
+ },
134
+
135
+ // Array of Individual Invoices
136
+ invoices: [
137
+ {
138
+ invoiceId: {
139
+ type: mongoose.Schema.Types.ObjectId,
140
+ ref: "Invoice",
141
+ required: true,
142
+ },
143
+ invoiceNumber: {
144
+ type: String,
145
+ required: true,
146
+ },
147
+ type: {
148
+ type: String,
149
+ required: true,
150
+ enum: ["contract", "lease", "water", "power", "other"],
151
+ },
152
+ description: {
153
+ type: String,
154
+ required: true,
155
+ },
156
+ period: {
157
+ type: String,
158
+ required: true,
159
+ match: /^\d{4}-\d{2}$/,
160
+ },
161
+ amount: {
162
+ type: Number,
163
+ required: true,
164
+ min: 0,
165
+ },
166
+ tax: {
167
+ type: Number,
168
+ required: true,
169
+ default: 0,
170
+ },
171
+ balanceBroughtForward: {
172
+ type: Number,
173
+ default: 0,
174
+ },
175
+ totalAmount: {
176
+ type: Number,
177
+ required: true,
178
+ },
179
+ amountPaid: {
180
+ type: Number,
181
+ default: 0,
182
+ },
183
+ status: {
184
+ type: String,
185
+ required: true,
186
+ enum: [
187
+ "Unpaid",
188
+ "Pending",
189
+ "Paid",
190
+ "Overdue",
191
+ "Cancelled",
192
+ "Partially Paid",
193
+ "Void",
194
+ ],
195
+ },
196
+ issueDate: {
197
+ type: Date,
198
+ required: true,
199
+ },
200
+ dueDate: {
201
+ type: Date,
202
+ required: true,
203
+ },
204
+ },
205
+ ],
206
+
207
+ // Notification tracking for the combined invoice
208
+ notificationsSent: {
209
+ sms: {
210
+ type: Boolean,
211
+ default: false,
212
+ },
213
+ email: {
214
+ type: Boolean,
215
+ default: false,
216
+ },
217
+ attempts: {
218
+ type: Number,
219
+ default: 0,
220
+ },
221
+ },
222
+
223
+ // Last reminder sent
224
+ lastReminderSent: Date,
225
+
226
+ // Combined invoice notes
227
+ invoiceNote: {
228
+ type: String,
229
+ default: null,
230
+ },
231
+
232
+ // View status tracking
233
+ viewStatus: {
234
+ isOpened: { type: Boolean, default: false },
235
+ openedAt: { type: Date, default: null },
236
+ openedBy: {
237
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
238
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
239
+ userRole: { type: String },
240
+ },
241
+ viewHistory: [
242
+ {
243
+ viewedAt: { type: Date, required: true },
244
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
245
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
246
+ },
247
+ ],
248
+ },
249
+
250
+ // Combined invoice URL (if generated)
251
+ invoiceUrl: {
252
+ type: String,
253
+ default: null,
254
+ },
255
+ },
256
+ {
257
+ timestamps: true,
258
+ }
259
+ );
260
+
261
+ // Indexes for efficient querying
262
+ combinedInvoiceSchema.index({ "customer.customerId": 1, period: 1 });
263
+ combinedInvoiceSchema.index({ "unit.id": 1, period: 1 });
264
+ combinedInvoiceSchema.index({ "facility.id": 1, period: 1 });
265
+ combinedInvoiceSchema.index({ status: 1 });
266
+ combinedInvoiceSchema.index({ period: 1 });
267
+ combinedInvoiceSchema.index({ issueDate: -1 });
268
+ combinedInvoiceSchema.index({ "customer.customerId": 1, status: 1 });
269
+ combinedInvoiceSchema.index({ "invoices.invoiceId": 1 });
270
+ combinedInvoiceSchema.index({ "currency.code": 1 });
271
+
272
+ // Compound index for unique combined invoice per customer/unit/facility/period
273
+ combinedInvoiceSchema.index(
274
+ {
275
+ "customer.customerId": 1,
276
+ "unit.id": 1,
277
+ "facility.id": 1,
278
+ period: 1
279
+ },
280
+ { unique: true }
281
+ );
282
+
283
+ // Virtual field for calculating total balance
284
+ combinedInvoiceSchema.virtual("calculatedBalance").get(function () {
285
+ return this.totalAmount - (this.amountPaid || 0) + (this.totalBalanceBroughtForward || 0);
286
+ });
287
+
288
+ // Virtual field for invoice count
289
+ combinedInvoiceSchema.virtual("invoiceCount").get(function () {
290
+ return this.invoices ? this.invoices.length : 0;
291
+ });
292
+
293
+ // Static method to find combined invoice by period and customer
294
+ combinedInvoiceSchema.statics.findByCustomerAndPeriod = function (customerId, period) {
295
+ return this.findOne({
296
+ "customer.customerId": customerId,
297
+ period: period,
298
+ });
299
+ };
300
+
301
+ // Static method to find combined invoices by facility and period
302
+ combinedInvoiceSchema.statics.findByFacilityAndPeriod = function (facilityId, period) {
303
+ return this.find({
304
+ "facility.id": facilityId,
305
+ period: period,
306
+ });
307
+ };
308
+
309
+ // Static method to find all combined invoices for a customer
310
+ combinedInvoiceSchema.statics.findByCustomer = function (customerId) {
311
+ return this.find({
312
+ "customer.customerId": customerId,
313
+ }).sort({ period: -1 });
314
+ };
315
+
316
+ // Static method to find pending notifications
317
+ combinedInvoiceSchema.statics.findPendingNotifications = function (facilityId) {
318
+ return this.find({
319
+ "facility.id": facilityId,
320
+ status: { $in: ["Unpaid", "Overdue"] },
321
+ $or: [
322
+ { "notificationsSent.sms": false },
323
+ { "notificationsSent.email": false },
324
+ ],
325
+ });
326
+ };
327
+
328
+ // Method to update status based on individual invoices
329
+ combinedInvoiceSchema.methods.updateOverallStatus = function () {
330
+ if (!this.invoices || this.invoices.length === 0) {
331
+ this.status = "Unpaid";
332
+ return;
333
+ }
334
+
335
+ const statuses = this.invoices.map(inv => inv.status);
336
+
337
+ // If all paid, mark as paid
338
+ if (statuses.every(s => s === "Paid")) {
339
+ this.status = "Paid";
340
+ }
341
+ // If any overdue, mark as overdue
342
+ else if (statuses.some(s => s === "Overdue")) {
343
+ this.status = "Overdue";
344
+ }
345
+ // If any partially paid, mark as partially paid
346
+ else if (statuses.some(s => s === "Partially Paid")) {
347
+ this.status = "Partially Paid";
348
+ }
349
+ // If all cancelled, mark as cancelled
350
+ else if (statuses.every(s => s === "Cancelled")) {
351
+ this.status = "Cancelled";
352
+ }
353
+ // If all void, mark as void
354
+ else if (statuses.every(s => s === "Void")) {
355
+ this.status = "Void";
356
+ }
357
+ // Otherwise unpaid
358
+ else {
359
+ this.status = "Unpaid";
360
+ }
361
+ };
362
+
363
+ // Method to recalculate totals from individual invoices
364
+ combinedInvoiceSchema.methods.recalculateTotals = function () {
365
+ if (!this.invoices || this.invoices.length === 0) {
366
+ this.totalAmount = 0;
367
+ this.totalTax = 0;
368
+ this.amountPaid = 0;
369
+ this.totalBalanceBroughtForward = 0;
370
+ return;
371
+ }
372
+
373
+ this.totalAmount = this.invoices.reduce((sum, inv) => sum + (inv.totalAmount || 0), 0);
374
+ this.totalTax = this.invoices.reduce((sum, inv) => sum + (inv.tax || 0), 0);
375
+ this.amountPaid = this.invoices.reduce((sum, inv) => sum + (inv.amountPaid || 0), 0);
376
+ this.totalBalanceBroughtForward = this.invoices.reduce(
377
+ (sum, inv) => sum + (inv.balanceBroughtForward || 0),
378
+ 0
379
+ );
380
+ };
381
+
382
+ // Pre-save middleware to auto-calculate totals and status
383
+ combinedInvoiceSchema.pre("save", function (next) {
384
+ this.recalculateTotals();
385
+ this.updateOverallStatus();
386
+ next();
387
+ });
388
+
389
+ const CombinedInvoice = mongoose.model("CombinedInvoice", combinedInvoiceSchema);
390
+
391
+ module.exports = CombinedInvoice;
@@ -22,7 +22,7 @@ const userSchema = new mongoose.Schema({
22
22
  type: String,
23
23
  required: false
24
24
  },
25
- type: {
25
+ type: {
26
26
  type: String,
27
27
  required: [true, 'Type is required'],
28
28
  enum: ['Company', 'Project Manager', 'Universal', 'Core', 'Resident', 'Landlord', 'Supplier', 'Customer_Support'],
@@ -126,7 +126,6 @@ const userSchema = new mongoose.Schema({
126
126
  }
127
127
  },
128
128
  kyc: {
129
-
130
129
  Id: {
131
130
  type: String
132
131
  }
@@ -135,6 +134,10 @@ const userSchema = new mongoose.Schema({
135
134
  type: Boolean,
136
135
  required: false
137
136
  },
137
+ combineInvoices: {
138
+ type: Boolean,
139
+ default: false
140
+ },
138
141
  companies: [{
139
142
  type: mongoose.Schema.Types.ObjectId,
140
143
  ref: 'Company',