payservedb 6.9.9 → 7.0.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.
Files changed (159) hide show
  1. package/.env +2 -2
  2. package/index.js +288 -283
  3. package/package.json +17 -17
  4. package/src/models/account.js +35 -35
  5. package/src/models/apilog.js +18 -18
  6. package/src/models/approvalsWorkflows.js +49 -49
  7. package/src/models/archivedapilog.js +18 -18
  8. package/src/models/asset.js +92 -92
  9. package/src/models/assetsAssignment.js +64 -64
  10. package/src/models/auditTrail.js +346 -346
  11. package/src/models/bankdetails.js +40 -40
  12. package/src/models/billerAddress.js +119 -119
  13. package/src/models/booking_invoice.js +151 -151
  14. package/src/models/bookinganalytics.js +63 -63
  15. package/src/models/bookingconfig.js +45 -45
  16. package/src/models/bookingproperty.js +122 -122
  17. package/src/models/bookingreservation.js +192 -192
  18. package/src/models/bookingrevenuerecord.js +84 -84
  19. package/src/models/budget.js +95 -95
  20. package/src/models/budgetCategory.js +19 -19
  21. package/src/models/campaigns.js +94 -94
  22. package/src/models/cashpayment.js +264 -264
  23. package/src/models/combinedUnits.js +62 -62
  24. package/src/models/common_area_electricity.js +38 -38
  25. package/src/models/common_area_generator.js +41 -41
  26. package/src/models/common_area_utility_alert.js +37 -37
  27. package/src/models/common_area_water.js +39 -39
  28. package/src/models/communication_status.js +33 -33
  29. package/src/models/company.js +53 -53
  30. package/src/models/coreBaseSettings.js +16 -16
  31. package/src/models/coreInvoiceSettings.js +100 -100
  32. package/src/models/country_tax.js +42 -42
  33. package/src/models/currency_settings.js +39 -39
  34. package/src/models/customer.js +208 -208
  35. package/src/models/dailyChecklist.js +312 -312
  36. package/src/models/default_payment_details.js +17 -17
  37. package/src/models/deliveryTimeMarks.js +18 -18
  38. package/src/models/dutyRosterChecklist.js +250 -250
  39. package/src/models/dutyroster.js +136 -136
  40. package/src/models/email.js +37 -37
  41. package/src/models/email_sms_queue.js +61 -61
  42. package/src/models/entry_exit.js +53 -53
  43. package/src/models/expense.js +99 -99
  44. package/src/models/expense_category.js +45 -45
  45. package/src/models/facility.js +62 -62
  46. package/src/models/facilityContractPricing.js +51 -0
  47. package/src/models/facilityInvoice.js +222 -223
  48. package/src/models/facilityInvoiceContract.js +268 -0
  49. package/src/models/facilityWalletTransactionsMetadata.js +236 -236
  50. package/src/models/facility_departements.js +20 -20
  51. package/src/models/facility_payment_details.js +20 -20
  52. package/src/models/facilityasset.js +25 -25
  53. package/src/models/faq.js +18 -18
  54. package/src/models/gl_account_double_entries.js +25 -25
  55. package/src/models/gl_accounts.js +56 -56
  56. package/src/models/gl_entries.js +49 -49
  57. package/src/models/goodsReceivedNotes.js +115 -115
  58. package/src/models/guard.js +47 -47
  59. package/src/models/handover.js +246 -246
  60. package/src/models/invoice.js +336 -336
  61. package/src/models/item_inspection.js +67 -67
  62. package/src/models/leaseagreement.js +226 -226
  63. package/src/models/leasetemplate.js +17 -17
  64. package/src/models/levy.js +206 -206
  65. package/src/models/levy_invoice_settings.js +26 -26
  66. package/src/models/levycontract.js +168 -168
  67. package/src/models/levytype.js +23 -23
  68. package/src/models/maintenance_service_vendor.js +38 -38
  69. package/src/models/maintenance_services.js +17 -17
  70. package/src/models/maintenancerequisition.js +31 -31
  71. package/src/models/master_workplan.js +32 -32
  72. package/src/models/master_workplan_child.js +34 -34
  73. package/src/models/message.js +38 -38
  74. package/src/models/module.js +21 -21
  75. package/src/models/notification.js +44 -44
  76. package/src/models/paymentTermsMarks.js +19 -19
  77. package/src/models/penalty.js +76 -76
  78. package/src/models/pendingCredentials.js +32 -32
  79. package/src/models/powerMeterCommunicationProtocol.js +17 -17
  80. package/src/models/powerMeterCustomerAccount.js +78 -78
  81. package/src/models/powerMeterCustomerBand.js +14 -14
  82. package/src/models/powerMeterDailyReading.js +30 -30
  83. package/src/models/powerMeterGateways.js +37 -37
  84. package/src/models/powerMeterMonthlyReading.js +34 -34
  85. package/src/models/powerMeterPowerCharges.js +53 -53
  86. package/src/models/powerMeterSettings.js +138 -138
  87. package/src/models/powerMeterSingleDayReading.js +32 -32
  88. package/src/models/powerMeters.js +104 -104
  89. package/src/models/powerMetersManufacturer.js +14 -14
  90. package/src/models/power_meter_account.js +81 -81
  91. package/src/models/power_meter_command_logs.js +30 -30
  92. package/src/models/power_meter_command_queue.js +33 -0
  93. package/src/models/power_meter_negative_balance.js +44 -0
  94. package/src/models/power_prepaid_credits.js +47 -0
  95. package/src/models/power_prepaid_debits.js +50 -0
  96. package/src/models/propertyManagerContract.js +556 -556
  97. package/src/models/propertyManagerRevenue.js +195 -195
  98. package/src/models/purchaseOrderInvoice.js +74 -74
  99. package/src/models/purchase_order.js +213 -213
  100. package/src/models/purchase_request.js +110 -110
  101. package/src/models/refresh_token.js +23 -23
  102. package/src/models/reminder.js +197 -197
  103. package/src/models/report.js +13 -13
  104. package/src/models/resident.js +121 -121
  105. package/src/models/rfq_details.js +131 -131
  106. package/src/models/rfq_response.js +153 -153
  107. package/src/models/service_charge_invoice_upload.js +42 -42
  108. package/src/models/service_charge_payments.js +27 -27
  109. package/src/models/servicerequest.js +55 -55
  110. package/src/models/settings.js +62 -62
  111. package/src/models/smart_meter_daily_consumption.js +44 -44
  112. package/src/models/sms_africastalking.js +20 -20
  113. package/src/models/sms_balance_notification.js +26 -26
  114. package/src/models/sms_meliora.js +20 -20
  115. package/src/models/staff.js +36 -36
  116. package/src/models/stocksandspare.js +161 -161
  117. package/src/models/suppliers.js +74 -74
  118. package/src/models/tickets.js +121 -121
  119. package/src/models/unitManagementTemplate.js +44 -44
  120. package/src/models/unitasset.js +25 -25
  121. package/src/models/units.js +117 -117
  122. package/src/models/user.js +186 -186
  123. package/src/models/valueaddedservices.js +79 -79
  124. package/src/models/vas_invoices_upload.js +50 -50
  125. package/src/models/vas_payments.js +24 -24
  126. package/src/models/vasinvoice.js +192 -192
  127. package/src/models/vasvendor.js +57 -57
  128. package/src/models/visitLog.js +95 -95
  129. package/src/models/visitor.js +67 -67
  130. package/src/models/waitlist.js +45 -45
  131. package/src/models/wallet.js +44 -44
  132. package/src/models/wallet_transactions.js +50 -50
  133. package/src/models/water_invoice.js +305 -305
  134. package/src/models/water_meter_Command_Queue.js +33 -33
  135. package/src/models/water_meter_account.js +82 -82
  136. package/src/models/water_meter_billing.js +58 -58
  137. package/src/models/water_meter_communication.js +17 -17
  138. package/src/models/water_meter_communication_logs.js +40 -30
  139. package/src/models/water_meter_concentrator.js +66 -66
  140. package/src/models/water_meter_daily_history.js +32 -32
  141. package/src/models/water_meter_high_risk.js +37 -0
  142. package/src/models/water_meter_iot_cards.js +34 -34
  143. package/src/models/water_meter_manufacturer.js +35 -35
  144. package/src/models/water_meter_monthly_history.js +36 -36
  145. package/src/models/water_meter_negative_amounts.js +44 -39
  146. package/src/models/water_meter_settings.js +227 -227
  147. package/src/models/water_meter_single_day_history.js +34 -34
  148. package/src/models/water_meter_size.js +15 -15
  149. package/src/models/water_meters.js +134 -134
  150. package/src/models/water_meters_delivery.js +76 -76
  151. package/src/models/water_prepaid_credit.js +47 -47
  152. package/src/models/water_prepaid_debit.js +50 -50
  153. package/src/models/workorder.js +49 -49
  154. package/.idea/material_theme_project_new.xml +0 -12
  155. package/.idea/modules.xml +0 -8
  156. package/.idea/psdb.iml +0 -12
  157. package/.idea/vcs.xml +0 -6
  158. package/src/models/facilityInvoiceRecipient.js +0 -34
  159. package/src/models/power_prepaid_credts.js +0 -0
@@ -1,337 +1,337 @@
1
- const mongoose = require("mongoose");
2
-
3
- const invoiceSchema = new mongoose.Schema(
4
- {
5
- invoiceNumber: {
6
- type: String,
7
- required: true,
8
- unique: true,
9
- },
10
- accountNumber: {
11
- type: String,
12
- required: true,
13
- unique: true,
14
- },
15
- client: {
16
- clientId: {
17
- type: mongoose.Schema.Types.ObjectId,
18
- ref: "Customer",
19
- required: true,
20
- },
21
- firstName: {
22
- type: String,
23
- required: true
24
- },
25
- lastName: {
26
- type: String,
27
- required: true
28
- }
29
- },
30
- facility: {
31
- id: {
32
- type: mongoose.Schema.Types.ObjectId,
33
- ref: "Facility",
34
- required: true,
35
- },
36
- name: {
37
- type: String,
38
- required: true
39
- }
40
- },
41
- unit: {
42
- id: { type: mongoose.Schema.Types.ObjectId, ref: "Unit", required: true },
43
- name: { type: String, required: true },
44
- },
45
- currency: {
46
- id: {
47
- type: mongoose.Schema.Types.ObjectId,
48
- ref: "Currency",
49
- required: true
50
- },
51
- name: {
52
- type: String,
53
- required: true
54
- },
55
- code: {
56
- type: String,
57
- required: true,
58
- uppercase: true,
59
- minlength: 3,
60
- maxlength: 3
61
- }
62
- },
63
- items: [
64
- {
65
- description: { type: String, required: true },
66
- quantity: { type: Number, required: true, min: 1 },
67
- unitPrice: { type: Number, required: true, min: 0 },
68
- },
69
- ],
70
- subTotal: {
71
- type: Number,
72
- required: true,
73
- },
74
- tax: {
75
- type: Number,
76
- required: true,
77
- },
78
- totalAmount: {
79
- type: Number,
80
- required: true,
81
- },
82
- amountPaid: {
83
- type: Number,
84
- default: 0,
85
- min: 0,
86
- },
87
- // Mark as deprecated, keep for backward compatibility
88
- overpay: {
89
- type: Number,
90
- default: 0,
91
- min: 0,
92
- deprecated: true
93
- },
94
- issueDate: {
95
- type: Date,
96
- required: true,
97
- },
98
- dueDate: {
99
- type: Date,
100
- required: true,
101
- },
102
- status: {
103
- type: String,
104
- required: true,
105
- enum: ["Unpaid", "Pending", "Paid", "Overdue", "Cancelled", "Partially Paid", "Void"],
106
- },
107
- penalty: {
108
- type: Number,
109
- default: 0,
110
- },
111
- whatFor: {
112
- invoiceType: { type: String, required: true },
113
- description: { type: String },
114
- },
115
- invoiceNote: {
116
- type: String,
117
- default: null,
118
- },
119
- balanceBroughtForward: {
120
- type: Number,
121
- default: 0,
122
- },
123
- voidMetadata: {
124
- voidedBy: {
125
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
126
- name: { type: String },
127
- role: { type: String }
128
- },
129
- voidedAt: { type: Date },
130
- reason: { type: String }
131
- },
132
- cancelMetadata: {
133
- cancelledBy: {
134
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
135
- name: { type: String },
136
- role: { type: String }
137
- },
138
- cancelledAt: { type: Date },
139
- reason: { type: String }
140
- },
141
- lastReminderSent: Date,
142
- reminderHistory: [
143
- {
144
- sentAt: Date,
145
- reminderId: mongoose.Schema.Types.ObjectId,
146
- notificationTypes: [String],
147
- },
148
- ],
149
- reconciliationHistory: [{
150
- date: { type: Date, required: true },
151
- amount: { type: Number, required: true },
152
- type: {
153
- type: String,
154
- enum: ['payment', 'cash', 'cheque', 'bank-transfer', 'mpesa-transfer', 'overpay-transfer', 'balance-deduction', 'overpay-received', 'credit-forward', 'debit-forward'],
155
- required: true
156
- },
157
- sourceInvoice: String,
158
- destinationInvoice: String,
159
- paymentReference: String,
160
- paymentCompletion: String,
161
- remainingBalance: Number,
162
- notes: String,
163
- exchangeRate: {
164
- type: Number,
165
- default: 1 // For cross-currency reconciliations
166
- },
167
- originalCurrency: {
168
- code: String, // Original currency code if different from invoice currency
169
- amount: Number // Amount in original currency
170
- }
171
- }],
172
- paymentDetails: {
173
- paymentStatus: { type: String, required: true },
174
- paymentMethod: { type: String },
175
- paymentDate: { type: Date },
176
- transactionId: { type: String },
177
- },
178
- // New field to track when an invoice has been viewed
179
- viewStatus: {
180
- isOpened: { type: Boolean, default: false },
181
- openedAt: { type: Date, default: null },
182
- openedBy: {
183
- facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
184
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
185
- userRole: { type: String }
186
- },
187
- viewHistory: [{
188
- viewedAt: { type: Date, required: true },
189
- facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
190
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" }
191
- }]
192
- },
193
- invoiceUrl: {
194
- type: String,
195
- default: null
196
- },
197
- // New fields for double entry accounts
198
- invoiceDoubleEntryAccount: {
199
- type: mongoose.Schema.Types.ObjectId,
200
- ref: "GLAccountDoubleEntries",
201
- required: false,
202
- },
203
- paymentDoubleEntryAccount: {
204
- type: mongoose.Schema.Types.ObjectId,
205
- ref: "GLAccountDoubleEntries",
206
- required: false,
207
- },
208
- // GL account details for invoice entries
209
- accountdebitedData: {
210
- amount: { type: Number },
211
- description: { type: String },
212
- isActive: { type: Boolean, default: true }
213
- },
214
- accountcreditedData: {
215
- amount: { type: Number },
216
- description: { type: String },
217
- isActive: { type: Boolean, default: true }
218
- }
219
- },
220
- {
221
- timestamps: true,
222
- }
223
- );
224
-
225
- // Add indexes for frequently queried fields
226
- invoiceSchema.index({ accountNumber: 1 });
227
- invoiceSchema.index({ status: 1 });
228
- invoiceSchema.index({ 'client.clientId': 1, status: 1 });
229
- invoiceSchema.index({ 'reconciliationHistory.paymentReference': 1 });
230
- invoiceSchema.index({ issueDate: -1 });
231
- invoiceSchema.index({ 'currency.code': 1 }); // Add index for currency code
232
- invoiceSchema.index({ 'currency.id': 1 }); // Add index for currency ID
233
- invoiceSchema.index({ 'currency.code': 1, 'client.clientId': 1, status: 1 }); // Compound index for currency-based queries
234
- invoiceSchema.index({ 'client.clientId': 1, balanceBroughtForward: 1 }); // Add index for finding invoices with credit balances
235
- invoiceSchema.index({ 'viewStatus.isOpened': 1 }); // Add index for view status
236
- invoiceSchema.index({ 'viewStatus.openedBy.facilityId': 1 }); // Add index for facility view tracking
237
- // Add index for double entry accounts
238
- invoiceSchema.index({ invoiceDoubleEntryAccount: 1 });
239
- invoiceSchema.index({ paymentDoubleEntryAccount: 1 });
240
-
241
- // Add virtual field for calculating balance
242
- invoiceSchema.virtual('calculatedBalance').get(function () {
243
- const baseBalance = this.totalAmount - (this.amountPaid || 0);
244
-
245
- // Add positive balanceBroughtForward (customer owes money)
246
- if (this.balanceBroughtForward > 0) {
247
- return baseBalance + this.balanceBroughtForward;
248
- }
249
-
250
- // Subtract negative balanceBroughtForward (credit)
251
- return baseBalance;
252
- });
253
-
254
- // Add virtual field for credit balance
255
- invoiceSchema.virtual('creditBalance').get(function () {
256
- return this.balanceBroughtForward < 0 ? Math.abs(this.balanceBroughtForward) : 0;
257
- });
258
-
259
- // Getter for compatible overpay field
260
- invoiceSchema.virtual('effectiveOverpay').get(function () {
261
- return this.balanceBroughtForward < 0 ? Math.abs(this.balanceBroughtForward) : 0;
262
- });
263
-
264
- // Add virtual populate for invoice double entry account
265
- invoiceSchema.virtual('invoiceDoubleEntry', {
266
- ref: 'GLAccountDoubleEntries',
267
- localField: 'invoiceDoubleEntryAccount',
268
- foreignField: '_id',
269
- justOne: true
270
- });
271
-
272
- // Add virtual populate for payment double entry account
273
- invoiceSchema.virtual('paymentDoubleEntry', {
274
- ref: 'GLAccountDoubleEntries',
275
- localField: 'paymentDoubleEntryAccount',
276
- foreignField: '_id',
277
- justOne: true
278
- });
279
-
280
- // Add method for currency conversion if needed
281
- invoiceSchema.methods.convertAmount = function (amount, fromCurrency, toCurrency, exchangeRate) {
282
- if (fromCurrency === toCurrency) {
283
- return amount;
284
- }
285
- return amount * exchangeRate;
286
- };
287
-
288
- // Add static method to find invoices by currency
289
- invoiceSchema.statics.findByCurrency = function (currencyCode) {
290
- return this.find({ 'currency.code': currencyCode.toUpperCase() });
291
- };
292
-
293
- // Add static method to find invoices with credit balance
294
- invoiceSchema.statics.findWithCreditBalance = function (clientId) {
295
- return this.find({
296
- 'client.clientId': clientId,
297
- 'balanceBroughtForward': { $lt: 0 }
298
- }).sort({ updatedAt: -1 });
299
- };
300
-
301
- // Add static method to calculate totals by currency
302
- invoiceSchema.statics.calculateTotalsByCurrency = function (query = {}) {
303
- return this.aggregate([
304
- { $match: query },
305
- {
306
- $group: {
307
- _id: '$currency.code',
308
- totalAmount: { $sum: '$totalAmount' },
309
- totalPaid: { $sum: '$amountPaid' },
310
- count: { $sum: 1 }
311
- }
312
- }
313
- ]);
314
- };
315
-
316
- // New static method to find all unviewed invoices
317
- invoiceSchema.statics.findUnviewedInvoices = function (facilityId) {
318
- return this.find({
319
- 'facility.id': facilityId,
320
- 'viewStatus.isOpened': false
321
- });
322
- };
323
-
324
- // Pre-save middleware to ensure overpay and balanceBroughtForward stay in sync during transition
325
- invoiceSchema.pre('save', function (next) {
326
- // If balanceBroughtForward is negative (credit), sync with overpay for backwards compatibility
327
- if (this.balanceBroughtForward < 0) {
328
- this.overpay = Math.abs(this.balanceBroughtForward);
329
- } else {
330
- this.overpay = 0; // No overpay if there's no negative balance
331
- }
332
- next();
333
- });
334
-
335
- const Invoice = mongoose.model('Invoice', invoiceSchema);
336
-
1
+ const mongoose = require("mongoose");
2
+
3
+ const invoiceSchema = new mongoose.Schema(
4
+ {
5
+ invoiceNumber: {
6
+ type: String,
7
+ required: true,
8
+ unique: true,
9
+ },
10
+ accountNumber: {
11
+ type: String,
12
+ required: true,
13
+ unique: true,
14
+ },
15
+ client: {
16
+ clientId: {
17
+ type: mongoose.Schema.Types.ObjectId,
18
+ ref: "Customer",
19
+ required: true,
20
+ },
21
+ firstName: {
22
+ type: String,
23
+ required: true
24
+ },
25
+ lastName: {
26
+ type: String,
27
+ required: true
28
+ }
29
+ },
30
+ facility: {
31
+ id: {
32
+ type: mongoose.Schema.Types.ObjectId,
33
+ ref: "Facility",
34
+ required: true,
35
+ },
36
+ name: {
37
+ type: String,
38
+ required: true
39
+ }
40
+ },
41
+ unit: {
42
+ id: { type: mongoose.Schema.Types.ObjectId, ref: "Unit", required: true },
43
+ name: { type: String, required: true },
44
+ },
45
+ currency: {
46
+ id: {
47
+ type: mongoose.Schema.Types.ObjectId,
48
+ ref: "Currency",
49
+ required: true
50
+ },
51
+ name: {
52
+ type: String,
53
+ required: true
54
+ },
55
+ code: {
56
+ type: String,
57
+ required: true,
58
+ uppercase: true,
59
+ minlength: 3,
60
+ maxlength: 3
61
+ }
62
+ },
63
+ items: [
64
+ {
65
+ description: { type: String, required: true },
66
+ quantity: { type: Number, required: true, min: 1 },
67
+ unitPrice: { type: Number, required: true, min: 0 },
68
+ },
69
+ ],
70
+ subTotal: {
71
+ type: Number,
72
+ required: true,
73
+ },
74
+ tax: {
75
+ type: Number,
76
+ required: true,
77
+ },
78
+ totalAmount: {
79
+ type: Number,
80
+ required: true,
81
+ },
82
+ amountPaid: {
83
+ type: Number,
84
+ default: 0,
85
+ min: 0,
86
+ },
87
+ // Mark as deprecated, keep for backward compatibility
88
+ overpay: {
89
+ type: Number,
90
+ default: 0,
91
+ min: 0,
92
+ deprecated: true
93
+ },
94
+ issueDate: {
95
+ type: Date,
96
+ required: true,
97
+ },
98
+ dueDate: {
99
+ type: Date,
100
+ required: true,
101
+ },
102
+ status: {
103
+ type: String,
104
+ required: true,
105
+ enum: ["Unpaid", "Pending", "Paid", "Overdue", "Cancelled", "Partially Paid", "Void"],
106
+ },
107
+ penalty: {
108
+ type: Number,
109
+ default: 0,
110
+ },
111
+ whatFor: {
112
+ invoiceType: { type: String, required: true },
113
+ description: { type: String },
114
+ },
115
+ invoiceNote: {
116
+ type: String,
117
+ default: null,
118
+ },
119
+ balanceBroughtForward: {
120
+ type: Number,
121
+ default: 0,
122
+ },
123
+ voidMetadata: {
124
+ voidedBy: {
125
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
126
+ name: { type: String },
127
+ role: { type: String }
128
+ },
129
+ voidedAt: { type: Date },
130
+ reason: { type: String }
131
+ },
132
+ cancelMetadata: {
133
+ cancelledBy: {
134
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
135
+ name: { type: String },
136
+ role: { type: String }
137
+ },
138
+ cancelledAt: { type: Date },
139
+ reason: { type: String }
140
+ },
141
+ lastReminderSent: Date,
142
+ reminderHistory: [
143
+ {
144
+ sentAt: Date,
145
+ reminderId: mongoose.Schema.Types.ObjectId,
146
+ notificationTypes: [String],
147
+ },
148
+ ],
149
+ reconciliationHistory: [{
150
+ date: { type: Date, required: true },
151
+ amount: { type: Number, required: true },
152
+ type: {
153
+ type: String,
154
+ enum: ['payment', 'cash', 'cheque', 'bank-transfer', 'mpesa-transfer', 'overpay-transfer', 'balance-deduction', 'overpay-received', 'credit-forward', 'debit-forward'],
155
+ required: true
156
+ },
157
+ sourceInvoice: String,
158
+ destinationInvoice: String,
159
+ paymentReference: String,
160
+ paymentCompletion: String,
161
+ remainingBalance: Number,
162
+ notes: String,
163
+ exchangeRate: {
164
+ type: Number,
165
+ default: 1 // For cross-currency reconciliations
166
+ },
167
+ originalCurrency: {
168
+ code: String, // Original currency code if different from invoice currency
169
+ amount: Number // Amount in original currency
170
+ }
171
+ }],
172
+ paymentDetails: {
173
+ paymentStatus: { type: String, required: true },
174
+ paymentMethod: { type: String },
175
+ paymentDate: { type: Date },
176
+ transactionId: { type: String },
177
+ },
178
+ // New field to track when an invoice has been viewed
179
+ viewStatus: {
180
+ isOpened: { type: Boolean, default: false },
181
+ openedAt: { type: Date, default: null },
182
+ openedBy: {
183
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
184
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
185
+ userRole: { type: String }
186
+ },
187
+ viewHistory: [{
188
+ viewedAt: { type: Date, required: true },
189
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
190
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" }
191
+ }]
192
+ },
193
+ invoiceUrl: {
194
+ type: String,
195
+ default: null
196
+ },
197
+ // New fields for double entry accounts
198
+ invoiceDoubleEntryAccount: {
199
+ type: mongoose.Schema.Types.ObjectId,
200
+ ref: "GLAccountDoubleEntries",
201
+ required: false,
202
+ },
203
+ paymentDoubleEntryAccount: {
204
+ type: mongoose.Schema.Types.ObjectId,
205
+ ref: "GLAccountDoubleEntries",
206
+ required: false,
207
+ },
208
+ // GL account details for invoice entries
209
+ accountdebitedData: {
210
+ amount: { type: Number },
211
+ description: { type: String },
212
+ isActive: { type: Boolean, default: true }
213
+ },
214
+ accountcreditedData: {
215
+ amount: { type: Number },
216
+ description: { type: String },
217
+ isActive: { type: Boolean, default: true }
218
+ }
219
+ },
220
+ {
221
+ timestamps: true,
222
+ }
223
+ );
224
+
225
+ // Add indexes for frequently queried fields
226
+ invoiceSchema.index({ accountNumber: 1 });
227
+ invoiceSchema.index({ status: 1 });
228
+ invoiceSchema.index({ 'client.clientId': 1, status: 1 });
229
+ invoiceSchema.index({ 'reconciliationHistory.paymentReference': 1 });
230
+ invoiceSchema.index({ issueDate: -1 });
231
+ invoiceSchema.index({ 'currency.code': 1 }); // Add index for currency code
232
+ invoiceSchema.index({ 'currency.id': 1 }); // Add index for currency ID
233
+ invoiceSchema.index({ 'currency.code': 1, 'client.clientId': 1, status: 1 }); // Compound index for currency-based queries
234
+ invoiceSchema.index({ 'client.clientId': 1, balanceBroughtForward: 1 }); // Add index for finding invoices with credit balances
235
+ invoiceSchema.index({ 'viewStatus.isOpened': 1 }); // Add index for view status
236
+ invoiceSchema.index({ 'viewStatus.openedBy.facilityId': 1 }); // Add index for facility view tracking
237
+ // Add index for double entry accounts
238
+ invoiceSchema.index({ invoiceDoubleEntryAccount: 1 });
239
+ invoiceSchema.index({ paymentDoubleEntryAccount: 1 });
240
+
241
+ // Add virtual field for calculating balance
242
+ invoiceSchema.virtual('calculatedBalance').get(function () {
243
+ const baseBalance = this.totalAmount - (this.amountPaid || 0);
244
+
245
+ // Add positive balanceBroughtForward (customer owes money)
246
+ if (this.balanceBroughtForward > 0) {
247
+ return baseBalance + this.balanceBroughtForward;
248
+ }
249
+
250
+ // Subtract negative balanceBroughtForward (credit)
251
+ return baseBalance;
252
+ });
253
+
254
+ // Add virtual field for credit balance
255
+ invoiceSchema.virtual('creditBalance').get(function () {
256
+ return this.balanceBroughtForward < 0 ? Math.abs(this.balanceBroughtForward) : 0;
257
+ });
258
+
259
+ // Getter for compatible overpay field
260
+ invoiceSchema.virtual('effectiveOverpay').get(function () {
261
+ return this.balanceBroughtForward < 0 ? Math.abs(this.balanceBroughtForward) : 0;
262
+ });
263
+
264
+ // Add virtual populate for invoice double entry account
265
+ invoiceSchema.virtual('invoiceDoubleEntry', {
266
+ ref: 'GLAccountDoubleEntries',
267
+ localField: 'invoiceDoubleEntryAccount',
268
+ foreignField: '_id',
269
+ justOne: true
270
+ });
271
+
272
+ // Add virtual populate for payment double entry account
273
+ invoiceSchema.virtual('paymentDoubleEntry', {
274
+ ref: 'GLAccountDoubleEntries',
275
+ localField: 'paymentDoubleEntryAccount',
276
+ foreignField: '_id',
277
+ justOne: true
278
+ });
279
+
280
+ // Add method for currency conversion if needed
281
+ invoiceSchema.methods.convertAmount = function (amount, fromCurrency, toCurrency, exchangeRate) {
282
+ if (fromCurrency === toCurrency) {
283
+ return amount;
284
+ }
285
+ return amount * exchangeRate;
286
+ };
287
+
288
+ // Add static method to find invoices by currency
289
+ invoiceSchema.statics.findByCurrency = function (currencyCode) {
290
+ return this.find({ 'currency.code': currencyCode.toUpperCase() });
291
+ };
292
+
293
+ // Add static method to find invoices with credit balance
294
+ invoiceSchema.statics.findWithCreditBalance = function (clientId) {
295
+ return this.find({
296
+ 'client.clientId': clientId,
297
+ 'balanceBroughtForward': { $lt: 0 }
298
+ }).sort({ updatedAt: -1 });
299
+ };
300
+
301
+ // Add static method to calculate totals by currency
302
+ invoiceSchema.statics.calculateTotalsByCurrency = function (query = {}) {
303
+ return this.aggregate([
304
+ { $match: query },
305
+ {
306
+ $group: {
307
+ _id: '$currency.code',
308
+ totalAmount: { $sum: '$totalAmount' },
309
+ totalPaid: { $sum: '$amountPaid' },
310
+ count: { $sum: 1 }
311
+ }
312
+ }
313
+ ]);
314
+ };
315
+
316
+ // New static method to find all unviewed invoices
317
+ invoiceSchema.statics.findUnviewedInvoices = function (facilityId) {
318
+ return this.find({
319
+ 'facility.id': facilityId,
320
+ 'viewStatus.isOpened': false
321
+ });
322
+ };
323
+
324
+ // Pre-save middleware to ensure overpay and balanceBroughtForward stay in sync during transition
325
+ invoiceSchema.pre('save', function (next) {
326
+ // If balanceBroughtForward is negative (credit), sync with overpay for backwards compatibility
327
+ if (this.balanceBroughtForward < 0) {
328
+ this.overpay = Math.abs(this.balanceBroughtForward);
329
+ } else {
330
+ this.overpay = 0; // No overpay if there's no negative balance
331
+ }
332
+ next();
333
+ });
334
+
335
+ const Invoice = mongoose.model('Invoice', invoiceSchema);
336
+
337
337
  module.exports = Invoice;