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.
- package/.env +2 -2
- package/index.js +288 -283
- package/package.json +17 -17
- package/src/models/account.js +35 -35
- package/src/models/apilog.js +18 -18
- package/src/models/approvalsWorkflows.js +49 -49
- package/src/models/archivedapilog.js +18 -18
- package/src/models/asset.js +92 -92
- package/src/models/assetsAssignment.js +64 -64
- package/src/models/auditTrail.js +346 -346
- package/src/models/bankdetails.js +40 -40
- package/src/models/billerAddress.js +119 -119
- package/src/models/booking_invoice.js +151 -151
- package/src/models/bookinganalytics.js +63 -63
- package/src/models/bookingconfig.js +45 -45
- package/src/models/bookingproperty.js +122 -122
- package/src/models/bookingreservation.js +192 -192
- package/src/models/bookingrevenuerecord.js +84 -84
- package/src/models/budget.js +95 -95
- package/src/models/budgetCategory.js +19 -19
- package/src/models/campaigns.js +94 -94
- package/src/models/cashpayment.js +264 -264
- package/src/models/combinedUnits.js +62 -62
- package/src/models/common_area_electricity.js +38 -38
- package/src/models/common_area_generator.js +41 -41
- package/src/models/common_area_utility_alert.js +37 -37
- package/src/models/common_area_water.js +39 -39
- package/src/models/communication_status.js +33 -33
- package/src/models/company.js +53 -53
- package/src/models/coreBaseSettings.js +16 -16
- package/src/models/coreInvoiceSettings.js +100 -100
- package/src/models/country_tax.js +42 -42
- package/src/models/currency_settings.js +39 -39
- package/src/models/customer.js +208 -208
- package/src/models/dailyChecklist.js +312 -312
- package/src/models/default_payment_details.js +17 -17
- package/src/models/deliveryTimeMarks.js +18 -18
- package/src/models/dutyRosterChecklist.js +250 -250
- package/src/models/dutyroster.js +136 -136
- package/src/models/email.js +37 -37
- package/src/models/email_sms_queue.js +61 -61
- package/src/models/entry_exit.js +53 -53
- package/src/models/expense.js +99 -99
- package/src/models/expense_category.js +45 -45
- package/src/models/facility.js +62 -62
- package/src/models/facilityContractPricing.js +51 -0
- package/src/models/facilityInvoice.js +222 -223
- package/src/models/facilityInvoiceContract.js +268 -0
- package/src/models/facilityWalletTransactionsMetadata.js +236 -236
- package/src/models/facility_departements.js +20 -20
- package/src/models/facility_payment_details.js +20 -20
- package/src/models/facilityasset.js +25 -25
- package/src/models/faq.js +18 -18
- package/src/models/gl_account_double_entries.js +25 -25
- package/src/models/gl_accounts.js +56 -56
- package/src/models/gl_entries.js +49 -49
- package/src/models/goodsReceivedNotes.js +115 -115
- package/src/models/guard.js +47 -47
- package/src/models/handover.js +246 -246
- package/src/models/invoice.js +336 -336
- package/src/models/item_inspection.js +67 -67
- package/src/models/leaseagreement.js +226 -226
- package/src/models/leasetemplate.js +17 -17
- package/src/models/levy.js +206 -206
- package/src/models/levy_invoice_settings.js +26 -26
- package/src/models/levycontract.js +168 -168
- package/src/models/levytype.js +23 -23
- package/src/models/maintenance_service_vendor.js +38 -38
- package/src/models/maintenance_services.js +17 -17
- package/src/models/maintenancerequisition.js +31 -31
- package/src/models/master_workplan.js +32 -32
- package/src/models/master_workplan_child.js +34 -34
- package/src/models/message.js +38 -38
- package/src/models/module.js +21 -21
- package/src/models/notification.js +44 -44
- package/src/models/paymentTermsMarks.js +19 -19
- package/src/models/penalty.js +76 -76
- package/src/models/pendingCredentials.js +32 -32
- package/src/models/powerMeterCommunicationProtocol.js +17 -17
- package/src/models/powerMeterCustomerAccount.js +78 -78
- package/src/models/powerMeterCustomerBand.js +14 -14
- package/src/models/powerMeterDailyReading.js +30 -30
- package/src/models/powerMeterGateways.js +37 -37
- package/src/models/powerMeterMonthlyReading.js +34 -34
- package/src/models/powerMeterPowerCharges.js +53 -53
- package/src/models/powerMeterSettings.js +138 -138
- package/src/models/powerMeterSingleDayReading.js +32 -32
- package/src/models/powerMeters.js +104 -104
- package/src/models/powerMetersManufacturer.js +14 -14
- package/src/models/power_meter_account.js +81 -81
- package/src/models/power_meter_command_logs.js +30 -30
- package/src/models/power_meter_command_queue.js +33 -0
- package/src/models/power_meter_negative_balance.js +44 -0
- package/src/models/power_prepaid_credits.js +47 -0
- package/src/models/power_prepaid_debits.js +50 -0
- package/src/models/propertyManagerContract.js +556 -556
- package/src/models/propertyManagerRevenue.js +195 -195
- package/src/models/purchaseOrderInvoice.js +74 -74
- package/src/models/purchase_order.js +213 -213
- package/src/models/purchase_request.js +110 -110
- package/src/models/refresh_token.js +23 -23
- package/src/models/reminder.js +197 -197
- package/src/models/report.js +13 -13
- package/src/models/resident.js +121 -121
- package/src/models/rfq_details.js +131 -131
- package/src/models/rfq_response.js +153 -153
- package/src/models/service_charge_invoice_upload.js +42 -42
- package/src/models/service_charge_payments.js +27 -27
- package/src/models/servicerequest.js +55 -55
- package/src/models/settings.js +62 -62
- package/src/models/smart_meter_daily_consumption.js +44 -44
- package/src/models/sms_africastalking.js +20 -20
- package/src/models/sms_balance_notification.js +26 -26
- package/src/models/sms_meliora.js +20 -20
- package/src/models/staff.js +36 -36
- package/src/models/stocksandspare.js +161 -161
- package/src/models/suppliers.js +74 -74
- package/src/models/tickets.js +121 -121
- package/src/models/unitManagementTemplate.js +44 -44
- package/src/models/unitasset.js +25 -25
- package/src/models/units.js +117 -117
- package/src/models/user.js +186 -186
- package/src/models/valueaddedservices.js +79 -79
- package/src/models/vas_invoices_upload.js +50 -50
- package/src/models/vas_payments.js +24 -24
- package/src/models/vasinvoice.js +192 -192
- package/src/models/vasvendor.js +57 -57
- package/src/models/visitLog.js +95 -95
- package/src/models/visitor.js +67 -67
- package/src/models/waitlist.js +45 -45
- package/src/models/wallet.js +44 -44
- package/src/models/wallet_transactions.js +50 -50
- package/src/models/water_invoice.js +305 -305
- package/src/models/water_meter_Command_Queue.js +33 -33
- package/src/models/water_meter_account.js +82 -82
- package/src/models/water_meter_billing.js +58 -58
- package/src/models/water_meter_communication.js +17 -17
- package/src/models/water_meter_communication_logs.js +40 -30
- package/src/models/water_meter_concentrator.js +66 -66
- package/src/models/water_meter_daily_history.js +32 -32
- package/src/models/water_meter_high_risk.js +37 -0
- package/src/models/water_meter_iot_cards.js +34 -34
- package/src/models/water_meter_manufacturer.js +35 -35
- package/src/models/water_meter_monthly_history.js +36 -36
- package/src/models/water_meter_negative_amounts.js +44 -39
- package/src/models/water_meter_settings.js +227 -227
- package/src/models/water_meter_single_day_history.js +34 -34
- package/src/models/water_meter_size.js +15 -15
- package/src/models/water_meters.js +134 -134
- package/src/models/water_meters_delivery.js +76 -76
- package/src/models/water_prepaid_credit.js +47 -47
- package/src/models/water_prepaid_debit.js +50 -50
- package/src/models/workorder.js +49 -49
- package/.idea/material_theme_project_new.xml +0 -12
- package/.idea/modules.xml +0 -8
- package/.idea/psdb.iml +0 -12
- package/.idea/vcs.xml +0 -6
- package/src/models/facilityInvoiceRecipient.js +0 -34
- package/src/models/power_prepaid_credts.js +0 -0
package/src/models/invoice.js
CHANGED
|
@@ -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;
|