payservedb 8.7.0 → 8.7.2
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/ZOHO_INTEGRATION_SCHEMA.md +644 -644
- package/index.js +319 -319
- package/package.json +17 -17
- package/src/models/account.js +52 -52
- package/src/models/agent_departments.js +59 -59
- package/src/models/agent_notifications.js +53 -53
- package/src/models/agent_performance.js +127 -127
- package/src/models/agent_roles.js +77 -77
- package/src/models/agents.js +154 -154
- 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 +47 -47
- package/src/models/billerAddress.js +124 -124
- package/src/models/booking_invoice.js +165 -165
- package/src/models/bookinganalytics.js +63 -63
- package/src/models/bookingconfig.js +45 -45
- package/src/models/bookingproperty.js +179 -179
- package/src/models/bookingreservation.js +239 -239
- 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 +108 -108
- package/src/models/cashpayment.js +291 -291
- package/src/models/combinedUnits.js +62 -62
- package/src/models/combined_invoice.js +424 -424
- 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/communication_user_opt.js +32 -32
- package/src/models/community_guidelines.js +35 -35
- package/src/models/company.js +53 -53
- package/src/models/coreBaseSettings.js +16 -16
- package/src/models/coreInvoiceSettings.js +100 -100
- package/src/models/counter_schema.js +21 -21
- package/src/models/country_tax.js +42 -42
- package/src/models/currency_settings.js +39 -39
- package/src/models/customer.js +214 -214
- package/src/models/customer_satisfaction_survey.js +278 -278
- package/src/models/customer_surveys.js +139 -139
- package/src/models/customer_tickets.js +239 -239
- 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/document_type.js +19 -19
- 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 +71 -71
- package/src/models/facilityBillingPrices.js +29 -29
- package/src/models/facilityInvoice.js +240 -240
- package/src/models/facilityInvoicePayment.js +52 -52
- package/src/models/facilityInvoiceRecipient.js +32 -32
- 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/facility_rating.js +78 -78
- package/src/models/facilityasset.js +25 -25
- package/src/models/faq.js +14 -14
- 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 +258 -258
- package/src/models/inspection_category.js +38 -38
- package/src/models/invoice.js +480 -480
- package/src/models/invoice_generation_approval.js +86 -86
- package/src/models/invoicing_schedule.js +40 -40
- package/src/models/item_inspection.js +96 -96
- package/src/models/knowledge_base.js +109 -109
- package/src/models/knowledge_base_rating.js +44 -44
- package/src/models/leaseagreement.js +237 -237
- package/src/models/leasetemplate.js +17 -17
- package/src/models/levy.js +223 -223
- package/src/models/levy_invoice_settings.js +26 -26
- package/src/models/levycontract.js +210 -210
- 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 +40 -40
- package/src/models/powerMeterMonthlyReading.js +34 -34
- package/src/models/powerMeterPowerCharges.js +85 -85
- package/src/models/powerMeterSettings.js +159 -159
- package/src/models/powerMeterSingleDayReading.js +32 -32
- package/src/models/powerMeters.js +116 -116
- 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 -33
- package/src/models/power_meter_negative_balance.js +44 -44
- package/src/models/power_prepaid_credits.js +47 -47
- package/src/models/power_prepaid_debits.js +53 -53
- package/src/models/power_prepaid_orders.js +78 -78
- package/src/models/power_sms_notification.js +26 -26
- package/src/models/privacy_policy.js +19 -19
- 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/quickbooks_config.js +52 -41
- 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/short_urls.js +21 -21
- 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/terms_and_conditions.js +19 -19
- package/src/models/tickets.js +186 -186
- package/src/models/tickets_category.js +72 -72
- package/src/models/unitManagementTemplate.js +44 -44
- package/src/models/unitasset.js +25 -25
- package/src/models/units.js +118 -118
- package/src/models/user.js +185 -185
- package/src/models/valueaddedservices.js +50 -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 +351 -351
- package/src/models/water_meter_Command_Queue.js +33 -33
- package/src/models/water_meter_account.js +86 -86
- package/src/models/water_meter_billing.js +58 -58
- package/src/models/water_meter_combined_accounts.js +92 -92
- package/src/models/water_meter_communication.js +17 -17
- package/src/models/water_meter_communication_logs.js +39 -39
- package/src/models/water_meter_concentrator.js +70 -70
- package/src/models/water_meter_daily_history.js +32 -32
- package/src/models/water_meter_high_risk.js +36 -36
- package/src/models/water_meter_iot_cards.js +34 -34
- package/src/models/water_meter_loan_deduction.js +134 -134
- 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 -44
- package/src/models/water_meter_settings.js +290 -290
- 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 +133 -133
- 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/src/models/zohoAccount.js +453 -453
- package/src/models/zohoIntegration.js +262 -262
- package/src/models/zohoItem.js +504 -504
|
@@ -1,425 +1,425 @@
|
|
|
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: ["levy", "lease", "water", "service", "utility", "contract"],
|
|
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
|
-
// Combined invoice number
|
|
257
|
-
combinedInvoiceNumber: {
|
|
258
|
-
type: String,
|
|
259
|
-
unique: true,
|
|
260
|
-
sparse: true,
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
// Account number for combined invoice
|
|
264
|
-
accountNumber: {
|
|
265
|
-
type: String,
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
// Payment method information (copied from first invoice's lease/levy)
|
|
269
|
-
paymentMethod: {
|
|
270
|
-
type: {
|
|
271
|
-
type: String,
|
|
272
|
-
enum: ["Bank Transfer", "Mobile Money", "M-Pesa", "Cash", "Cheque", "Other"],
|
|
273
|
-
},
|
|
274
|
-
bankDetailsId: {
|
|
275
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
276
|
-
ref: "BankDetails",
|
|
277
|
-
},
|
|
278
|
-
mobileMoneyDetails: {
|
|
279
|
-
businessNumber: String,
|
|
280
|
-
shortCode: String,
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
// Biller address reference (copied from first invoice's lease/levy)
|
|
285
|
-
billerAddressId: {
|
|
286
|
-
type: mongoose.Schema.Types.ObjectId,
|
|
287
|
-
ref: "BillerAddress",
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
timestamps: true,
|
|
292
|
-
}
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
// Indexes for efficient querying
|
|
296
|
-
combinedInvoiceSchema.index({ "customer.customerId": 1, period: 1 });
|
|
297
|
-
combinedInvoiceSchema.index({ "unit.id": 1, period: 1 });
|
|
298
|
-
combinedInvoiceSchema.index({ "facility.id": 1, period: 1 });
|
|
299
|
-
combinedInvoiceSchema.index({ status: 1 });
|
|
300
|
-
combinedInvoiceSchema.index({ period: 1 });
|
|
301
|
-
combinedInvoiceSchema.index({ issueDate: -1 });
|
|
302
|
-
combinedInvoiceSchema.index({ "customer.customerId": 1, status: 1 });
|
|
303
|
-
combinedInvoiceSchema.index({ "invoices.invoiceId": 1 });
|
|
304
|
-
combinedInvoiceSchema.index({ "currency.code": 1 });
|
|
305
|
-
|
|
306
|
-
// Compound index for unique combined invoice per customer/unit/facility/period
|
|
307
|
-
combinedInvoiceSchema.index(
|
|
308
|
-
{
|
|
309
|
-
"customer.customerId": 1,
|
|
310
|
-
"unit.id": 1,
|
|
311
|
-
"facility.id": 1,
|
|
312
|
-
period: 1
|
|
313
|
-
},
|
|
314
|
-
{ unique: true }
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
// Virtual field for calculating total balance
|
|
318
|
-
combinedInvoiceSchema.virtual("calculatedBalance").get(function () {
|
|
319
|
-
return this.totalAmount - (this.amountPaid || 0) + (this.totalBalanceBroughtForward || 0);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Virtual field for invoice count
|
|
323
|
-
combinedInvoiceSchema.virtual("invoiceCount").get(function () {
|
|
324
|
-
return this.invoices ? this.invoices.length : 0;
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// Static method to find combined invoice by period and customer
|
|
328
|
-
combinedInvoiceSchema.statics.findByCustomerAndPeriod = function (customerId, period) {
|
|
329
|
-
return this.findOne({
|
|
330
|
-
"customer.customerId": customerId,
|
|
331
|
-
period: period,
|
|
332
|
-
});
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
// Static method to find combined invoices by facility and period
|
|
336
|
-
combinedInvoiceSchema.statics.findByFacilityAndPeriod = function (facilityId, period) {
|
|
337
|
-
return this.find({
|
|
338
|
-
"facility.id": facilityId,
|
|
339
|
-
period: period,
|
|
340
|
-
});
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
// Static method to find all combined invoices for a customer
|
|
344
|
-
combinedInvoiceSchema.statics.findByCustomer = function (customerId) {
|
|
345
|
-
return this.find({
|
|
346
|
-
"customer.customerId": customerId,
|
|
347
|
-
}).sort({ period: -1 });
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
// Static method to find pending notifications
|
|
351
|
-
combinedInvoiceSchema.statics.findPendingNotifications = function (facilityId) {
|
|
352
|
-
return this.find({
|
|
353
|
-
"facility.id": facilityId,
|
|
354
|
-
status: { $in: ["Unpaid", "Overdue"] },
|
|
355
|
-
$or: [
|
|
356
|
-
{ "notificationsSent.sms": false },
|
|
357
|
-
{ "notificationsSent.email": false },
|
|
358
|
-
],
|
|
359
|
-
});
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
// Method to update status based on individual invoices
|
|
363
|
-
combinedInvoiceSchema.methods.updateOverallStatus = function () {
|
|
364
|
-
if (!this.invoices || this.invoices.length === 0) {
|
|
365
|
-
this.status = "Unpaid";
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const statuses = this.invoices.map(inv => inv.status);
|
|
370
|
-
|
|
371
|
-
// If all paid, mark as paid
|
|
372
|
-
if (statuses.every(s => s === "Paid")) {
|
|
373
|
-
this.status = "Paid";
|
|
374
|
-
}
|
|
375
|
-
// If any overdue, mark as overdue
|
|
376
|
-
else if (statuses.some(s => s === "Overdue")) {
|
|
377
|
-
this.status = "Overdue";
|
|
378
|
-
}
|
|
379
|
-
// If any partially paid, mark as partially paid
|
|
380
|
-
else if (statuses.some(s => s === "Partially Paid")) {
|
|
381
|
-
this.status = "Partially Paid";
|
|
382
|
-
}
|
|
383
|
-
// If all cancelled, mark as cancelled
|
|
384
|
-
else if (statuses.every(s => s === "Cancelled")) {
|
|
385
|
-
this.status = "Cancelled";
|
|
386
|
-
}
|
|
387
|
-
// If all void, mark as void
|
|
388
|
-
else if (statuses.every(s => s === "Void")) {
|
|
389
|
-
this.status = "Void";
|
|
390
|
-
}
|
|
391
|
-
// Otherwise unpaid
|
|
392
|
-
else {
|
|
393
|
-
this.status = "Unpaid";
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
// Method to recalculate totals from individual invoices
|
|
398
|
-
combinedInvoiceSchema.methods.recalculateTotals = function () {
|
|
399
|
-
if (!this.invoices || this.invoices.length === 0) {
|
|
400
|
-
this.totalAmount = 0;
|
|
401
|
-
this.totalTax = 0;
|
|
402
|
-
this.amountPaid = 0;
|
|
403
|
-
this.totalBalanceBroughtForward = 0;
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
this.totalAmount = this.invoices.reduce((sum, inv) => sum + (inv.totalAmount || 0), 0);
|
|
408
|
-
this.totalTax = this.invoices.reduce((sum, inv) => sum + (inv.tax || 0), 0);
|
|
409
|
-
this.amountPaid = this.invoices.reduce((sum, inv) => sum + (inv.amountPaid || 0), 0);
|
|
410
|
-
this.totalBalanceBroughtForward = this.invoices.reduce(
|
|
411
|
-
(sum, inv) => sum + (inv.balanceBroughtForward || 0),
|
|
412
|
-
0
|
|
413
|
-
);
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
// Pre-save middleware to auto-calculate totals and status
|
|
417
|
-
combinedInvoiceSchema.pre("save", function (next) {
|
|
418
|
-
this.recalculateTotals();
|
|
419
|
-
this.updateOverallStatus();
|
|
420
|
-
next();
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
const CombinedInvoice = mongoose.model("CombinedInvoice", combinedInvoiceSchema);
|
|
424
|
-
|
|
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: ["levy", "lease", "water", "service", "utility", "contract"],
|
|
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
|
+
// Combined invoice number
|
|
257
|
+
combinedInvoiceNumber: {
|
|
258
|
+
type: String,
|
|
259
|
+
unique: true,
|
|
260
|
+
sparse: true,
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
// Account number for combined invoice
|
|
264
|
+
accountNumber: {
|
|
265
|
+
type: String,
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
// Payment method information (copied from first invoice's lease/levy)
|
|
269
|
+
paymentMethod: {
|
|
270
|
+
type: {
|
|
271
|
+
type: String,
|
|
272
|
+
enum: ["Bank Transfer", "Mobile Money", "M-Pesa", "Cash", "Cheque", "Other"],
|
|
273
|
+
},
|
|
274
|
+
bankDetailsId: {
|
|
275
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
276
|
+
ref: "BankDetails",
|
|
277
|
+
},
|
|
278
|
+
mobileMoneyDetails: {
|
|
279
|
+
businessNumber: String,
|
|
280
|
+
shortCode: String,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// Biller address reference (copied from first invoice's lease/levy)
|
|
285
|
+
billerAddressId: {
|
|
286
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
287
|
+
ref: "BillerAddress",
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
timestamps: true,
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Indexes for efficient querying
|
|
296
|
+
combinedInvoiceSchema.index({ "customer.customerId": 1, period: 1 });
|
|
297
|
+
combinedInvoiceSchema.index({ "unit.id": 1, period: 1 });
|
|
298
|
+
combinedInvoiceSchema.index({ "facility.id": 1, period: 1 });
|
|
299
|
+
combinedInvoiceSchema.index({ status: 1 });
|
|
300
|
+
combinedInvoiceSchema.index({ period: 1 });
|
|
301
|
+
combinedInvoiceSchema.index({ issueDate: -1 });
|
|
302
|
+
combinedInvoiceSchema.index({ "customer.customerId": 1, status: 1 });
|
|
303
|
+
combinedInvoiceSchema.index({ "invoices.invoiceId": 1 });
|
|
304
|
+
combinedInvoiceSchema.index({ "currency.code": 1 });
|
|
305
|
+
|
|
306
|
+
// Compound index for unique combined invoice per customer/unit/facility/period
|
|
307
|
+
combinedInvoiceSchema.index(
|
|
308
|
+
{
|
|
309
|
+
"customer.customerId": 1,
|
|
310
|
+
"unit.id": 1,
|
|
311
|
+
"facility.id": 1,
|
|
312
|
+
period: 1
|
|
313
|
+
},
|
|
314
|
+
{ unique: true }
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Virtual field for calculating total balance
|
|
318
|
+
combinedInvoiceSchema.virtual("calculatedBalance").get(function () {
|
|
319
|
+
return this.totalAmount - (this.amountPaid || 0) + (this.totalBalanceBroughtForward || 0);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Virtual field for invoice count
|
|
323
|
+
combinedInvoiceSchema.virtual("invoiceCount").get(function () {
|
|
324
|
+
return this.invoices ? this.invoices.length : 0;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Static method to find combined invoice by period and customer
|
|
328
|
+
combinedInvoiceSchema.statics.findByCustomerAndPeriod = function (customerId, period) {
|
|
329
|
+
return this.findOne({
|
|
330
|
+
"customer.customerId": customerId,
|
|
331
|
+
period: period,
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Static method to find combined invoices by facility and period
|
|
336
|
+
combinedInvoiceSchema.statics.findByFacilityAndPeriod = function (facilityId, period) {
|
|
337
|
+
return this.find({
|
|
338
|
+
"facility.id": facilityId,
|
|
339
|
+
period: period,
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Static method to find all combined invoices for a customer
|
|
344
|
+
combinedInvoiceSchema.statics.findByCustomer = function (customerId) {
|
|
345
|
+
return this.find({
|
|
346
|
+
"customer.customerId": customerId,
|
|
347
|
+
}).sort({ period: -1 });
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
// Static method to find pending notifications
|
|
351
|
+
combinedInvoiceSchema.statics.findPendingNotifications = function (facilityId) {
|
|
352
|
+
return this.find({
|
|
353
|
+
"facility.id": facilityId,
|
|
354
|
+
status: { $in: ["Unpaid", "Overdue"] },
|
|
355
|
+
$or: [
|
|
356
|
+
{ "notificationsSent.sms": false },
|
|
357
|
+
{ "notificationsSent.email": false },
|
|
358
|
+
],
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// Method to update status based on individual invoices
|
|
363
|
+
combinedInvoiceSchema.methods.updateOverallStatus = function () {
|
|
364
|
+
if (!this.invoices || this.invoices.length === 0) {
|
|
365
|
+
this.status = "Unpaid";
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const statuses = this.invoices.map(inv => inv.status);
|
|
370
|
+
|
|
371
|
+
// If all paid, mark as paid
|
|
372
|
+
if (statuses.every(s => s === "Paid")) {
|
|
373
|
+
this.status = "Paid";
|
|
374
|
+
}
|
|
375
|
+
// If any overdue, mark as overdue
|
|
376
|
+
else if (statuses.some(s => s === "Overdue")) {
|
|
377
|
+
this.status = "Overdue";
|
|
378
|
+
}
|
|
379
|
+
// If any partially paid, mark as partially paid
|
|
380
|
+
else if (statuses.some(s => s === "Partially Paid")) {
|
|
381
|
+
this.status = "Partially Paid";
|
|
382
|
+
}
|
|
383
|
+
// If all cancelled, mark as cancelled
|
|
384
|
+
else if (statuses.every(s => s === "Cancelled")) {
|
|
385
|
+
this.status = "Cancelled";
|
|
386
|
+
}
|
|
387
|
+
// If all void, mark as void
|
|
388
|
+
else if (statuses.every(s => s === "Void")) {
|
|
389
|
+
this.status = "Void";
|
|
390
|
+
}
|
|
391
|
+
// Otherwise unpaid
|
|
392
|
+
else {
|
|
393
|
+
this.status = "Unpaid";
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// Method to recalculate totals from individual invoices
|
|
398
|
+
combinedInvoiceSchema.methods.recalculateTotals = function () {
|
|
399
|
+
if (!this.invoices || this.invoices.length === 0) {
|
|
400
|
+
this.totalAmount = 0;
|
|
401
|
+
this.totalTax = 0;
|
|
402
|
+
this.amountPaid = 0;
|
|
403
|
+
this.totalBalanceBroughtForward = 0;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
this.totalAmount = this.invoices.reduce((sum, inv) => sum + (inv.totalAmount || 0), 0);
|
|
408
|
+
this.totalTax = this.invoices.reduce((sum, inv) => sum + (inv.tax || 0), 0);
|
|
409
|
+
this.amountPaid = this.invoices.reduce((sum, inv) => sum + (inv.amountPaid || 0), 0);
|
|
410
|
+
this.totalBalanceBroughtForward = this.invoices.reduce(
|
|
411
|
+
(sum, inv) => sum + (inv.balanceBroughtForward || 0),
|
|
412
|
+
0
|
|
413
|
+
);
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// Pre-save middleware to auto-calculate totals and status
|
|
417
|
+
combinedInvoiceSchema.pre("save", function (next) {
|
|
418
|
+
this.recalculateTotals();
|
|
419
|
+
this.updateOverallStatus();
|
|
420
|
+
next();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const CombinedInvoice = mongoose.model("CombinedInvoice", combinedInvoiceSchema);
|
|
424
|
+
|
|
425
425
|
module.exports = CombinedInvoice;
|