payservedb 8.4.5 → 8.4.7

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.
@@ -9,7 +9,7 @@ const invoiceSchema = new mongoose.Schema(
9
9
  },
10
10
  accountNumber: {
11
11
  type: String,
12
- required: true
12
+ required: true,
13
13
  },
14
14
  client: {
15
15
  clientId: {
@@ -19,12 +19,12 @@ const invoiceSchema = new mongoose.Schema(
19
19
  },
20
20
  firstName: {
21
21
  type: String,
22
- required: true
22
+ required: true,
23
23
  },
24
24
  lastName: {
25
25
  type: String,
26
- required: true
27
- }
26
+ required: true,
27
+ },
28
28
  },
29
29
  facility: {
30
30
  id: {
@@ -34,8 +34,8 @@ const invoiceSchema = new mongoose.Schema(
34
34
  },
35
35
  name: {
36
36
  type: String,
37
- required: true
38
- }
37
+ required: true,
38
+ },
39
39
  },
40
40
  unit: {
41
41
  id: { type: mongoose.Schema.Types.ObjectId, ref: "Unit", required: true },
@@ -45,19 +45,19 @@ const invoiceSchema = new mongoose.Schema(
45
45
  id: {
46
46
  type: mongoose.Schema.Types.ObjectId,
47
47
  ref: "Currency",
48
- required: true
48
+ required: true,
49
49
  },
50
50
  name: {
51
51
  type: String,
52
- required: true
52
+ required: true,
53
53
  },
54
54
  code: {
55
55
  type: String,
56
56
  required: true,
57
57
  uppercase: true,
58
58
  minlength: 3,
59
- maxlength: 3
60
- }
59
+ maxlength: 3,
60
+ },
61
61
  },
62
62
  items: [
63
63
  {
@@ -88,7 +88,7 @@ const invoiceSchema = new mongoose.Schema(
88
88
  type: Number,
89
89
  default: 0,
90
90
  min: 0,
91
- deprecated: true
91
+ deprecated: true,
92
92
  },
93
93
  issueDate: {
94
94
  type: Date,
@@ -101,7 +101,15 @@ const invoiceSchema = new mongoose.Schema(
101
101
  status: {
102
102
  type: String,
103
103
  required: true,
104
- enum: ["Unpaid", "Pending", "Paid", "Overdue", "Cancelled", "Partially Paid", "Void"],
104
+ enum: [
105
+ "Unpaid",
106
+ "Pending",
107
+ "Paid",
108
+ "Overdue",
109
+ "Cancelled",
110
+ "Partially Paid",
111
+ "Void",
112
+ ],
105
113
  },
106
114
  penalty: {
107
115
  type: Number,
@@ -123,40 +131,40 @@ const invoiceSchema = new mongoose.Schema(
123
131
  yearMonth: {
124
132
  type: String,
125
133
  match: /^\d{4}-\d{2}$/, // Validates format like "2025-08"
126
- index: true
134
+ index: true,
127
135
  },
128
136
  // NEW FIELD 2: Simple notification tracking
129
137
  notificationsSent: {
130
138
  sms: {
131
139
  type: Boolean,
132
- default: false
140
+ default: false,
133
141
  },
134
142
  email: {
135
143
  type: Boolean,
136
- default: false
144
+ default: false,
137
145
  },
138
146
  attempts: {
139
147
  type: Number,
140
- default: 0
141
- }
148
+ default: 0,
149
+ },
142
150
  },
143
151
  voidMetadata: {
144
152
  voidedBy: {
145
153
  userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
146
154
  name: { type: String },
147
- role: { type: String }
155
+ role: { type: String },
148
156
  },
149
157
  voidedAt: { type: Date },
150
- reason: { type: String }
158
+ reason: { type: String },
151
159
  },
152
160
  cancelMetadata: {
153
161
  cancelledBy: {
154
162
  userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
155
163
  name: { type: String },
156
- role: { type: String }
164
+ role: { type: String },
157
165
  },
158
166
  cancelledAt: { type: Date },
159
- reason: { type: String }
167
+ reason: { type: String },
160
168
  },
161
169
  lastReminderSent: Date,
162
170
  reminderHistory: [
@@ -166,29 +174,42 @@ const invoiceSchema = new mongoose.Schema(
166
174
  notificationTypes: [String],
167
175
  },
168
176
  ],
169
- reconciliationHistory: [{
170
- date: { type: Date, required: true },
171
- amount: { type: Number, required: true },
172
- type: {
173
- type: String,
174
- enum: ['payment', 'cash', 'cheque', 'bank-transfer', 'mpesa-transfer', 'overpay-transfer', 'balance-deduction', 'overpay-received', 'credit-forward', 'debit-forward'],
175
- required: true
176
- },
177
- sourceInvoice: String,
178
- destinationInvoice: String,
179
- paymentReference: String,
180
- paymentCompletion: String,
181
- remainingBalance: Number,
182
- notes: String,
183
- exchangeRate: {
184
- type: Number,
185
- default: 1 // For cross-currency reconciliations
177
+ reconciliationHistory: [
178
+ {
179
+ date: { type: Date, required: true },
180
+ amount: { type: Number, required: true },
181
+ type: {
182
+ type: String,
183
+ enum: [
184
+ "payment",
185
+ "cash",
186
+ "cheque",
187
+ "bank-transfer",
188
+ "mpesa-transfer",
189
+ "overpay-transfer",
190
+ "balance-deduction",
191
+ "overpay-received",
192
+ "credit-forward",
193
+ "debit-forward",
194
+ ],
195
+ required: true,
196
+ },
197
+ sourceInvoice: String,
198
+ destinationInvoice: String,
199
+ paymentReference: String,
200
+ paymentCompletion: String,
201
+ remainingBalance: Number,
202
+ notes: String,
203
+ exchangeRate: {
204
+ type: Number,
205
+ default: 1, // For cross-currency reconciliations
206
+ },
207
+ originalCurrency: {
208
+ code: String, // Original currency code if different from invoice currency
209
+ amount: Number, // Amount in original currency
210
+ },
186
211
  },
187
- originalCurrency: {
188
- code: String, // Original currency code if different from invoice currency
189
- amount: Number // Amount in original currency
190
- }
191
- }],
212
+ ],
192
213
  paymentDetails: {
193
214
  paymentStatus: { type: String, required: true },
194
215
  paymentMethod: { type: String },
@@ -202,17 +223,60 @@ const invoiceSchema = new mongoose.Schema(
202
223
  openedBy: {
203
224
  facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
204
225
  userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
205
- userRole: { type: String }
226
+ userRole: { type: String },
206
227
  },
207
- viewHistory: [{
208
- viewedAt: { type: Date, required: true },
209
- facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
210
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" }
211
- }]
228
+ viewHistory: [
229
+ {
230
+ viewedAt: { type: Date, required: true },
231
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
232
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
233
+ },
234
+ ],
212
235
  },
213
236
  invoiceUrl: {
214
237
  type: String,
215
- default: null
238
+ default: null,
239
+ },
240
+ // eTims tax integration fields
241
+ ticketUrl: {
242
+ type: String,
243
+ default: null,
244
+ description: "QR code URL from eTims/KRA for tax invoice verification",
245
+ },
246
+ txsync: {
247
+ status: {
248
+ type: String,
249
+ enum: ["pending", "synced", "failed", "not-applicable"],
250
+ default: "not-applicable",
251
+ },
252
+ syncedAt: {
253
+ type: Date,
254
+ default: null,
255
+ },
256
+ etimsInvoiceNo: {
257
+ type: String,
258
+ default: null,
259
+ },
260
+ sdcId: {
261
+ type: String,
262
+ default: null,
263
+ },
264
+ receiptNo: {
265
+ type: String,
266
+ default: null,
267
+ },
268
+ errorMessage: {
269
+ type: String,
270
+ default: null,
271
+ },
272
+ attempts: {
273
+ type: Number,
274
+ default: 0,
275
+ },
276
+ lastAttemptAt: {
277
+ type: Date,
278
+ default: null,
279
+ },
216
280
  },
217
281
  // New fields for double entry accounts
218
282
  invoiceDoubleEntryAccount: {
@@ -229,42 +293,45 @@ const invoiceSchema = new mongoose.Schema(
229
293
  accountdebitedData: {
230
294
  amount: { type: Number },
231
295
  description: { type: String },
232
- isActive: { type: Boolean, default: true }
296
+ isActive: { type: Boolean, default: true },
233
297
  },
234
298
  accountcreditedData: {
235
299
  amount: { type: Number },
236
300
  description: { type: String },
237
- isActive: { type: Boolean, default: true }
238
- }
301
+ isActive: { type: Boolean, default: true },
302
+ },
239
303
  },
240
304
  {
241
305
  timestamps: true,
242
- }
306
+ },
243
307
  );
244
308
 
245
309
  // Add indexes for frequently queried fields
246
310
  invoiceSchema.index({ accountNumber: 1 });
247
311
  invoiceSchema.index({ status: 1 });
248
- invoiceSchema.index({ 'client.clientId': 1, status: 1 });
249
- invoiceSchema.index({ 'reconciliationHistory.paymentReference': 1 });
312
+ invoiceSchema.index({ "client.clientId": 1, status: 1 });
313
+ invoiceSchema.index({ "reconciliationHistory.paymentReference": 1 });
250
314
  invoiceSchema.index({ issueDate: -1 });
251
- invoiceSchema.index({ 'currency.code': 1 }); // Add index for currency code
252
- invoiceSchema.index({ 'currency.id': 1 }); // Add index for currency ID
253
- invoiceSchema.index({ 'currency.code': 1, 'client.clientId': 1, status: 1 }); // Compound index for currency-based queries
254
- invoiceSchema.index({ 'client.clientId': 1, balanceBroughtForward: 1 }); // Add index for finding invoices with credit balances
255
- invoiceSchema.index({ 'viewStatus.isOpened': 1 }); // Add index for view status
256
- invoiceSchema.index({ 'viewStatus.openedBy.facilityId': 1 }); // Add index for facility view tracking
315
+ invoiceSchema.index({ "currency.code": 1 }); // Add index for currency code
316
+ invoiceSchema.index({ "currency.id": 1 }); // Add index for currency ID
317
+ invoiceSchema.index({ "currency.code": 1, "client.clientId": 1, status: 1 }); // Compound index for currency-based queries
318
+ invoiceSchema.index({ "client.clientId": 1, balanceBroughtForward: 1 }); // Add index for finding invoices with credit balances
319
+ invoiceSchema.index({ "viewStatus.isOpened": 1 }); // Add index for view status
320
+ invoiceSchema.index({ "viewStatus.openedBy.facilityId": 1 }); // Add index for facility view tracking
257
321
  // Add index for double entry accounts
258
322
  invoiceSchema.index({ invoiceDoubleEntryAccount: 1 });
259
323
  invoiceSchema.index({ paymentDoubleEntryAccount: 1 });
260
324
  // NEW INDEXES for new fields
261
325
  invoiceSchema.index({ yearMonth: 1 }); // For filtering by year-month
262
326
  invoiceSchema.index({ yearMonth: 1, status: 1 }); // Combined index for monthly reports
263
- invoiceSchema.index({ 'notificationsSent.sms': 1 }); // For finding SMS sent/not sent
264
- invoiceSchema.index({ 'notificationsSent.email': 1 }); // For finding email sent/not sent
327
+ invoiceSchema.index({ "notificationsSent.sms": 1 }); // For finding SMS sent/not sent
328
+ invoiceSchema.index({ "notificationsSent.email": 1 }); // For finding email sent/not sent
329
+ // eTims tax sync indexes
330
+ invoiceSchema.index({ "txsync.status": 1 }); // For finding invoices by sync status
331
+ invoiceSchema.index({ "txsync.etimsInvoiceNo": 1 }); // For looking up by eTims invoice number
265
332
 
266
333
  // Add virtual field for calculating balance
267
- invoiceSchema.virtual('calculatedBalance').get(function () {
334
+ invoiceSchema.virtual("calculatedBalance").get(function () {
268
335
  const baseBalance = this.totalAmount - (this.amountPaid || 0);
269
336
 
270
337
  // Add positive balanceBroughtForward (customer owes money)
@@ -277,33 +344,42 @@ invoiceSchema.virtual('calculatedBalance').get(function () {
277
344
  });
278
345
 
279
346
  // Add virtual field for credit balance
280
- invoiceSchema.virtual('creditBalance').get(function () {
281
- return this.balanceBroughtForward < 0 ? Math.abs(this.balanceBroughtForward) : 0;
347
+ invoiceSchema.virtual("creditBalance").get(function () {
348
+ return this.balanceBroughtForward < 0
349
+ ? Math.abs(this.balanceBroughtForward)
350
+ : 0;
282
351
  });
283
352
 
284
353
  // Getter for compatible overpay field
285
- invoiceSchema.virtual('effectiveOverpay').get(function () {
286
- return this.balanceBroughtForward < 0 ? Math.abs(this.balanceBroughtForward) : 0;
354
+ invoiceSchema.virtual("effectiveOverpay").get(function () {
355
+ return this.balanceBroughtForward < 0
356
+ ? Math.abs(this.balanceBroughtForward)
357
+ : 0;
287
358
  });
288
359
 
289
360
  // Add virtual populate for invoice double entry account
290
- invoiceSchema.virtual('invoiceDoubleEntry', {
291
- ref: 'GLAccountDoubleEntries',
292
- localField: 'invoiceDoubleEntryAccount',
293
- foreignField: '_id',
294
- justOne: true
361
+ invoiceSchema.virtual("invoiceDoubleEntry", {
362
+ ref: "GLAccountDoubleEntries",
363
+ localField: "invoiceDoubleEntryAccount",
364
+ foreignField: "_id",
365
+ justOne: true,
295
366
  });
296
367
 
297
368
  // Add virtual populate for payment double entry account
298
- invoiceSchema.virtual('paymentDoubleEntry', {
299
- ref: 'GLAccountDoubleEntries',
300
- localField: 'paymentDoubleEntryAccount',
301
- foreignField: '_id',
302
- justOne: true
369
+ invoiceSchema.virtual("paymentDoubleEntry", {
370
+ ref: "GLAccountDoubleEntries",
371
+ localField: "paymentDoubleEntryAccount",
372
+ foreignField: "_id",
373
+ justOne: true,
303
374
  });
304
375
 
305
376
  // Add method for currency conversion if needed
306
- invoiceSchema.methods.convertAmount = function (amount, fromCurrency, toCurrency, exchangeRate) {
377
+ invoiceSchema.methods.convertAmount = function (
378
+ amount,
379
+ fromCurrency,
380
+ toCurrency,
381
+ exchangeRate,
382
+ ) {
307
383
  if (fromCurrency === toCurrency) {
308
384
  return amount;
309
385
  }
@@ -312,14 +388,14 @@ invoiceSchema.methods.convertAmount = function (amount, fromCurrency, toCurrency
312
388
 
313
389
  // Add static method to find invoices by currency
314
390
  invoiceSchema.statics.findByCurrency = function (currencyCode) {
315
- return this.find({ 'currency.code': currencyCode.toUpperCase() });
391
+ return this.find({ "currency.code": currencyCode.toUpperCase() });
316
392
  };
317
393
 
318
394
  // Add static method to find invoices with credit balance
319
395
  invoiceSchema.statics.findWithCreditBalance = function (clientId) {
320
396
  return this.find({
321
- 'client.clientId': clientId,
322
- 'balanceBroughtForward': { $lt: 0 }
397
+ "client.clientId": clientId,
398
+ balanceBroughtForward: { $lt: 0 },
323
399
  }).sort({ updatedAt: -1 });
324
400
  };
325
401
 
@@ -329,20 +405,20 @@ invoiceSchema.statics.calculateTotalsByCurrency = function (query = {}) {
329
405
  { $match: query },
330
406
  {
331
407
  $group: {
332
- _id: '$currency.code',
333
- totalAmount: { $sum: '$totalAmount' },
334
- totalPaid: { $sum: '$amountPaid' },
335
- count: { $sum: 1 }
336
- }
337
- }
408
+ _id: "$currency.code",
409
+ totalAmount: { $sum: "$totalAmount" },
410
+ totalPaid: { $sum: "$amountPaid" },
411
+ count: { $sum: 1 },
412
+ },
413
+ },
338
414
  ]);
339
415
  };
340
416
 
341
417
  // New static method to find all unviewed invoices
342
418
  invoiceSchema.statics.findUnviewedInvoices = function (facilityId) {
343
419
  return this.find({
344
- 'facility.id': facilityId,
345
- 'viewStatus.isOpened': false
420
+ "facility.id": facilityId,
421
+ "viewStatus.isOpened": false,
346
422
  });
347
423
  };
348
424
 
@@ -355,17 +431,33 @@ invoiceSchema.statics.findByYearMonth = function (yearMonth) {
355
431
  // Find invoices where notifications haven't been sent
356
432
  invoiceSchema.statics.findPendingNotifications = function (facilityId) {
357
433
  return this.find({
358
- 'facility.id': facilityId,
359
- status: { $in: ['Unpaid', 'Overdue'] },
434
+ "facility.id": facilityId,
435
+ status: { $in: ["Unpaid", "Overdue"] },
360
436
  $or: [
361
- { 'notificationsSent.sms': false },
362
- { 'notificationsSent.email': false }
363
- ]
437
+ { "notificationsSent.sms": false },
438
+ { "notificationsSent.email": false },
439
+ ],
440
+ });
441
+ };
442
+
443
+ // Find invoices pending eTims sync
444
+ invoiceSchema.statics.findPendingTaxSync = function (facilityId) {
445
+ return this.find({
446
+ "facility.id": facilityId,
447
+ "txsync.status": "pending",
448
+ });
449
+ };
450
+
451
+ // Find invoices with failed eTims sync
452
+ invoiceSchema.statics.findFailedTaxSync = function (facilityId) {
453
+ return this.find({
454
+ "facility.id": facilityId,
455
+ "txsync.status": "failed",
364
456
  });
365
457
  };
366
458
 
367
459
  // Pre-save middleware to ensure overpay and balanceBroughtForward stay in sync during transition
368
- invoiceSchema.pre('save', function (next) {
460
+ invoiceSchema.pre("save", function (next) {
369
461
  // If balanceBroughtForward is negative (credit), sync with overpay for backwards compatibility
370
462
  if (this.balanceBroughtForward < 0) {
371
463
  this.overpay = Math.abs(this.balanceBroughtForward);
@@ -376,13 +468,13 @@ invoiceSchema.pre('save', function (next) {
376
468
  // NEW: Auto-generate yearMonth from issueDate if not provided
377
469
  if (!this.yearMonth && this.issueDate) {
378
470
  const year = this.issueDate.getFullYear();
379
- const month = String(this.issueDate.getMonth() + 1).padStart(2, '0');
471
+ const month = String(this.issueDate.getMonth() + 1).padStart(2, "0");
380
472
  this.yearMonth = `${year}-${month}`;
381
473
  }
382
474
 
383
475
  next();
384
476
  });
385
477
 
386
- const Invoice = mongoose.model('Invoice', invoiceSchema);
478
+ const Invoice = mongoose.model("Invoice", invoiceSchema);
387
479
 
388
- module.exports = Invoice;
480
+ module.exports = Invoice;
@@ -16,6 +16,11 @@ const InvoicingScheduleSchema = new mongoose.Schema({
16
16
  trim: true,
17
17
  index: true
18
18
  },
19
+ isAutomatic: {
20
+ type: Boolean,
21
+ required: true,
22
+ default: true
23
+ },
19
24
  createdAt: {
20
25
  type: Date,
21
26
  default: Date.now
@@ -33,4 +38,4 @@ InvoicingScheduleSchema.pre('save', function(next) {
33
38
 
34
39
  const InvoicingSchedule = mongoose.model('InvoicingSchedule', InvoicingScheduleSchema);
35
40
 
36
- module.exports = InvoicingSchedule;
41
+ module.exports = InvoicingSchedule;