payservedb 9.5.1 → 9.5.3

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 (232) hide show
  1. package/.env +2 -2
  2. package/ZOHO_INTEGRATION_SCHEMA.md +644 -644
  3. package/index.js +418 -417
  4. package/package.json +17 -17
  5. package/src/models/InvoiceWithholdingTax.js +67 -67
  6. package/src/models/account.js +59 -59
  7. package/src/models/agent_departments.js +59 -59
  8. package/src/models/agent_notifications.js +53 -53
  9. package/src/models/agent_performance.js +127 -127
  10. package/src/models/agent_roles.js +77 -77
  11. package/src/models/agents.js +154 -154
  12. package/src/models/apilog.js +18 -18
  13. package/src/models/approvalsWorkflows.js +49 -49
  14. package/src/models/archivedapilog.js +18 -18
  15. package/src/models/asset.js +92 -92
  16. package/src/models/assetsAssignment.js +64 -64
  17. package/src/models/auditTrail.js +346 -346
  18. package/src/models/auto_reply_rule.js +68 -68
  19. package/src/models/bankdetails.js +47 -47
  20. package/src/models/billerAddress.js +124 -124
  21. package/src/models/booking_invoice.js +187 -187
  22. package/src/models/bookinganalytics.js +63 -63
  23. package/src/models/bookingconfig.js +67 -67
  24. package/src/models/bookingproperty.js +187 -187
  25. package/src/models/bookingreservation.js +266 -266
  26. package/src/models/bookingrevenuerecord.js +84 -84
  27. package/src/models/budget.js +95 -95
  28. package/src/models/budgetCategory.js +19 -19
  29. package/src/models/campaigns.js +108 -108
  30. package/src/models/cancellation_policy.js +56 -56
  31. package/src/models/cashpayment.js +289 -289
  32. package/src/models/combinedUnits.js +62 -62
  33. package/src/models/combined_invoice.js +424 -424
  34. package/src/models/common_area_electricity.js +38 -38
  35. package/src/models/common_area_generator.js +41 -41
  36. package/src/models/common_area_utility_alert.js +37 -37
  37. package/src/models/common_area_water.js +39 -39
  38. package/src/models/communication_status.js +33 -33
  39. package/src/models/communication_user_opt.js +32 -32
  40. package/src/models/community_guidelines.js +35 -35
  41. package/src/models/company.js +53 -53
  42. package/src/models/coreBaseSettings.js +16 -16
  43. package/src/models/coreInvoiceSettings.js +100 -100
  44. package/src/models/counter_schema.js +21 -21
  45. package/src/models/country_tax.js +42 -42
  46. package/src/models/currency_settings.js +39 -39
  47. package/src/models/customer.js +251 -251
  48. package/src/models/customer_preference.js +52 -52
  49. package/src/models/customer_satisfaction_survey.js +297 -297
  50. package/src/models/customer_surveys.js +139 -139
  51. package/src/models/customer_tickets.js +237 -237
  52. package/src/models/dailyChecklist.js +312 -312
  53. package/src/models/default_payment_details.js +17 -17
  54. package/src/models/deliveryTimeMarks.js +18 -18
  55. package/src/models/document_type.js +19 -19
  56. package/src/models/dutyRosterChecklist.js +250 -250
  57. package/src/models/dutyroster.js +136 -136
  58. package/src/models/email.js +37 -37
  59. package/src/models/email_cc_config.js +48 -48
  60. package/src/models/email_sms_queue.js +61 -61
  61. package/src/models/email_thread.js +35 -35
  62. package/src/models/entry_exit.js +53 -53
  63. package/src/models/expense.js +99 -99
  64. package/src/models/expense_category.js +45 -45
  65. package/src/models/facility.js +100 -76
  66. package/src/models/facilityBillingPrices.js +29 -29
  67. package/src/models/facilityInvoice.js +240 -240
  68. package/src/models/facilityInvoicePayment.js +52 -52
  69. package/src/models/facilityInvoiceRecipient.js +32 -32
  70. package/src/models/facilityWalletTransactionsMetadata.js +236 -236
  71. package/src/models/facility_departements.js +20 -20
  72. package/src/models/facility_etims_config.js +116 -116
  73. package/src/models/facility_payment_details.js +20 -20
  74. package/src/models/facility_rating.js +78 -78
  75. package/src/models/facilityasset.js +25 -25
  76. package/src/models/faq.js +15 -15
  77. package/src/models/gl_account_double_entries.js +25 -25
  78. package/src/models/gl_accounts.js +56 -56
  79. package/src/models/gl_entries.js +49 -49
  80. package/src/models/goodsReceivedNotes.js +115 -115
  81. package/src/models/guard.js +47 -47
  82. package/src/models/handover.js +258 -258
  83. package/src/models/inspection_category.js +38 -38
  84. package/src/models/invoice.js +569 -569
  85. package/src/models/invoiceCreditAdjustment.js +45 -45
  86. package/src/models/invoice_edit_log.js +81 -81
  87. package/src/models/invoice_generation_approval.js +86 -86
  88. package/src/models/invoicing_schedule.js +40 -40
  89. package/src/models/item_inspection.js +96 -96
  90. package/src/models/knowledge_base.js +109 -109
  91. package/src/models/knowledge_base_rating.js +44 -44
  92. package/src/models/leaseagreement.js +266 -266
  93. package/src/models/leasetemplate.js +17 -17
  94. package/src/models/levy.js +231 -231
  95. package/src/models/levy_deposits.js +139 -139
  96. package/src/models/levy_invoice_settings.js +26 -26
  97. package/src/models/levy_payment_receipt.js +156 -156
  98. package/src/models/levycontract.js +213 -213
  99. package/src/models/levytype.js +23 -23
  100. package/src/models/maintenance_service_vendor.js +38 -38
  101. package/src/models/maintenance_services.js +17 -17
  102. package/src/models/maintenancerequisition.js +31 -31
  103. package/src/models/master_workplan.js +32 -32
  104. package/src/models/master_workplan_child.js +34 -34
  105. package/src/models/message.js +38 -38
  106. package/src/models/module.js +21 -21
  107. package/src/models/movein_amenity.js +13 -13
  108. package/src/models/movein_application.js +28 -28
  109. package/src/models/movein_audit_log.js +21 -21
  110. package/src/models/movein_booking.js +33 -33
  111. package/src/models/movein_commission.js +46 -46
  112. package/src/models/movein_conversation.js +25 -25
  113. package/src/models/movein_deal.js +79 -79
  114. package/src/models/movein_disbursement.js +36 -36
  115. package/src/models/movein_email_thread.js +19 -19
  116. package/src/models/movein_enquiry.js +16 -16
  117. package/src/models/movein_featured_package.js +16 -16
  118. package/src/models/movein_handoff_token.js +16 -16
  119. package/src/models/movein_invoice.js +24 -24
  120. package/src/models/movein_landlord.js +18 -18
  121. package/src/models/movein_landlord_user.js +20 -20
  122. package/src/models/movein_lease.js +37 -37
  123. package/src/models/movein_message.js +27 -27
  124. package/src/models/movein_notification.js +27 -27
  125. package/src/models/movein_otp.js +14 -14
  126. package/src/models/movein_payment.js +46 -46
  127. package/src/models/movein_reminder.js +77 -77
  128. package/src/models/movein_reservation.js +31 -31
  129. package/src/models/movein_settings.js +25 -25
  130. package/src/models/movein_tenancy.js +55 -55
  131. package/src/models/movein_unit.js +59 -59
  132. package/src/models/movein_user.js +15 -15
  133. package/src/models/movein_viewing_slot.js +21 -21
  134. package/src/models/movein_want_unit.js +20 -20
  135. package/src/models/notification.js +44 -44
  136. package/src/models/paymentTermsMarks.js +19 -19
  137. package/src/models/penalty.js +76 -76
  138. package/src/models/pendingCredentials.js +32 -32
  139. package/src/models/powerMeterCommunicationProtocol.js +17 -17
  140. package/src/models/powerMeterCustomerAccount.js +78 -78
  141. package/src/models/powerMeterCustomerBand.js +14 -14
  142. package/src/models/powerMeterDailyReading.js +30 -30
  143. package/src/models/powerMeterGateways.js +40 -40
  144. package/src/models/powerMeterMonthlyReading.js +34 -34
  145. package/src/models/powerMeterPowerCharges.js +85 -85
  146. package/src/models/powerMeterSettings.js +211 -211
  147. package/src/models/powerMeterSingleDayReading.js +32 -32
  148. package/src/models/powerMeters.js +149 -149
  149. package/src/models/powerMetersManufacturer.js +14 -14
  150. package/src/models/power_invoice.js +371 -371
  151. package/src/models/power_meter_account.js +81 -81
  152. package/src/models/power_meter_command_logs.js +30 -30
  153. package/src/models/power_meter_command_queue.js +33 -33
  154. package/src/models/power_meter_negative_balance.js +44 -44
  155. package/src/models/power_prepaid_credits.js +47 -47
  156. package/src/models/power_prepaid_debits.js +53 -53
  157. package/src/models/power_prepaid_orders.js +78 -78
  158. package/src/models/power_sms_notification.js +26 -26
  159. package/src/models/privacy_policy.js +19 -19
  160. package/src/models/propertyManagementSettings.js +55 -55
  161. package/src/models/propertyManagerContract.js +611 -611
  162. package/src/models/propertyManagerRevenue.js +195 -195
  163. package/src/models/propertyManagerRevenueEntry.js +118 -118
  164. package/src/models/purchaseOrderInvoice.js +74 -74
  165. package/src/models/purchase_order.js +213 -213
  166. package/src/models/purchase_request.js +110 -110
  167. package/src/models/quickbooks_config.js +52 -52
  168. package/src/models/recipient_group.js +61 -61
  169. package/src/models/recipient_group_member.js +62 -62
  170. package/src/models/refresh_token.js +23 -23
  171. package/src/models/reminder.js +197 -197
  172. package/src/models/report.js +13 -13
  173. package/src/models/resident.js +121 -121
  174. package/src/models/rfq_details.js +131 -131
  175. package/src/models/rfq_response.js +153 -153
  176. package/src/models/service_charge_invoice_upload.js +42 -42
  177. package/src/models/service_charge_payments.js +27 -27
  178. package/src/models/servicerequest.js +55 -55
  179. package/src/models/settings.js +62 -62
  180. package/src/models/short_urls.js +21 -21
  181. package/src/models/smart_meter_daily_consumption.js +44 -44
  182. package/src/models/sms_africastalking.js +20 -20
  183. package/src/models/sms_balance_notification.js +26 -26
  184. package/src/models/sms_meliora.js +20 -20
  185. package/src/models/staff.js +36 -36
  186. package/src/models/stocksandspare.js +161 -161
  187. package/src/models/suppliers.js +79 -79
  188. package/src/models/terms_and_conditions.js +19 -19
  189. package/src/models/tickets.js +186 -186
  190. package/src/models/tickets_category.js +72 -72
  191. package/src/models/unitManagementTemplate.js +44 -44
  192. package/src/models/unitasset.js +25 -25
  193. package/src/models/units.js +148 -148
  194. package/src/models/user.js +186 -186
  195. package/src/models/valueaddedservices.js +50 -50
  196. package/src/models/vas_invoices_upload.js +50 -50
  197. package/src/models/vas_payments.js +24 -24
  198. package/src/models/vasinvoice.js +196 -196
  199. package/src/models/vasvendor.js +52 -52
  200. package/src/models/visitLog.js +95 -95
  201. package/src/models/visitor.js +67 -67
  202. package/src/models/waitlist.js +45 -45
  203. package/src/models/wallet.js +44 -44
  204. package/src/models/wallet_transaction.js +67 -67
  205. package/src/models/wallet_transactions.js +72 -72
  206. package/src/models/water_invoice.js +362 -362
  207. package/src/models/water_meter_Command_Queue.js +33 -33
  208. package/src/models/water_meter_account.js +86 -86
  209. package/src/models/water_meter_billing.js +58 -58
  210. package/src/models/water_meter_combined_accounts.js +92 -92
  211. package/src/models/water_meter_communication.js +17 -17
  212. package/src/models/water_meter_communication_logs.js +39 -39
  213. package/src/models/water_meter_concentrator.js +77 -77
  214. package/src/models/water_meter_daily_history.js +32 -32
  215. package/src/models/water_meter_high_risk.js +36 -36
  216. package/src/models/water_meter_iot_cards.js +34 -34
  217. package/src/models/water_meter_loan_deduction.js +134 -134
  218. package/src/models/water_meter_manufacturer.js +35 -35
  219. package/src/models/water_meter_monthly_history.js +36 -36
  220. package/src/models/water_meter_negative_amounts.js +44 -44
  221. package/src/models/water_meter_settings.js +296 -296
  222. package/src/models/water_meter_single_day_history.js +34 -34
  223. package/src/models/water_meter_size.js +15 -15
  224. package/src/models/water_meters.js +176 -176
  225. package/src/models/water_meters_delivery.js +76 -76
  226. package/src/models/water_prepaid_credit.js +47 -47
  227. package/src/models/water_prepaid_debit.js +50 -50
  228. package/src/models/whatsapp_conversation.js +23 -23
  229. package/src/models/workorder.js +49 -49
  230. package/src/models/zohoAccount.js +453 -453
  231. package/src/models/zohoIntegration.js +262 -262
  232. package/src/models/zohoItem.js +504 -504
@@ -1,570 +1,570 @@
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
- },
14
- client: {
15
- clientId: {
16
- type: mongoose.Schema.Types.ObjectId,
17
- ref: "Customer",
18
- required: true,
19
- },
20
- firstName: {
21
- type: String,
22
- required: true,
23
- },
24
- lastName: {
25
- type: String,
26
- required: true,
27
- },
28
- kraPin: {
29
- type: String,
30
- default: null,
31
- },
32
- },
33
- facility: {
34
- id: {
35
- type: mongoose.Schema.Types.ObjectId,
36
- ref: "Facility",
37
- required: true,
38
- },
39
- name: {
40
- type: String,
41
- required: true,
42
- },
43
- },
44
- unit: {
45
- id: { type: mongoose.Schema.Types.ObjectId, ref: "Unit", required: true },
46
- name: { type: String, required: true },
47
- },
48
- // Per-unit fee breakdown for a single invoice covering multiple units
49
- // (e.g. a Property-Management contract billing several units at
50
- // different rates). Only set when the invoice spans more than one
51
- // unit - `unit` above still identifies the contract's primary unit for
52
- // backward compatibility with code that expects a single unit.
53
- unitBreakdown: [{
54
- unitId: { type: mongoose.Schema.Types.ObjectId, ref: "Unit" },
55
- amount: Number
56
- }],
57
- currency: {
58
- id: {
59
- type: mongoose.Schema.Types.ObjectId,
60
- ref: "Currency",
61
- required: true,
62
- },
63
- name: {
64
- type: String,
65
- required: true,
66
- },
67
- code: {
68
- type: String,
69
- required: true,
70
- uppercase: true,
71
- minlength: 3,
72
- maxlength: 3,
73
- },
74
- },
75
- items: [
76
- {
77
- description: { type: String, required: true },
78
- quantity: { type: Number, required: true, min: 1 },
79
- unitPrice: { type: Number, required: true, min: 0 },
80
- },
81
- ],
82
- subTotal: {
83
- type: Number,
84
- required: true,
85
- },
86
- tax: {
87
- type: Number,
88
- required: true,
89
- },
90
- taxes: [
91
- {
92
- name: { type: String, required: true }, // e.g. "VAT", "Withholding Tax"
93
- percentage: { type: Number, required: true }, // e.g. 16
94
- amount: { type: Number, required: true }, // e.g. 160.00 (subTotal * percentage / 100)
95
- },
96
- ],
97
- withholdingTaxes: [
98
- {
99
- name: { type: String, required: true }, // e.g. "withholding"
100
- percentage: { type: Number, required: true }, // e.g. 5
101
- amount: { type: Number, required: true }, // e.g. 50 (subTotal * percentage / 100)
102
- },
103
- ],
104
- totalWithholdingTax: {
105
- type: Number,
106
- default: 0,
107
- },
108
- totalAmount: {
109
- type: Number,
110
- required: true,
111
- },
112
- amountPaid: {
113
- type: Number,
114
- default: 0,
115
- min: 0,
116
- },
117
- // Mark as deprecated, keep for backward compatibility
118
- overpay: {
119
- type: Number,
120
- default: 0,
121
- min: 0,
122
- deprecated: true,
123
- },
124
- issueDate: {
125
- type: Date,
126
- required: true,
127
- },
128
- dueDate: {
129
- type: Date,
130
- required: true,
131
- },
132
- status: {
133
- type: String,
134
- required: true,
135
- enum: [
136
- "Unpaid",
137
- "Pending",
138
- "Paid",
139
- "Overdue",
140
- "Cancelled",
141
- "Partially Paid",
142
- "Void",
143
- ],
144
- },
145
- penalty: {
146
- type: Number,
147
- default: 0,
148
- },
149
- whatFor: {
150
- invoiceType: { type: String, required: true },
151
- description: { type: String },
152
- },
153
- invoiceNote: {
154
- type: String,
155
- default: null,
156
- },
157
- balanceBroughtForward: {
158
- type: Number,
159
- default: 0,
160
- },
161
- // NEW FIELD 1: Year-Month for easy filtering
162
- yearMonth: {
163
- type: String,
164
- match: /^\d{4}-\d{2}$/, // Validates format like "2025-08"
165
- index: true,
166
- },
167
- // NEW FIELD 2: Simple notification tracking
168
- notificationsSent: {
169
- sms: {
170
- type: Boolean,
171
- default: false,
172
- },
173
- email: {
174
- type: Boolean,
175
- default: false,
176
- },
177
- attempts: {
178
- type: Number,
179
- default: 0,
180
- },
181
- },
182
- voidMetadata: {
183
- voidedBy: {
184
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
185
- name: { type: String },
186
- role: { type: String },
187
- },
188
- voidedAt: { type: Date },
189
- reason: { type: String },
190
- },
191
- cancelMetadata: {
192
- cancelledBy: {
193
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
194
- name: { type: String },
195
- role: { type: String },
196
- },
197
- cancelledAt: { type: Date },
198
- reason: { type: String },
199
- },
200
- lastReminderSent: Date,
201
- reminderHistory: [
202
- {
203
- sentAt: Date,
204
- reminderId: mongoose.Schema.Types.ObjectId,
205
- notificationTypes: [String],
206
- },
207
- ],
208
- reconciliationHistory: [
209
- {
210
- date: { type: Date, required: true },
211
- paymentDate: {
212
- type: Date
213
- },
214
- amount: { type: Number, required: true },
215
- type: {
216
- type: String,
217
- enum: [
218
- "payment",
219
- "cash",
220
- "cheque",
221
- "bank-transfer",
222
- "mpesa-transfer",
223
- "overpay-transfer",
224
- "balance-deduction",
225
- "overpay-received",
226
- "credit-forward",
227
- "debit-forward",
228
- ],
229
- required: true,
230
- },
231
- sourceInvoice: String,
232
- destinationInvoice: String,
233
- paymentReference: String,
234
- paymentCompletion: String,
235
- remainingBalance: Number,
236
- notes: String,
237
- exchangeRate: {
238
- type: Number,
239
- default: 1, // For cross-currency reconciliations
240
- },
241
- originalCurrency: {
242
- code: String, // Original currency code if different from invoice currency
243
- amount: Number, // Amount in original currency
244
- },
245
- },
246
- ],
247
- paymentDetails: {
248
- paymentStatus: { type: String, required: true },
249
- paymentMethod: { type: String },
250
- paymentDate: { type: Date },
251
- transactionId: { type: String },
252
- },
253
- // New field to track when an invoice has been viewed
254
- viewStatus: {
255
- isOpened: { type: Boolean, default: false },
256
- openedAt: { type: Date, default: null },
257
- openedBy: {
258
- facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
259
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
260
- userRole: { type: String },
261
- },
262
- viewHistory: [
263
- {
264
- viewedAt: { type: Date, required: true },
265
- facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
266
- userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
267
- },
268
- ],
269
- },
270
- invoiceUrl: {
271
- type: String,
272
- default: null,
273
- },
274
- // eTims tax integration fields
275
- ticketUrl: {
276
- type: String,
277
- default: null,
278
- description: "QR code URL from eTims/KRA for tax invoice verification",
279
- },
280
- txsync: {
281
- status: {
282
- type: String,
283
- enum: ["pending", "synced", "failed", "not-applicable"],
284
- default: "not-applicable",
285
- },
286
- syncedAt: {
287
- type: Date,
288
- default: null,
289
- },
290
- etimsInvoiceNo: {
291
- type: String,
292
- default: null,
293
- },
294
- sdcId: {
295
- type: String,
296
- default: null,
297
- },
298
- receiptNo: {
299
- type: String,
300
- default: null,
301
- },
302
- errorMessage: {
303
- type: String,
304
- default: null,
305
- },
306
- attempts: {
307
- type: Number,
308
- default: 0,
309
- },
310
- lastAttemptAt: {
311
- type: Date,
312
- default: null,
313
- },
314
- },
315
- creditNote: {
316
- status: {
317
- type: String,
318
- enum: ["pending", "synced", "failed"],
319
- default: undefined, // absent until a void triggers one
320
- },
321
- creditNoteTraderNo: { type: String, default: null },
322
- etimsCreditNoteNo: { type: String, default: null },
323
- sdcId: { type: String, default: null },
324
- receiptNo: { type: String, default: null },
325
- verificationUrl: { type: String, default: null },
326
- syncedAt: { type: Date, default: null },
327
- lastAttemptAt: { type: Date, default: null },
328
- errorMessage: { type: String, default: null },
329
- attempts: { type: Number, default: 0 },
330
- },
331
- digitax: {
332
- synced: {
333
- type: Boolean,
334
- default: false
335
- },
336
- syncedAt: Date,
337
- status: {
338
- type: String,
339
- enum: [
340
- "PENDING",
341
- "PROCESSING",
342
- "SUCCESS",
343
- "FAILED"
344
- ],
345
- default: "PENDING"
346
- },
347
- saleId: String,
348
- traderInvoiceNumber: String,
349
- digitaxInvoiceNumber: Number,
350
- receiptNumber: Number,
351
- serialNumber: String,
352
- receiptTypeCode: String,
353
- offlineUrl: String,
354
- saleDetailUrl: String,
355
- etimsUrl: String,
356
- attempts: {
357
- type: Number,
358
- default: 0
359
- },
360
- lastAttemptAt: Date,
361
- lastError: String,
362
- response: mongoose.Schema.Types.Mixed
363
- },
364
- // New fields for double entry accounts
365
- invoiceDoubleEntryAccount: {
366
- type: mongoose.Schema.Types.ObjectId,
367
- ref: "GLAccountDoubleEntries",
368
- required: false,
369
- },
370
- paymentDoubleEntryAccount: {
371
- type: mongoose.Schema.Types.ObjectId,
372
- ref: "GLAccountDoubleEntries",
373
- required: false,
374
- },
375
- // GL account details for invoice entries
376
- accountdebitedData: {
377
- amount: { type: Number },
378
- description: { type: String },
379
- isActive: { type: Boolean, default: true },
380
- },
381
- accountcreditedData: {
382
- amount: { type: Number },
383
- description: { type: String },
384
- isActive: { type: Boolean, default: true },
385
- },
386
- },
387
- {
388
- timestamps: true,
389
- },
390
- );
391
-
392
- // Add indexes for frequently queried fields
393
- invoiceSchema.index({ accountNumber: 1 });
394
- invoiceSchema.index({ status: 1 });
395
- invoiceSchema.index({ "client.clientId": 1, status: 1 });
396
- invoiceSchema.index({ "reconciliationHistory.paymentReference": 1 });
397
- invoiceSchema.index({ issueDate: -1 });
398
- invoiceSchema.index({ "currency.code": 1 }); // Add index for currency code
399
- invoiceSchema.index({ "currency.id": 1 }); // Add index for currency ID
400
- invoiceSchema.index({ "currency.code": 1, "client.clientId": 1, status: 1 }); // Compound index for currency-based queries
401
- invoiceSchema.index({ "client.clientId": 1, balanceBroughtForward: 1 }); // Add index for finding invoices with credit balances
402
- invoiceSchema.index({ "viewStatus.isOpened": 1 }); // Add index for view status
403
- invoiceSchema.index({ "viewStatus.openedBy.facilityId": 1 }); // Add index for facility view tracking
404
- // Add index for double entry accounts
405
- invoiceSchema.index({ invoiceDoubleEntryAccount: 1 });
406
- invoiceSchema.index({ paymentDoubleEntryAccount: 1 });
407
- // NEW INDEXES for new fields
408
- invoiceSchema.index({ yearMonth: 1 }); // For filtering by year-month
409
- invoiceSchema.index({ yearMonth: 1, status: 1 }); // Combined index for monthly reports
410
- invoiceSchema.index({ "notificationsSent.sms": 1 }); // For finding SMS sent/not sent
411
- invoiceSchema.index({ "notificationsSent.email": 1 }); // For finding email sent/not sent
412
- // eTims tax sync indexes
413
- invoiceSchema.index({ "txsync.status": 1 }); // For finding invoices by sync status
414
- invoiceSchema.index({ "txsync.etimsInvoiceNo": 1 }); // For looking up by eTims invoice number
415
-
416
- // Add virtual field for calculating balance
417
- invoiceSchema.virtual("calculatedBalance").get(function () {
418
- const baseBalance = this.totalAmount - (this.amountPaid || 0);
419
-
420
- // Add positive balanceBroughtForward (customer owes money)
421
- if (this.balanceBroughtForward > 0) {
422
- return baseBalance + this.balanceBroughtForward;
423
- }
424
-
425
- // Subtract negative balanceBroughtForward (credit)
426
- return baseBalance;
427
- });
428
-
429
- // Add virtual field for credit balance
430
- invoiceSchema.virtual("creditBalance").get(function () {
431
- return this.balanceBroughtForward < 0
432
- ? Math.abs(this.balanceBroughtForward)
433
- : 0;
434
- });
435
-
436
- // Getter for compatible overpay field
437
- invoiceSchema.virtual("effectiveOverpay").get(function () {
438
- return this.balanceBroughtForward < 0
439
- ? Math.abs(this.balanceBroughtForward)
440
- : 0;
441
- });
442
-
443
- // Add virtual populate for invoice double entry account
444
- invoiceSchema.virtual("invoiceDoubleEntry", {
445
- ref: "GLAccountDoubleEntries",
446
- localField: "invoiceDoubleEntryAccount",
447
- foreignField: "_id",
448
- justOne: true,
449
- });
450
-
451
- // Add virtual populate for payment double entry account
452
- invoiceSchema.virtual("paymentDoubleEntry", {
453
- ref: "GLAccountDoubleEntries",
454
- localField: "paymentDoubleEntryAccount",
455
- foreignField: "_id",
456
- justOne: true,
457
- });
458
-
459
- // Add method for currency conversion if needed
460
- invoiceSchema.methods.convertAmount = function (
461
- amount,
462
- fromCurrency,
463
- toCurrency,
464
- exchangeRate,
465
- ) {
466
- if (fromCurrency === toCurrency) {
467
- return amount;
468
- }
469
- return amount * exchangeRate;
470
- };
471
-
472
- // Add static method to find invoices by currency
473
- invoiceSchema.statics.findByCurrency = function (currencyCode) {
474
- return this.find({ "currency.code": currencyCode.toUpperCase() });
475
- };
476
-
477
- // Add static method to find invoices with credit balance
478
- invoiceSchema.statics.findWithCreditBalance = function (clientId) {
479
- return this.find({
480
- "client.clientId": clientId,
481
- balanceBroughtForward: { $lt: 0 },
482
- }).sort({ updatedAt: -1 });
483
- };
484
-
485
- // Add static method to calculate totals by currency
486
- invoiceSchema.statics.calculateTotalsByCurrency = function (query = {}) {
487
- return this.aggregate([
488
- { $match: query },
489
- {
490
- $group: {
491
- _id: "$currency.code",
492
- totalAmount: { $sum: "$totalAmount" },
493
- totalPaid: { $sum: "$amountPaid" },
494
- count: { $sum: 1 },
495
- },
496
- },
497
- ]);
498
- };
499
-
500
- // New static method to find all unviewed invoices
501
- invoiceSchema.statics.findUnviewedInvoices = function (facilityId) {
502
- return this.find({
503
- "facility.id": facilityId,
504
- "viewStatus.isOpened": false,
505
- });
506
- };
507
-
508
- // NEW STATIC METHODS for new fields
509
- // Find invoices by year-month
510
- invoiceSchema.statics.findByYearMonth = function (yearMonth) {
511
- return this.find({ yearMonth: yearMonth });
512
- };
513
-
514
- // Find invoices where notifications haven't been sent
515
- invoiceSchema.statics.findPendingNotifications = function (facilityId) {
516
- return this.find({
517
- "facility.id": facilityId,
518
- status: { $in: ["Unpaid", "Overdue"] },
519
- $or: [
520
- { "notificationsSent.sms": false },
521
- { "notificationsSent.email": false },
522
- ],
523
- });
524
- };
525
-
526
- // Find invoices pending eTims sync
527
- invoiceSchema.statics.findPendingTaxSync = function (facilityId) {
528
- return this.find({
529
- "facility.id": facilityId,
530
- "txsync.status": "pending",
531
- });
532
- };
533
-
534
- // Find invoices with failed eTims sync
535
- invoiceSchema.statics.findFailedTaxSync = function (facilityId) {
536
- return this.find({
537
- "facility.id": facilityId,
538
- "txsync.status": "failed",
539
- });
540
- };
541
-
542
- // Pre-save middleware to ensure overpay and balanceBroughtForward stay in sync during transition
543
- invoiceSchema.pre("save", function (next) {
544
-
545
- if (this.taxes && this.taxes.length > 0) {
546
- this.tax = Math.round(
547
- this.taxes.reduce((sum, t) => sum + (t.amount || 0), 0) * 100
548
- ) / 100;
549
- }
550
-
551
- // If balanceBroughtForward is negative (credit), sync with overpay for backwards compatibility
552
- if (this.balanceBroughtForward < 0) {
553
- this.overpay = Math.abs(this.balanceBroughtForward);
554
- } else {
555
- this.overpay = 0; // No overpay if there's no negative balance
556
- }
557
-
558
- // NEW: Auto-generate yearMonth from issueDate if not provided
559
- if (!this.yearMonth && this.issueDate) {
560
- const year = this.issueDate.getFullYear();
561
- const month = String(this.issueDate.getMonth() + 1).padStart(2, "0");
562
- this.yearMonth = `${year}-${month}`;
563
- }
564
-
565
- next();
566
- });
567
-
568
- const Invoice = mongoose.model("Invoice", invoiceSchema);
569
-
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
+ },
14
+ client: {
15
+ clientId: {
16
+ type: mongoose.Schema.Types.ObjectId,
17
+ ref: "Customer",
18
+ required: true,
19
+ },
20
+ firstName: {
21
+ type: String,
22
+ required: true,
23
+ },
24
+ lastName: {
25
+ type: String,
26
+ required: true,
27
+ },
28
+ kraPin: {
29
+ type: String,
30
+ default: null,
31
+ },
32
+ },
33
+ facility: {
34
+ id: {
35
+ type: mongoose.Schema.Types.ObjectId,
36
+ ref: "Facility",
37
+ required: true,
38
+ },
39
+ name: {
40
+ type: String,
41
+ required: true,
42
+ },
43
+ },
44
+ unit: {
45
+ id: { type: mongoose.Schema.Types.ObjectId, ref: "Unit", required: true },
46
+ name: { type: String, required: true },
47
+ },
48
+ // Per-unit fee breakdown for a single invoice covering multiple units
49
+ // (e.g. a Property-Management contract billing several units at
50
+ // different rates). Only set when the invoice spans more than one
51
+ // unit - `unit` above still identifies the contract's primary unit for
52
+ // backward compatibility with code that expects a single unit.
53
+ unitBreakdown: [{
54
+ unitId: { type: mongoose.Schema.Types.ObjectId, ref: "Unit" },
55
+ amount: Number
56
+ }],
57
+ currency: {
58
+ id: {
59
+ type: mongoose.Schema.Types.ObjectId,
60
+ ref: "Currency",
61
+ required: true,
62
+ },
63
+ name: {
64
+ type: String,
65
+ required: true,
66
+ },
67
+ code: {
68
+ type: String,
69
+ required: true,
70
+ uppercase: true,
71
+ minlength: 3,
72
+ maxlength: 3,
73
+ },
74
+ },
75
+ items: [
76
+ {
77
+ description: { type: String, required: true },
78
+ quantity: { type: Number, required: true, min: 1 },
79
+ unitPrice: { type: Number, required: true, min: 0 },
80
+ },
81
+ ],
82
+ subTotal: {
83
+ type: Number,
84
+ required: true,
85
+ },
86
+ tax: {
87
+ type: Number,
88
+ required: true,
89
+ },
90
+ taxes: [
91
+ {
92
+ name: { type: String, required: true }, // e.g. "VAT", "Withholding Tax"
93
+ percentage: { type: Number, required: true }, // e.g. 16
94
+ amount: { type: Number, required: true }, // e.g. 160.00 (subTotal * percentage / 100)
95
+ },
96
+ ],
97
+ withholdingTaxes: [
98
+ {
99
+ name: { type: String, required: true }, // e.g. "withholding"
100
+ percentage: { type: Number, required: true }, // e.g. 5
101
+ amount: { type: Number, required: true }, // e.g. 50 (subTotal * percentage / 100)
102
+ },
103
+ ],
104
+ totalWithholdingTax: {
105
+ type: Number,
106
+ default: 0,
107
+ },
108
+ totalAmount: {
109
+ type: Number,
110
+ required: true,
111
+ },
112
+ amountPaid: {
113
+ type: Number,
114
+ default: 0,
115
+ min: 0,
116
+ },
117
+ // Mark as deprecated, keep for backward compatibility
118
+ overpay: {
119
+ type: Number,
120
+ default: 0,
121
+ min: 0,
122
+ deprecated: true,
123
+ },
124
+ issueDate: {
125
+ type: Date,
126
+ required: true,
127
+ },
128
+ dueDate: {
129
+ type: Date,
130
+ required: true,
131
+ },
132
+ status: {
133
+ type: String,
134
+ required: true,
135
+ enum: [
136
+ "Unpaid",
137
+ "Pending",
138
+ "Paid",
139
+ "Overdue",
140
+ "Cancelled",
141
+ "Partially Paid",
142
+ "Void",
143
+ ],
144
+ },
145
+ penalty: {
146
+ type: Number,
147
+ default: 0,
148
+ },
149
+ whatFor: {
150
+ invoiceType: { type: String, required: true },
151
+ description: { type: String },
152
+ },
153
+ invoiceNote: {
154
+ type: String,
155
+ default: null,
156
+ },
157
+ balanceBroughtForward: {
158
+ type: Number,
159
+ default: 0,
160
+ },
161
+ // NEW FIELD 1: Year-Month for easy filtering
162
+ yearMonth: {
163
+ type: String,
164
+ match: /^\d{4}-\d{2}$/, // Validates format like "2025-08"
165
+ index: true,
166
+ },
167
+ // NEW FIELD 2: Simple notification tracking
168
+ notificationsSent: {
169
+ sms: {
170
+ type: Boolean,
171
+ default: false,
172
+ },
173
+ email: {
174
+ type: Boolean,
175
+ default: false,
176
+ },
177
+ attempts: {
178
+ type: Number,
179
+ default: 0,
180
+ },
181
+ },
182
+ voidMetadata: {
183
+ voidedBy: {
184
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
185
+ name: { type: String },
186
+ role: { type: String },
187
+ },
188
+ voidedAt: { type: Date },
189
+ reason: { type: String },
190
+ },
191
+ cancelMetadata: {
192
+ cancelledBy: {
193
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
194
+ name: { type: String },
195
+ role: { type: String },
196
+ },
197
+ cancelledAt: { type: Date },
198
+ reason: { type: String },
199
+ },
200
+ lastReminderSent: Date,
201
+ reminderHistory: [
202
+ {
203
+ sentAt: Date,
204
+ reminderId: mongoose.Schema.Types.ObjectId,
205
+ notificationTypes: [String],
206
+ },
207
+ ],
208
+ reconciliationHistory: [
209
+ {
210
+ date: { type: Date, required: true },
211
+ paymentDate: {
212
+ type: Date
213
+ },
214
+ amount: { type: Number, required: true },
215
+ type: {
216
+ type: String,
217
+ enum: [
218
+ "payment",
219
+ "cash",
220
+ "cheque",
221
+ "bank-transfer",
222
+ "mpesa-transfer",
223
+ "overpay-transfer",
224
+ "balance-deduction",
225
+ "overpay-received",
226
+ "credit-forward",
227
+ "debit-forward",
228
+ ],
229
+ required: true,
230
+ },
231
+ sourceInvoice: String,
232
+ destinationInvoice: String,
233
+ paymentReference: String,
234
+ paymentCompletion: String,
235
+ remainingBalance: Number,
236
+ notes: String,
237
+ exchangeRate: {
238
+ type: Number,
239
+ default: 1, // For cross-currency reconciliations
240
+ },
241
+ originalCurrency: {
242
+ code: String, // Original currency code if different from invoice currency
243
+ amount: Number, // Amount in original currency
244
+ },
245
+ },
246
+ ],
247
+ paymentDetails: {
248
+ paymentStatus: { type: String, required: true },
249
+ paymentMethod: { type: String },
250
+ paymentDate: { type: Date },
251
+ transactionId: { type: String },
252
+ },
253
+ // New field to track when an invoice has been viewed
254
+ viewStatus: {
255
+ isOpened: { type: Boolean, default: false },
256
+ openedAt: { type: Date, default: null },
257
+ openedBy: {
258
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
259
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
260
+ userRole: { type: String },
261
+ },
262
+ viewHistory: [
263
+ {
264
+ viewedAt: { type: Date, required: true },
265
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: "Facility" },
266
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
267
+ },
268
+ ],
269
+ },
270
+ invoiceUrl: {
271
+ type: String,
272
+ default: null,
273
+ },
274
+ // eTims tax integration fields
275
+ ticketUrl: {
276
+ type: String,
277
+ default: null,
278
+ description: "QR code URL from eTims/KRA for tax invoice verification",
279
+ },
280
+ txsync: {
281
+ status: {
282
+ type: String,
283
+ enum: ["pending", "synced", "failed", "not-applicable"],
284
+ default: "not-applicable",
285
+ },
286
+ syncedAt: {
287
+ type: Date,
288
+ default: null,
289
+ },
290
+ etimsInvoiceNo: {
291
+ type: String,
292
+ default: null,
293
+ },
294
+ sdcId: {
295
+ type: String,
296
+ default: null,
297
+ },
298
+ receiptNo: {
299
+ type: String,
300
+ default: null,
301
+ },
302
+ errorMessage: {
303
+ type: String,
304
+ default: null,
305
+ },
306
+ attempts: {
307
+ type: Number,
308
+ default: 0,
309
+ },
310
+ lastAttemptAt: {
311
+ type: Date,
312
+ default: null,
313
+ },
314
+ },
315
+ creditNote: {
316
+ status: {
317
+ type: String,
318
+ enum: ["pending", "synced", "failed"],
319
+ default: undefined, // absent until a void triggers one
320
+ },
321
+ creditNoteTraderNo: { type: String, default: null },
322
+ etimsCreditNoteNo: { type: String, default: null },
323
+ sdcId: { type: String, default: null },
324
+ receiptNo: { type: String, default: null },
325
+ verificationUrl: { type: String, default: null },
326
+ syncedAt: { type: Date, default: null },
327
+ lastAttemptAt: { type: Date, default: null },
328
+ errorMessage: { type: String, default: null },
329
+ attempts: { type: Number, default: 0 },
330
+ },
331
+ digitax: {
332
+ synced: {
333
+ type: Boolean,
334
+ default: false
335
+ },
336
+ syncedAt: Date,
337
+ status: {
338
+ type: String,
339
+ enum: [
340
+ "PENDING",
341
+ "PROCESSING",
342
+ "SUCCESS",
343
+ "FAILED"
344
+ ],
345
+ default: "PENDING"
346
+ },
347
+ saleId: String,
348
+ traderInvoiceNumber: String,
349
+ digitaxInvoiceNumber: Number,
350
+ receiptNumber: Number,
351
+ serialNumber: String,
352
+ receiptTypeCode: String,
353
+ offlineUrl: String,
354
+ saleDetailUrl: String,
355
+ etimsUrl: String,
356
+ attempts: {
357
+ type: Number,
358
+ default: 0
359
+ },
360
+ lastAttemptAt: Date,
361
+ lastError: String,
362
+ response: mongoose.Schema.Types.Mixed
363
+ },
364
+ // New fields for double entry accounts
365
+ invoiceDoubleEntryAccount: {
366
+ type: mongoose.Schema.Types.ObjectId,
367
+ ref: "GLAccountDoubleEntries",
368
+ required: false,
369
+ },
370
+ paymentDoubleEntryAccount: {
371
+ type: mongoose.Schema.Types.ObjectId,
372
+ ref: "GLAccountDoubleEntries",
373
+ required: false,
374
+ },
375
+ // GL account details for invoice entries
376
+ accountdebitedData: {
377
+ amount: { type: Number },
378
+ description: { type: String },
379
+ isActive: { type: Boolean, default: true },
380
+ },
381
+ accountcreditedData: {
382
+ amount: { type: Number },
383
+ description: { type: String },
384
+ isActive: { type: Boolean, default: true },
385
+ },
386
+ },
387
+ {
388
+ timestamps: true,
389
+ },
390
+ );
391
+
392
+ // Add indexes for frequently queried fields
393
+ invoiceSchema.index({ accountNumber: 1 });
394
+ invoiceSchema.index({ status: 1 });
395
+ invoiceSchema.index({ "client.clientId": 1, status: 1 });
396
+ invoiceSchema.index({ "reconciliationHistory.paymentReference": 1 });
397
+ invoiceSchema.index({ issueDate: -1 });
398
+ invoiceSchema.index({ "currency.code": 1 }); // Add index for currency code
399
+ invoiceSchema.index({ "currency.id": 1 }); // Add index for currency ID
400
+ invoiceSchema.index({ "currency.code": 1, "client.clientId": 1, status: 1 }); // Compound index for currency-based queries
401
+ invoiceSchema.index({ "client.clientId": 1, balanceBroughtForward: 1 }); // Add index for finding invoices with credit balances
402
+ invoiceSchema.index({ "viewStatus.isOpened": 1 }); // Add index for view status
403
+ invoiceSchema.index({ "viewStatus.openedBy.facilityId": 1 }); // Add index for facility view tracking
404
+ // Add index for double entry accounts
405
+ invoiceSchema.index({ invoiceDoubleEntryAccount: 1 });
406
+ invoiceSchema.index({ paymentDoubleEntryAccount: 1 });
407
+ // NEW INDEXES for new fields
408
+ invoiceSchema.index({ yearMonth: 1 }); // For filtering by year-month
409
+ invoiceSchema.index({ yearMonth: 1, status: 1 }); // Combined index for monthly reports
410
+ invoiceSchema.index({ "notificationsSent.sms": 1 }); // For finding SMS sent/not sent
411
+ invoiceSchema.index({ "notificationsSent.email": 1 }); // For finding email sent/not sent
412
+ // eTims tax sync indexes
413
+ invoiceSchema.index({ "txsync.status": 1 }); // For finding invoices by sync status
414
+ invoiceSchema.index({ "txsync.etimsInvoiceNo": 1 }); // For looking up by eTims invoice number
415
+
416
+ // Add virtual field for calculating balance
417
+ invoiceSchema.virtual("calculatedBalance").get(function () {
418
+ const baseBalance = this.totalAmount - (this.amountPaid || 0);
419
+
420
+ // Add positive balanceBroughtForward (customer owes money)
421
+ if (this.balanceBroughtForward > 0) {
422
+ return baseBalance + this.balanceBroughtForward;
423
+ }
424
+
425
+ // Subtract negative balanceBroughtForward (credit)
426
+ return baseBalance;
427
+ });
428
+
429
+ // Add virtual field for credit balance
430
+ invoiceSchema.virtual("creditBalance").get(function () {
431
+ return this.balanceBroughtForward < 0
432
+ ? Math.abs(this.balanceBroughtForward)
433
+ : 0;
434
+ });
435
+
436
+ // Getter for compatible overpay field
437
+ invoiceSchema.virtual("effectiveOverpay").get(function () {
438
+ return this.balanceBroughtForward < 0
439
+ ? Math.abs(this.balanceBroughtForward)
440
+ : 0;
441
+ });
442
+
443
+ // Add virtual populate for invoice double entry account
444
+ invoiceSchema.virtual("invoiceDoubleEntry", {
445
+ ref: "GLAccountDoubleEntries",
446
+ localField: "invoiceDoubleEntryAccount",
447
+ foreignField: "_id",
448
+ justOne: true,
449
+ });
450
+
451
+ // Add virtual populate for payment double entry account
452
+ invoiceSchema.virtual("paymentDoubleEntry", {
453
+ ref: "GLAccountDoubleEntries",
454
+ localField: "paymentDoubleEntryAccount",
455
+ foreignField: "_id",
456
+ justOne: true,
457
+ });
458
+
459
+ // Add method for currency conversion if needed
460
+ invoiceSchema.methods.convertAmount = function (
461
+ amount,
462
+ fromCurrency,
463
+ toCurrency,
464
+ exchangeRate,
465
+ ) {
466
+ if (fromCurrency === toCurrency) {
467
+ return amount;
468
+ }
469
+ return amount * exchangeRate;
470
+ };
471
+
472
+ // Add static method to find invoices by currency
473
+ invoiceSchema.statics.findByCurrency = function (currencyCode) {
474
+ return this.find({ "currency.code": currencyCode.toUpperCase() });
475
+ };
476
+
477
+ // Add static method to find invoices with credit balance
478
+ invoiceSchema.statics.findWithCreditBalance = function (clientId) {
479
+ return this.find({
480
+ "client.clientId": clientId,
481
+ balanceBroughtForward: { $lt: 0 },
482
+ }).sort({ updatedAt: -1 });
483
+ };
484
+
485
+ // Add static method to calculate totals by currency
486
+ invoiceSchema.statics.calculateTotalsByCurrency = function (query = {}) {
487
+ return this.aggregate([
488
+ { $match: query },
489
+ {
490
+ $group: {
491
+ _id: "$currency.code",
492
+ totalAmount: { $sum: "$totalAmount" },
493
+ totalPaid: { $sum: "$amountPaid" },
494
+ count: { $sum: 1 },
495
+ },
496
+ },
497
+ ]);
498
+ };
499
+
500
+ // New static method to find all unviewed invoices
501
+ invoiceSchema.statics.findUnviewedInvoices = function (facilityId) {
502
+ return this.find({
503
+ "facility.id": facilityId,
504
+ "viewStatus.isOpened": false,
505
+ });
506
+ };
507
+
508
+ // NEW STATIC METHODS for new fields
509
+ // Find invoices by year-month
510
+ invoiceSchema.statics.findByYearMonth = function (yearMonth) {
511
+ return this.find({ yearMonth: yearMonth });
512
+ };
513
+
514
+ // Find invoices where notifications haven't been sent
515
+ invoiceSchema.statics.findPendingNotifications = function (facilityId) {
516
+ return this.find({
517
+ "facility.id": facilityId,
518
+ status: { $in: ["Unpaid", "Overdue"] },
519
+ $or: [
520
+ { "notificationsSent.sms": false },
521
+ { "notificationsSent.email": false },
522
+ ],
523
+ });
524
+ };
525
+
526
+ // Find invoices pending eTims sync
527
+ invoiceSchema.statics.findPendingTaxSync = function (facilityId) {
528
+ return this.find({
529
+ "facility.id": facilityId,
530
+ "txsync.status": "pending",
531
+ });
532
+ };
533
+
534
+ // Find invoices with failed eTims sync
535
+ invoiceSchema.statics.findFailedTaxSync = function (facilityId) {
536
+ return this.find({
537
+ "facility.id": facilityId,
538
+ "txsync.status": "failed",
539
+ });
540
+ };
541
+
542
+ // Pre-save middleware to ensure overpay and balanceBroughtForward stay in sync during transition
543
+ invoiceSchema.pre("save", function (next) {
544
+
545
+ if (this.taxes && this.taxes.length > 0) {
546
+ this.tax = Math.round(
547
+ this.taxes.reduce((sum, t) => sum + (t.amount || 0), 0) * 100
548
+ ) / 100;
549
+ }
550
+
551
+ // If balanceBroughtForward is negative (credit), sync with overpay for backwards compatibility
552
+ if (this.balanceBroughtForward < 0) {
553
+ this.overpay = Math.abs(this.balanceBroughtForward);
554
+ } else {
555
+ this.overpay = 0; // No overpay if there's no negative balance
556
+ }
557
+
558
+ // NEW: Auto-generate yearMonth from issueDate if not provided
559
+ if (!this.yearMonth && this.issueDate) {
560
+ const year = this.issueDate.getFullYear();
561
+ const month = String(this.issueDate.getMonth() + 1).padStart(2, "0");
562
+ this.yearMonth = `${year}-${month}`;
563
+ }
564
+
565
+ next();
566
+ });
567
+
568
+ const Invoice = mongoose.model("Invoice", invoiceSchema);
569
+
570
570
  module.exports = Invoice;