gemcap-be-common 1.2.140 → 1.3.0

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 (303) hide show
  1. package/classes/bank-transaction-item.d.ts +17 -0
  2. package/classes/bank-transaction-item.js +64 -0
  3. package/classes/bank-transaction-item.ts +66 -0
  4. package/classes/bank-uploaded-transaction.d.ts +17 -0
  5. package/classes/bank-uploaded-transaction.js +35 -0
  6. package/classes/bank-uploaded-transaction.ts +35 -0
  7. package/classes/inventory-item.d.ts +41 -0
  8. package/classes/inventory-item.js +44 -0
  9. package/classes/inventory-item.ts +63 -0
  10. package/classes/payable-account-item.d.ts +22 -0
  11. package/classes/payable-account-item.js +27 -0
  12. package/classes/payable-account-item.ts +35 -0
  13. package/classes/quickbook-item.d.ts +37 -0
  14. package/classes/quickbook-item.js +51 -0
  15. package/classes/quickbook-item.ts +59 -0
  16. package/classes/receivable-item.d.ts +26 -0
  17. package/classes/receivable-item.js +28 -0
  18. package/classes/receivable-item.ts +38 -0
  19. package/constants/date-formats.contsants.d.ts +1 -0
  20. package/constants/date-formats.contsants.js +4 -0
  21. package/constants/date-formats.contsants.ts +1 -0
  22. package/db/brokers.db.d.ts +185 -0
  23. package/db/brokers.db.js +35 -2
  24. package/db/brokers.db.ts +34 -1
  25. package/db/collateral-adjustments.db.d.ts +34 -0
  26. package/db/collateral-adjustments.db.js +52 -0
  27. package/db/collateral-adjustments.db.ts +54 -0
  28. package/db/collaterals.db.d.ts +1 -1
  29. package/db/equipment.db.d.ts +40 -0
  30. package/db/equipment.db.js +55 -0
  31. package/db/equipment.db.ts +56 -0
  32. package/db/financial-spreading.db.ts +2 -1
  33. package/db/groups.d.ts +5 -0
  34. package/db/groups.js +57 -0
  35. package/db/groups.ts +52 -0
  36. package/db/inventories.d.ts +91 -0
  37. package/db/inventories.js +449 -0
  38. package/db/inventories.ts +481 -0
  39. package/db/inventory-availability.d.ts +3 -0
  40. package/db/inventory-availability.js +103 -0
  41. package/db/inventory-availability.ts +113 -0
  42. package/db/new-summary.d.ts +31 -0
  43. package/db/new-summary.js +1295 -0
  44. package/db/new-summary.ts +1509 -0
  45. package/db/payable-accounts.d.ts +30 -0
  46. package/db/payable-accounts.js +55 -0
  47. package/db/payable-accounts.ts +50 -0
  48. package/db/reserve.db.d.ts +34 -0
  49. package/db/reserve.db.js +52 -0
  50. package/db/reserve.db.ts +48 -0
  51. package/db/uploads.db.d.ts +2 -0
  52. package/db/uploads.db.js +29 -0
  53. package/db/uploads.db.ts +24 -0
  54. package/helpers/main.helper.d.ts +31 -0
  55. package/helpers/main.helper.js +63 -0
  56. package/helpers/main.helper.ts +63 -0
  57. package/models/AccountPayableItem.model.d.ts +6 -6
  58. package/models/AllocatedBankTransaction.model.d.ts +54 -0
  59. package/models/AllocatedBankTransaction.model.js +70 -0
  60. package/models/AllocatedBankTransaction.model.ts +94 -0
  61. package/models/AllocatedData.model.d.ts +33 -0
  62. package/models/AllocatedData.model.js +19 -0
  63. package/models/AllocatedData.model.ts +24 -0
  64. package/models/BBCDate.model.d.ts +3 -3
  65. package/models/BBCSheet.model.d.ts +3 -3
  66. package/models/Banks.model.d.ts +3 -3
  67. package/models/Borrower.model.d.ts +3 -3
  68. package/models/BorrowerData.model.d.ts +3 -3
  69. package/models/BorrowerDataInsurance.model.d.ts +3 -3
  70. package/models/BorrowerDataTerm.model.d.ts +3 -3
  71. package/models/BorrowerSummary.model.js +1 -1
  72. package/models/BorrowerSummary.model.ts +1 -1
  73. package/models/CalandarDay.model.d.ts +40 -0
  74. package/models/CalandarDay.model.js +47 -0
  75. package/models/CalandarDay.model.ts +61 -0
  76. package/models/CashAllocationProduct.model.d.ts +119 -0
  77. package/models/CashAllocationProduct.model.js +102 -0
  78. package/models/CashAllocationProduct.model.ts +112 -0
  79. package/models/CashAllocationReference.model.d.ts +37 -0
  80. package/models/CashAllocationReference.model.js +27 -0
  81. package/models/CashAllocationReference.model.ts +40 -0
  82. package/models/CollateralAdjustment.model.d.ts +51 -0
  83. package/models/CollateralAdjustment.model.js +61 -0
  84. package/models/CollateralAdjustment.model.ts +98 -0
  85. package/models/Company.model.d.ts +35 -0
  86. package/models/Company.model.js +18 -0
  87. package/models/Company.model.ts +29 -0
  88. package/models/CustomerAPGroup.model.d.ts +32 -0
  89. package/models/CustomerAPGroup.model.js +24 -0
  90. package/models/CustomerAPGroup.model.ts +31 -0
  91. package/models/Equipment.model.d.ts +53 -0
  92. package/models/Equipment.model.js +140 -0
  93. package/models/Equipment.model.ts +172 -0
  94. package/models/FinancialCompliance.model.d.ts +39 -0
  95. package/models/FinancialCompliance.model.js +64 -0
  96. package/models/FinancialCompliance.model.ts +78 -0
  97. package/models/FinancialComplianceBorrower.model.d.ts +58 -0
  98. package/models/FinancialComplianceBorrower.model.js +82 -0
  99. package/models/FinancialComplianceBorrower.model.ts +118 -0
  100. package/models/FinancialIndexes.model.d.ts +36 -0
  101. package/models/FinancialIndexes.model.js +27 -0
  102. package/models/FinancialIndexes.model.ts +37 -0
  103. package/models/Inventory.model.d.ts +18 -18
  104. package/models/InventoryAvailability.model.d.ts +21 -21
  105. package/models/InventoryAvailabilityItem.model.d.ts +6 -6
  106. package/models/InventoryItem.model.d.ts +24 -24
  107. package/models/InventoryManualEntry.model.d.ts +9 -9
  108. package/models/InventorySeasonalRates.model.d.ts +3 -3
  109. package/models/LoanBroker.model.d.ts +3 -3
  110. package/models/LoanCharges.model.d.ts +12 -12
  111. package/models/LoanProducts.model.d.ts +9 -9
  112. package/models/LoanStatementStatus.model.d.ts +35 -0
  113. package/models/LoanStatementStatus.model.js +34 -0
  114. package/models/LoanStatementStatus.model.ts +45 -0
  115. package/models/LoanStatementTransaction.model.d.ts +9 -9
  116. package/models/LoanTransactionFile.model.d.ts +41 -0
  117. package/models/LoanTransactionFile.model.js +44 -0
  118. package/models/LoanTransactionFile.model.ts +61 -0
  119. package/models/MappedGroup.model.d.ts +37 -0
  120. package/models/MappedGroup.model.js +33 -0
  121. package/models/MappedGroup.model.ts +46 -0
  122. package/models/MonthEndData.Model.d.ts +41 -0
  123. package/models/MonthEndData.Model.js +42 -0
  124. package/models/MonthEndData.Model.ts +53 -0
  125. package/models/OrganizationEmails.model.d.ts +44 -0
  126. package/models/OrganizationEmails.model.js +40 -0
  127. package/models/OrganizationEmails.model.ts +54 -0
  128. package/models/ProductBroker.model.d.ts +9 -9
  129. package/models/QuickbooksAccount.model.d.ts +39 -0
  130. package/models/QuickbooksAccount.model.js +43 -0
  131. package/models/QuickbooksAccount.model.ts +57 -0
  132. package/models/Receivable.model.d.ts +12 -12
  133. package/models/ReceivableAvailability.model.d.ts +54 -54
  134. package/models/ReceivableAvailabilityItem.model.d.ts +57 -57
  135. package/models/ReceivableItem.model.d.ts +6 -6
  136. package/models/Reserve.model.d.ts +51 -0
  137. package/models/Reserve.model.js +96 -0
  138. package/models/Reserve.model.ts +125 -0
  139. package/models/TermLoan.model.d.ts +3 -3
  140. package/models/TermLoanCalculated.model.d.ts +6 -6
  141. package/models/TransactionAttachedFile.Model.d.ts +35 -0
  142. package/models/TransactionAttachedFile.Model.js +37 -0
  143. package/models/TransactionAttachedFile.Model.ts +48 -0
  144. package/models/UploadedBankTransaction.model.d.ts +56 -0
  145. package/models/UploadedBankTransaction.model.js +78 -0
  146. package/models/UploadedBankTransaction.model.ts +110 -0
  147. package/models/UploadedData.model.d.ts +36 -0
  148. package/models/UploadedData.model.js +23 -0
  149. package/models/UploadedData.model.ts +35 -0
  150. package/models/UploadedFile.model.d.ts +40 -0
  151. package/models/UploadedFile.model.js +41 -0
  152. package/models/UploadedFile.model.ts +57 -0
  153. package/models/UploadedSheet.model.d.ts +46 -0
  154. package/models/UploadedSheet.model.js +27 -0
  155. package/models/UploadedSheet.model.ts +51 -0
  156. package/package.json +10 -1
  157. package/repositories/globals.repository.d.ts +8 -0
  158. package/repositories/globals.repository.js +24 -0
  159. package/repositories/globals.repository.ts +21 -0
  160. package/services/attached-files.service.d.ts +57 -0
  161. package/services/attached-files.service.js +103 -0
  162. package/services/attached-files.service.ts +123 -0
  163. package/services/availability.service.d.ts +77 -0
  164. package/services/availability.service.js +897 -0
  165. package/services/availability.service.ts +1034 -0
  166. package/services/bank-uploaded-transactions.service.d.ts +33 -0
  167. package/services/bank-uploaded-transactions.service.js +430 -0
  168. package/services/bank-uploaded-transactions.service.ts +475 -0
  169. package/services/banks.service.d.ts +36 -0
  170. package/services/banks.service.js +91 -0
  171. package/services/banks.service.ts +95 -0
  172. package/services/borrower-summary.service.d.ts +35 -0
  173. package/services/borrower-summary.service.js +310 -0
  174. package/services/borrower-summary.service.ts +334 -0
  175. package/services/borrowers.service.d.ts +103 -0
  176. package/services/borrowers.service.js +268 -0
  177. package/services/borrowers.service.ts +302 -0
  178. package/services/brokers.service.d.ts +212 -0
  179. package/services/brokers.service.js +160 -0
  180. package/services/brokers.service.ts +200 -0
  181. package/services/calendar.service.d.ts +53 -0
  182. package/services/calendar.service.js +108 -0
  183. package/services/calendar.service.ts +128 -0
  184. package/services/cash-allocation.service.d.ts +40 -0
  185. package/services/cash-allocation.service.js +92 -0
  186. package/services/cash-allocation.service.ts +105 -0
  187. package/services/collateral-adjustments.service.d.ts +38 -0
  188. package/services/collateral-adjustments.service.js +82 -0
  189. package/services/collateral-adjustments.service.ts +95 -0
  190. package/services/collaterals.service.d.ts +69 -0
  191. package/services/collaterals.service.js +279 -0
  192. package/services/collaterals.service.ts +319 -0
  193. package/services/companies.service.d.ts +5 -0
  194. package/services/companies.service.js +21 -0
  195. package/services/companies.service.ts +23 -0
  196. package/services/compliance-borrowers.service.d.ts +152 -0
  197. package/services/compliance-borrowers.service.js +569 -0
  198. package/services/compliance-borrowers.service.ts +617 -0
  199. package/services/equipment.service.d.ts +42 -0
  200. package/services/equipment.service.js +120 -0
  201. package/services/equipment.service.ts +149 -0
  202. package/services/file-manager.service.d.ts +44 -0
  203. package/services/file-manager.service.js +120 -0
  204. package/services/file-manager.service.ts +146 -0
  205. package/services/financial-compliance.service.d.ts +58 -0
  206. package/services/financial-compliance.service.js +281 -0
  207. package/services/financial-compliance.service.ts +309 -0
  208. package/services/financial-indexes.service.d.ts +20 -0
  209. package/services/financial-indexes.service.js +241 -0
  210. package/services/financial-indexes.service.ts +257 -0
  211. package/services/financial-spreading.service.d.ts +74 -0
  212. package/services/financial-spreading.service.js +450 -0
  213. package/services/financial-spreading.service.ts +517 -0
  214. package/services/globals.service.d.ts +5 -0
  215. package/services/globals.service.js +11 -0
  216. package/services/globals.service.ts +8 -0
  217. package/services/groups.service.d.ts +39 -0
  218. package/services/groups.service.js +65 -0
  219. package/services/groups.service.ts +64 -0
  220. package/services/inventory-availability.service.d.ts +13 -0
  221. package/services/inventory-availability.service.js +170 -0
  222. package/services/inventory-availability.service.ts +187 -0
  223. package/services/inventory.service.d.ts +118 -0
  224. package/services/inventory.service.js +239 -0
  225. package/services/inventory.service.ts +276 -0
  226. package/services/loan-charges.service.d.ts +83 -0
  227. package/services/loan-charges.service.js +343 -0
  228. package/services/loan-charges.service.ts +396 -0
  229. package/services/loan-payments.service.d.ts +94 -0
  230. package/services/loan-payments.service.js +485 -0
  231. package/services/loan-payments.service.ts +541 -0
  232. package/services/loan-products.service.d.ts +12 -0
  233. package/services/loan-products.service.js +55 -0
  234. package/services/loan-products.service.ts +58 -0
  235. package/services/loan-statement-balance.service.d.ts +16 -0
  236. package/services/loan-statement-balance.service.js +106 -0
  237. package/services/loan-statement-balance.service.ts +113 -0
  238. package/services/loan-statement-effects.service.d.ts +8 -0
  239. package/services/loan-statement-effects.service.js +42 -0
  240. package/services/loan-statement-effects.service.ts +41 -0
  241. package/services/loan-statement-status.service.d.ts +208 -0
  242. package/services/loan-statement-status.service.js +159 -0
  243. package/services/loan-statement-status.service.ts +177 -0
  244. package/services/loan-statement.service.d.ts +186 -0
  245. package/services/loan-statement.service.js +935 -0
  246. package/services/loan-statement.service.ts +1040 -0
  247. package/services/loan-transactions.service.d.ts +169 -0
  248. package/services/loan-transactions.service.js +941 -0
  249. package/services/loan-transactions.service.ts +1042 -0
  250. package/services/lock.service.d.ts +6 -0
  251. package/services/lock.service.js +45 -0
  252. package/services/lock.service.ts +45 -0
  253. package/services/manual-entry.service.d.ts +20 -0
  254. package/services/manual-entry.service.js +186 -0
  255. package/services/manual-entry.service.ts +201 -0
  256. package/services/month-end-data.service.d.ts +34 -0
  257. package/services/month-end-data.service.js +30 -0
  258. package/services/month-end-data.service.ts +35 -0
  259. package/services/nodemailer.service.d.ts +96 -0
  260. package/services/nodemailer.service.js +689 -0
  261. package/services/nodemailer.service.ts +774 -0
  262. package/services/organization-emails.service.d.ts +31 -0
  263. package/services/organization-emails.service.js +10 -0
  264. package/services/organization-emails.service.ts +7 -0
  265. package/services/organizations.service.d.ts +34 -0
  266. package/services/organizations.service.js +74 -0
  267. package/services/organizations.service.ts +84 -0
  268. package/services/pdf.service.d.ts +61 -0
  269. package/services/pdf.service.js +547 -0
  270. package/services/pdf.service.ts +642 -0
  271. package/services/quickbooks.service.d.ts +99 -0
  272. package/services/quickbooks.service.js +640 -0
  273. package/services/quickbooks.service.ts +734 -0
  274. package/services/reports/investor-summary.service.d.ts +28 -0
  275. package/services/reports/investor-summary.service.js +136 -0
  276. package/services/reports/investor-summary.service.ts +159 -0
  277. package/services/reports.service.d.ts +126 -0
  278. package/services/reports.service.js +584 -0
  279. package/services/reports.service.ts +702 -0
  280. package/services/reserve.service.d.ts +37 -0
  281. package/services/reserve.service.js +76 -0
  282. package/services/reserve.service.ts +79 -0
  283. package/services/sentry.service.d.ts +11 -0
  284. package/services/sentry.service.js +49 -0
  285. package/services/sentry.service.ts +33 -0
  286. package/services/signs.service.d.ts +69 -0
  287. package/services/signs.service.js +230 -0
  288. package/services/signs.service.ts +260 -0
  289. package/services/term-loan.service.d.ts +30 -0
  290. package/services/term-loan.service.js +614 -0
  291. package/services/term-loan.service.ts +696 -0
  292. package/services/uploads.service.d.ts +134 -0
  293. package/services/uploads.service.js +587 -0
  294. package/services/uploads.service.ts +643 -0
  295. package/services/user-logs.service.d.ts +23 -0
  296. package/services/user-logs.service.js +160 -0
  297. package/services/user-logs.service.ts +177 -0
  298. package/services/users.service.d.ts +4 -4
  299. package/services/yield.service.d.ts +46 -0
  300. package/services/yield.service.js +42 -12
  301. package/services/yield.service.ts +38 -8
  302. package/tsconfig.json +5 -5
  303. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,1040 @@
1
+ import dayjs from 'dayjs';
2
+ import utc from 'dayjs/plugin/utc';
3
+ import timezone from 'dayjs/plugin/timezone';
4
+ import Decimal from 'decimal.js';
5
+
6
+ import { EChargeCalculationBasis, EChargeFrequencies, ELoanTypes } from '../enums/loan-types.enum';
7
+ import { createFilteredObject } from '../helpers/common.helper';
8
+ import { ELoanChargeType } from '../enums/loan-charge-type.enum';
9
+ import {
10
+ ILoanStatementTransaction,
11
+ ILoanStatementTransactionDoc,
12
+ ILoanStatementTransactionView,
13
+ ILoanStatementTransactionWithId,
14
+ LOAN_STATEMENT_TRANSACTION_FIELDS,
15
+ LoanStatementTransactionModel,
16
+ } from '../models/LoanStatementTransaction.model';
17
+ import { ILoanChargeView, LoanCharge } from '../models/LoanCharges.model';
18
+ import { LoanProduct } from '../models/LoanProducts.model';
19
+ import { BorrowerModel } from '../models/Borrower.model';
20
+ import { ILoanPaymentWithId, LoanPaymentModel } from '../models/LoanPayment.model';
21
+ import { defaultDateFormat } from '../helpers/date.helper';
22
+ import { TermLoanCalculatedModel } from '../models/TermLoanCalculated.model';
23
+ import { TermLoanModel } from '../models/TermLoan.model';
24
+ import {
25
+ ELedgerReportType,
26
+ getBorrowerWithPeriods,
27
+ getPeriods,
28
+ getStatementBorrowersTransactions,
29
+ } from '../db/loan-statement.db';
30
+ import { BBCDateModel } from '../models/BBCDate.model';
31
+
32
+ import { EFinancialIndex, FinancialIndexesService } from './financial-indexes.service';
33
+ import { ITransactionsFilter, LoanTransactionsService } from './loan-transactions.service';
34
+ import { LoanStatementStatusService, SelectablePeriod } from './loan-statement-status.service';
35
+ import { LoanChargesService } from './loan-charges.service';
36
+ import { LoanPaymentsService } from './loan-payments.service';
37
+ import { LoanStatementBalanceService } from './loan-statement-balance.service';
38
+ import { TermLoanService } from './term-loan.service';
39
+ import { UploadsService } from './uploads.service';
40
+ import { LoanStatementEffectsService } from './loan-statement-effects.service';
41
+
42
+ dayjs.extend(utc);
43
+ dayjs.extend(timezone);
44
+
45
+ export interface IStatementPeriod {
46
+ start: dayjs.Dayjs;
47
+ end: dayjs.Dayjs;
48
+ }
49
+
50
+ export class LoanStatementService {
51
+
52
+ constructor(
53
+ private readonly financialIndexesService: FinancialIndexesService,
54
+ private readonly loanChargesService: LoanChargesService,
55
+ private readonly loanPaymentsService: LoanPaymentsService,
56
+ private readonly loanStatementBalanceService: LoanStatementBalanceService,
57
+ private readonly loanStatementStatusService: LoanStatementStatusService,
58
+ private readonly loanTransactionsService: LoanTransactionsService,
59
+ private readonly termLoanService: TermLoanService,
60
+ private readonly uploadsService: UploadsService,
61
+ private readonly getLoanStatementEffectsService: () => LoanStatementEffectsService,
62
+ ) {
63
+ }
64
+
65
+ private getGroupedData(statements: { date: Date, amount: number, chargeType: ELoanChargeType, memo: string }[]) {
66
+ const groups = {
67
+ [ELoanChargeType.INTEREST_FEE]: 0,
68
+ [ELoanChargeType.ADMIN_FEE]: 0,
69
+ [ELoanChargeType.UNUSED_LINE_FEE]: 0,
70
+ [ELoanChargeType.WIRE_FEE]: 0,
71
+ [ELoanChargeType.ANNUAL_LINE_FEE]: 0,
72
+ };
73
+ const otherStatements = [];
74
+ let totalAmount = 0;
75
+ const groupedData = statements.reduce((acc, record) => {
76
+ totalAmount = new Decimal(totalAmount).add(record.amount).toNumber();
77
+ if (record.chargeType === ELoanChargeType.OTHER || record.chargeType === ELoanChargeType.RECOVERABLE) {
78
+ otherStatements.push(record);
79
+ return acc;
80
+ }
81
+ return {
82
+ ...acc,
83
+ [record.chargeType]: new Decimal(acc[record.chargeType]).add(record.amount).toNumber(),
84
+ };
85
+ }, groups);
86
+ return { groupedData, otherStatements };
87
+ }
88
+
89
+ async getTransactionById(transactionId: string) {
90
+ return LoanStatementTransactionModel.findById(transactionId).lean();
91
+ }
92
+
93
+ async getFirstStatementTransaction(productId: string) {
94
+ const loanCharges = await this.loanChargesService.getLoanChargeForProduct(productId);
95
+ const chargesIds = loanCharges.map((charge) => charge._id);
96
+ const firstStatementTransactions = await LoanStatementTransactionModel.aggregate<ILoanStatementTransactionDoc>([
97
+ {
98
+ $match: {
99
+ 'chargeId': { '$in': chargesIds },
100
+ },
101
+ }, {
102
+ $sort: {
103
+ 'date': 1,
104
+ 'createdAt': 1,
105
+ 'order': 1,
106
+ },
107
+ }, {
108
+ $limit: 1,
109
+ },
110
+ ]);
111
+
112
+ if (!firstStatementTransactions.length) {
113
+ return null;
114
+ }
115
+ return firstStatementTransactions[0];
116
+ }
117
+
118
+ async getBorrowerWithPeriods(borrowerId: string, period: {
119
+ selectedPeriod: string,
120
+ selectedStart: Date,
121
+ selectedEnd: Date
122
+ }, splitPeriods = false) {
123
+ return getBorrowerWithPeriods(borrowerId, period, splitPeriods);
124
+ }
125
+
126
+ async getPeriods(selectedPeriod: string, productId: string): Promise<IStatementPeriod[]> {
127
+ return getPeriods(selectedPeriod, productId);
128
+ }
129
+
130
+ getDays(period: {
131
+ start: dayjs.Dayjs,
132
+ end: dayjs.Dayjs
133
+ }) {
134
+ const days: string[] = [];
135
+ for (let day = dayjs(period.start); day.isBefore(period.end); day = day.add(1, 'day')) {
136
+ days.push(day.format('YYYY-MM-DD'));
137
+ }
138
+ return days;
139
+ }
140
+
141
+ async calculateBorrowerStatement(borrowerId: string, selectedPeriod: SelectablePeriod) {
142
+ const products = await this.loanChargesService.getLoanProducts(borrowerId);
143
+ for (const product of products) {
144
+ const periods = await this.getPeriods(selectedPeriod, product._id.toString());
145
+ if (selectedPeriod === 'ENTIRE_LOAN') {
146
+ const charges = await this.loanChargesService.getLoanChargeForProduct(product._id.toString());
147
+ await this.cleanProductAllStatements(charges.map((charge) => charge._id.toString()));
148
+ }
149
+ await this.calculateProductStatement(product._id.toString(), periods);
150
+ }
151
+ }
152
+
153
+ async recalculateAllProducts() {
154
+ const borrowers = await BorrowerModel.find({ active: true });
155
+ for (const borrower of borrowers) {
156
+ const products = await this.loanChargesService.getLoanProducts(borrower._id.toString());
157
+ await Promise.all(products.map(async (product) => {
158
+ await this.loanTransactionsService.recalculateProduct(product._id.toString());
159
+ }));
160
+ }
161
+ }
162
+
163
+ async calculateAllBorrowers() {
164
+ const statementDate = this.loanStatementStatusService.getStatementDateFromStringPeriod('CURRENT_MONTH');
165
+ const date = this.loanStatementStatusService.getDateFromStatementDate(statementDate);
166
+ const periods = this.loanStatementStatusService.getPeriodsFromStatementDate(statementDate);
167
+ const borrowers = await BorrowerModel.find({ active: true });
168
+ for (const borrower of borrowers) {
169
+ const products = await this.loanChargesService.getLoanProducts(borrower._id.toString());
170
+ await Promise.all(products.map(async (product) => {
171
+ const validationError = await this.loanStatementStatusService.validateDate(product._id.toString(), date);
172
+ if (!validationError) {
173
+ try {
174
+ console.log('calculating borrower/product', borrower.name, product.code);
175
+ await this.calculateProductStatement(product._id.toString(), periods);
176
+ } catch (e) {
177
+ console.error(e);
178
+ }
179
+ }
180
+ }));
181
+ }
182
+ }
183
+
184
+ async calculateProductStatement(productId: string, periods: IStatementPeriod[]) {
185
+ await Promise.all(periods.map(async (period) => {
186
+ const days = this.getDays(period);
187
+ for (const day of days) {
188
+ await this.calculateStatement(productId, period, new Date(day));
189
+ }
190
+ const statementDate = this.loanStatementStatusService.getStatementDateFromPeriod(period);
191
+ await this.loanStatementStatusService.updateStatementStatus(productId, statementDate, true);
192
+ }));
193
+ const startDate = periods[0].start.startOf('day').toDate();
194
+ await this.loanStatementBalanceService.calculateProductBalance(productId, startDate);
195
+ }
196
+
197
+ async useFloatingBalance(productId: string) {
198
+ const product = await this.loanChargesService.getLoanProductById(productId);
199
+ return product.type === ELoanTypes.REVOLVER;
200
+ }
201
+
202
+ async calculateStatement(productId: string, period: IStatementPeriod, statementDate?: Date) {
203
+ if (!statementDate) {
204
+ statementDate = dayjs(new Date()).startOf('day').toDate();
205
+ }
206
+ const primeRate = await this.financialIndexesService.getFinancialIndexValue(EFinancialIndex.PRIME_RATE, statementDate);
207
+ if (!primeRate) {
208
+ return;
209
+ }
210
+ await this.cleanProductStatement(productId, statementDate);
211
+ const balance = await this.loanChargesService.getLoanProductBalance(productId, statementDate);
212
+
213
+ const loanStatementEffectsService = this.getLoanStatementEffectsService();
214
+ await loanStatementEffectsService.saveMonthData(productId, statementDate);
215
+
216
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
217
+ const product = await this.loanChargesService.getLoanProductById(productId);
218
+
219
+ const daysPerYear = 360;
220
+ const dayInMonth = dayjs(period.end).diff(period.start, 'days') + 1;
221
+ let orderIndex = 0;
222
+ for (const charge of charges) {
223
+ if (charge.applyFrom.getTime() > statementDate.getTime()) {
224
+ return;
225
+ }
226
+ const statementDateFormat = dayjs(statementDate).format('YYYY-MM-DD');
227
+ let calculateFee = false;
228
+ switch (charge.frequency) {
229
+ case EChargeFrequencies.DISBURSEMENT: {
230
+ calculateFee = true;
231
+ break;
232
+ }
233
+ case EChargeFrequencies.DAILY: {
234
+ calculateFee = true;
235
+ break;
236
+ }
237
+ case EChargeFrequencies.WEEKLY: {
238
+ if (dayjs(charge.applyFrom).day() === dayjs(statementDate).day()) {
239
+ calculateFee = true;
240
+ }
241
+ break;
242
+ }
243
+ case EChargeFrequencies.MONTHLY: {
244
+ const lastDayOfMonthFormat = dayjs(statementDate).endOf('month').format('YYYY-MM-DD');
245
+ if (lastDayOfMonthFormat === statementDateFormat) {
246
+ calculateFee = true;
247
+ }
248
+ break;
249
+ }
250
+ case EChargeFrequencies.ANNUAL: {
251
+ const chargeDate = dayjs(charge.applyFrom).year(statementDate.getFullYear()).format('YYYY-MM-DD');
252
+ if (chargeDate === statementDateFormat) {
253
+ calculateFee = true;
254
+ }
255
+ break;
256
+ }
257
+ case EChargeFrequencies.ONE_TIME: {
258
+ const chargeDate = dayjs(charge.applyFrom).format('YYYY-MM-DD');
259
+ if (chargeDate === statementDateFormat) {
260
+ calculateFee = true;
261
+ }
262
+ break;
263
+ }
264
+ }
265
+
266
+ const useFloating = await this.useFloatingBalance(productId);
267
+
268
+ const getBalance = () => {
269
+ return Math.max(0, useFloating ? balance.floatedBalance : balance.balance);
270
+ };
271
+
272
+ if (calculateFee) {
273
+ let fee = 0;
274
+ let calculatedFee = 0;
275
+ let percentFee = charge.percent;
276
+ if (charge.chargeType === ELoanChargeType.INTEREST_FEE) {
277
+ percentFee = new Decimal(primeRate).add(percentFee).toDP(4).toNumber();
278
+ const minPercent = product.minPercent ?? 0;
279
+ const maxPercent = (product.maxPercent ?? 0) === 0 ? 1 : product.maxPercent;
280
+ percentFee = Math.min(Math.max(percentFee, minPercent), maxPercent);
281
+ }
282
+ const dailyPercent = new Decimal(percentFee).div(daysPerYear).toNumber();
283
+ switch (charge.calculationBasis) {
284
+ case EChargeCalculationBasis.DAILY_BALANCE:
285
+ if (dayjs(statementDate).isAfter(dayjs(charge.applyFrom))) {
286
+ fee = new Decimal(getBalance()).mul(dailyPercent).toDP(2).toNumber();
287
+ }
288
+ break;
289
+ case EChargeCalculationBasis.AVERAGE_MONTHLY_BALANCE:
290
+ if (dayjs(statementDate).isAfter(dayjs(charge.applyFrom))) {
291
+ fee = new Decimal(getBalance()).mul(percentFee).div(dayInMonth).toDP(2).toNumber();
292
+ }
293
+ if (!percentFee && charge.minimumAmount) {
294
+ fee = new Decimal(charge.minimumAmount).div(dayInMonth).toDP(2).toNumber();
295
+ }
296
+ break;
297
+ case EChargeCalculationBasis.UNUSED_AMOUNT:
298
+ if (dayjs(statementDate).isAfter(dayjs(charge.applyFrom))) {
299
+ calculatedFee = new Decimal(product.commitment).sub(getBalance()).mul(charge.percent).div(daysPerYear).toDP(2).toNumber();
300
+ fee = Math.max(calculatedFee, 0);
301
+ }
302
+ break;
303
+ case EChargeCalculationBasis.COMMITMENT:
304
+ fee = new Decimal(product.commitment).mul(charge.percent).toDP(2).toNumber();
305
+ break;
306
+ case EChargeCalculationBasis.DISBURSEMENT_AMOUNT:
307
+ if (dayjs(statementDate).isAfter(dayjs(charge.applyFrom))) {
308
+ fee = await this.calculateDisbursementFee(product._id.toString(), charge, statementDate);
309
+ }
310
+ break;
311
+ case EChargeCalculationBasis.FIXED:
312
+ if (dayjs(statementDate).isAfter(dayjs(charge.applyFrom))) {
313
+ calculatedFee = new Decimal(getBalance()).mul(dailyPercent).toDP(2).toNumber();
314
+ fee = Math.max(calculatedFee, charge.minimumAmount);
315
+ }
316
+ break;
317
+ }
318
+
319
+ const checkMinAmount = (fee: number) => {
320
+ if (charge.minimumAmount > 0) {
321
+ if (charge.chargeType === ELoanChargeType.WIRE_FEE) {
322
+ return fee ? Math.max(charge.minimumAmount, fee) : 0;
323
+ }
324
+ if (charge.frequency === EChargeFrequencies.DAILY && charge.calculationBasis === EChargeCalculationBasis.AVERAGE_MONTHLY_BALANCE) {
325
+ return fee;
326
+ }
327
+ return fee ? Math.max(charge.minimumAmount, fee) : charge.minimumAmount;
328
+ }
329
+ return fee;
330
+ };
331
+
332
+ fee = checkMinAmount(fee);
333
+
334
+ if (fee !== null && fee > 0) {
335
+ const newStatement: ILoanStatementTransaction = {
336
+ order: orderIndex,
337
+ date: statementDate,
338
+ chargeId: charge._id,
339
+ amount: fee,
340
+ amountPaid: 0,
341
+ isSystem: true,
342
+ memo: 'System Generated',
343
+ balance: 0,
344
+ };
345
+ const newStatementDoc = await LoanStatementTransactionModel.create(newStatement);
346
+ await newStatementDoc.save();
347
+ orderIndex = orderIndex + 1;
348
+ }
349
+ }
350
+ }
351
+
352
+ await this.reorderStatementForDate(productId, statementDate);
353
+ }
354
+
355
+ async reorderStatementForDate(productId: string, date: Date) {
356
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
357
+ const lastTransactions = await LoanStatementTransactionModel.aggregate<ILoanStatementTransactionDoc>([
358
+ {
359
+ $match: {
360
+ 'chargeId': { $in: charges.map((charge) => charge._id) },
361
+ 'date': { $eq: date },
362
+ 'isSystem': false,
363
+ },
364
+ }, {
365
+ $sort: {
366
+ 'order': 1,
367
+ },
368
+ },
369
+ ]);
370
+ if (lastTransactions.length > 0) {
371
+ let lastOrder = await this.getLastTransactionOrder(lastTransactions[0]);
372
+ for (const transaction of lastTransactions) {
373
+ lastOrder = lastOrder + 1;
374
+ await LoanStatementTransactionModel.findByIdAndUpdate(transaction._id, { order: lastOrder });
375
+ }
376
+ }
377
+ }
378
+
379
+ async getBalancesDaily(productId: string, period: { start: Date, end: Date }) {
380
+ const days = this.getDays({ start: dayjs(period.start).utcOffset(0), end: dayjs(period.end).utcOffset(0) });
381
+ return Promise.all(days.map(async (day) => {
382
+ const balance = await this.loanChargesService.getLoanProductBalance(productId, new Date(day));
383
+ return { day, ...balance };
384
+ }));
385
+ }
386
+
387
+ private async calculateDisbursementFee(productId: string, charge: ILoanChargeView, day: Date): Promise<number> {
388
+ const totalDoc = await this.loanTransactionsService.getDisbursementTransactions(productId, day);
389
+ if (totalDoc === 0) {
390
+ return 0;
391
+ }
392
+ return new Decimal(totalDoc).mul(charge.minimumAmount).toDP(2).toNumber();
393
+ }
394
+
395
+ async cleanProductStatement(productId: string, statementDate: Date) {
396
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
397
+ await LoanStatementTransactionModel.deleteMany({
398
+ chargeId: { $in: charges.map((charge) => charge._id) },
399
+ date: statementDate,
400
+ isSystem: true,
401
+ });
402
+ }
403
+
404
+ async cleanProductAllStatements(chargesId: string[]) {
405
+ await LoanStatementTransactionModel.deleteMany({ chargeId: { $in: chargesId }, isSystem: true });
406
+ }
407
+
408
+ async getProductStatementPdfData(productId: string, start: string, end: string) {
409
+ const product = await LoanProduct.findById(productId).lean();
410
+ const borrower = await BorrowerModel.findById(product.borrowerId).lean();
411
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
412
+ const periodStart = dayjs(start);
413
+ const periodEnd = dayjs(end);
414
+
415
+ const statements = await LoanStatementTransactionModel.aggregate<{
416
+ date: Date,
417
+ amount: number,
418
+ chargeType: ELoanChargeType,
419
+ memo: string
420
+ }>([
421
+ {
422
+ $match: {
423
+ $and: [
424
+ { 'chargeId': { $in: charges.map((charge) => charge._id) } },
425
+ { 'date': { $gte: periodStart.toDate() } },
426
+ { 'date': { $lte: periodEnd.toDate() } },
427
+ ],
428
+ },
429
+ }, {
430
+ $sort: {
431
+ 'date': 1,
432
+ 'order': 1,
433
+ },
434
+ }, {
435
+ $lookup: {
436
+ from: 'loan_charges',
437
+ localField: 'chargeId',
438
+ foreignField: '_id',
439
+ as: 'charge',
440
+ },
441
+ }, {
442
+ $unwind: {
443
+ 'path': '$charge',
444
+ },
445
+ }, {
446
+ $addFields: {
447
+ 'chargeType': '$charge.chargeType',
448
+ 'chargeName': '$charge.name',
449
+ },
450
+ }, {
451
+ $unset: ['_id', 'productId', 'chargeId', 'charge', 'createdAt', 'updatedAt', '__v'],
452
+ },
453
+ ]);
454
+
455
+ const totalAmount = 0;
456
+ const { groupedData, otherStatements } = this.getGroupedData(statements);
457
+
458
+ const filter: ITransactionsFilter = {
459
+ borrowerId: borrower._id.toString(),
460
+ productId: product._id.toString(),
461
+ periodEnd: periodEnd.toDate(),
462
+ periodStart: periodStart.toDate(),
463
+ };
464
+
465
+ const transactions = await this.loanTransactionsService.getLoanTransactions(filter, null, true);
466
+
467
+ return {
468
+ groupedData,
469
+ otherStatements,
470
+ totalAmount,
471
+ transactions,
472
+ };
473
+ }
474
+
475
+ async getProductShortStatementRepresentation(productId: string, start: string, end: string) {
476
+ const product = await LoanProduct.findById(productId).lean();
477
+ const borrower = await BorrowerModel.findById(product.borrowerId).lean();
478
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
479
+ const periodStart = dayjs(start);
480
+ const periodEnd = dayjs(end);
481
+ const statements = await LoanStatementTransactionModel.aggregate<{
482
+ date: Date,
483
+ amount: number,
484
+ chargeType: ELoanChargeType,
485
+ memo: string
486
+ }>([
487
+ {
488
+ $match: {
489
+ $and: [
490
+ { 'chargeId': { $in: charges.map((charge) => charge._id) } },
491
+ { 'date': { $gte: periodStart.toDate() } },
492
+ { 'date': { $lte: periodEnd.toDate() } },
493
+ ],
494
+ },
495
+ }, {
496
+ $sort: {
497
+ 'date': 1,
498
+ 'order': 1,
499
+ },
500
+ }, {
501
+ $lookup: {
502
+ from: 'loan_charges',
503
+ localField: 'chargeId',
504
+ foreignField: '_id',
505
+ as: 'charge',
506
+ },
507
+ }, {
508
+ $unwind: {
509
+ 'path': '$charge',
510
+ },
511
+ }, {
512
+ $addFields: {
513
+ 'chargeType': '$charge.chargeType',
514
+ },
515
+ }, {
516
+ $unset: ['_id', 'productId', 'chargeId', 'charge', 'createdAt', 'updatedAt', '__v'],
517
+ },
518
+ ]);
519
+
520
+ const totalAmount = 0;
521
+ const { groupedData, otherStatements } = this.getGroupedData(statements);
522
+
523
+ const chargeMap = {
524
+ [ELoanChargeType.INTEREST_FEE]: 'INTEREST',
525
+ [ELoanChargeType.ADMIN_FEE]: 'ADMIN FEE',
526
+ [ELoanChargeType.UNUSED_LINE_FEE]: 'UNUSED',
527
+ [ELoanChargeType.WIRE_FEE]: 'WIRE FEES',
528
+ [ELoanChargeType.ANNUAL_LINE_FEE]: 'ANNUAL LINE',
529
+ [ELoanChargeType.OTHER]: 'OTHER',
530
+ [ELoanChargeType.RECOVERABLE]: 'RECOVERABLE',
531
+ };
532
+
533
+ const preparedStatement = Object.entries(groupedData).map(([chargeType, amount]) => {
534
+ return [chargeMap[chargeType], null, amount];
535
+ });
536
+
537
+ otherStatements.forEach((record) => {
538
+ preparedStatement.push([chargeMap[record.chargeType], record.memo, record.amount]);
539
+ });
540
+
541
+ const preparedStatementHeader = [
542
+ [`STATEMENT: ${borrower.name}`],
543
+ ['PERIOD FROM', periodStart.format('MM/DD/YYYY'), 'PERIOD TO', dayjs(end.slice(0, 10)).format('MM/DD/YYYY')],
544
+ [null],
545
+ ['THIS PERIOD STATEMENT', null, 'AMOUNT'],
546
+ ];
547
+
548
+ const preparedStatementTotal = [
549
+ [null],
550
+ [null, 'TOTAL', totalAmount],
551
+ [null],
552
+ [null],
553
+ ];
554
+
555
+ const preparedTransactionsHeader = [
556
+ [`LOAN TRANSACTIONS: ${product.name}`],
557
+ ['Date', 'Type', 'Amount', 'Reference', 'Bank', 'Balance'],
558
+ ];
559
+
560
+ const filter: ITransactionsFilter = {
561
+ borrowerId: borrower._id.toString(),
562
+ productId: product._id.toString(),
563
+ periodEnd: periodEnd.toDate(),
564
+ periodStart: periodStart.toDate(),
565
+ };
566
+
567
+ const transactions = await this.loanTransactionsService.getLoanTransactions(filter, null, true);
568
+ const preparedTransactions = transactions.map((transaction) => {
569
+ return [
570
+ dayjs(transaction.date).format('MM/DD/YYYY'),
571
+ transaction.transactionType,
572
+ transaction.amount,
573
+ transaction.reference,
574
+ transaction.bank?.name ?? '',
575
+ transaction.balance,
576
+ ];
577
+ });
578
+
579
+ return [
580
+ ...preparedStatementHeader,
581
+ ...preparedStatement,
582
+ ...preparedStatementTotal,
583
+ ...preparedTransactionsHeader,
584
+ ...preparedTransactions,
585
+ ];
586
+ }
587
+
588
+ async createStatementTransaction(transaction: Partial<ILoanStatementTransactionWithId>) {
589
+ const pureTransactions = createFilteredObject<ILoanStatementTransactionView>(transaction, LOAN_STATEMENT_TRANSACTION_FIELDS);
590
+ const lastOrder = await this.getLastTransactionOrder(transaction);
591
+ await this.saveLoanTransaction({ ...pureTransactions, isSystem: false, amountPaid: 0, order: lastOrder + 1 });
592
+ }
593
+
594
+ async updateStatementTransaction(transaction: Partial<ILoanStatementTransactionWithId>) {
595
+ const foundTransaction = await LoanStatementTransactionModel.findById(transaction._id).lean();
596
+ if (!foundTransaction) {
597
+ return;
598
+ }
599
+ const pureTransactions = foundTransaction.isSystem
600
+ ? createFilteredObject<ILoanStatementTransactionDoc>({
601
+ ...foundTransaction,
602
+ memo: transaction.memo,
603
+ }, LOAN_STATEMENT_TRANSACTION_FIELDS)
604
+ : createFilteredObject<ILoanStatementTransactionDoc>(transaction, LOAN_STATEMENT_TRANSACTION_FIELDS);
605
+ await this.saveLoanTransaction(pureTransactions as Partial<ILoanStatementTransactionWithId>);
606
+ }
607
+
608
+ async saveLoanTransaction(transaction: Partial<ILoanStatementTransactionWithId>) {
609
+ if (!transaction._id) {
610
+ delete transaction._id;
611
+ const updatedStatementTransaction = new LoanStatementTransactionModel(transaction);
612
+ await updatedStatementTransaction.save();
613
+ } else {
614
+ await LoanStatementTransactionModel.findByIdAndUpdate(transaction._id, transaction, { new: true });
615
+ }
616
+ const charge = await LoanCharge.findById(transaction.chargeId);
617
+ await this.loanStatementBalanceService.calculateProductBalance(charge.productId.toString(), new Date(transaction.date));
618
+ const statementDate = this.loanStatementStatusService.getStatementDateForDate(new Date(transaction.date));
619
+ await this.loanStatementStatusService.updateStatementStatus(charge.productId.toString(), statementDate, false);
620
+ }
621
+
622
+ async deleteStatementTransaction(transactionId: string) {
623
+ const foundTransaction = await LoanStatementTransactionModel.findById(transactionId).lean();
624
+ if (!foundTransaction) {
625
+ return;
626
+ }
627
+ const charge = await LoanCharge.findById(foundTransaction.chargeId);
628
+ await LoanStatementTransactionModel.findByIdAndDelete(transactionId);
629
+ await this.loanStatementBalanceService.calculateProductBalance(charge.productId.toString(), new Date(foundTransaction.date));
630
+ const statementDate = this.loanStatementStatusService.getStatementDateForDate(new Date(foundTransaction.date));
631
+ await this.loanStatementStatusService.updateStatementStatus(charge.productId.toString(), statementDate, false);
632
+ }
633
+
634
+ async addBalancesToStatements(transactionDocs: ILoanStatementTransactionDoc[]) {
635
+ return await Promise.all(transactionDocs.map(async (item) => {
636
+ const charge = await LoanCharge.findById(item.chargeId).lean();
637
+ const balances = await this.loanChargesService.getLoanProductBalance(charge.productId.toString(), item.date);
638
+ return { ...item, floatedBalance: balances.floatedBalance };
639
+ }));
640
+ }
641
+
642
+ async getLastTransactionOrder(transaction: Partial<ILoanStatementTransactionDoc>, onlySystem = false) {
643
+ const charge = await LoanCharge.findById(transaction.chargeId);
644
+ if (!charge) {
645
+ return null;
646
+ }
647
+ const charges = await this.loanChargesService.getLoanChargeForProduct(charge.productId.toString());
648
+ const getOnlySystem = () => onlySystem ? { isSystem: true } : {};
649
+ const lastTransactions = await LoanStatementTransactionModel.aggregate<ILoanStatementTransactionDoc>([
650
+ {
651
+ $match: {
652
+ 'chargeId': { $in: charges.map((charge) => charge._id) },
653
+ '_id': { $ne: transaction._id },
654
+ 'date': { $eq: new Date(transaction.date) },
655
+ ...getOnlySystem(),
656
+ },
657
+ }, {
658
+ $sort: {
659
+ 'date': -1,
660
+ 'order': -1,
661
+ },
662
+ }, {
663
+ $limit: 1,
664
+ },
665
+ ]);
666
+ if (!lastTransactions || lastTransactions.length === 0) {
667
+ return 0;
668
+ }
669
+ return lastTransactions[0].order;
670
+ }
671
+
672
+ async getPreviousTransaction(transaction: ILoanStatementTransactionDoc) {
673
+ const charge = await LoanCharge.findById(transaction.chargeId);
674
+ if (!charge) {
675
+ return null;
676
+ }
677
+ const charges = await this.loanChargesService.getLoanChargeForProduct(charge.productId.toString());
678
+
679
+ const lastTransactions = await LoanStatementTransactionModel.aggregate<ILoanStatementTransactionDoc>([
680
+ {
681
+ $match: {
682
+ 'chargeId': { $in: charges.map((charge) => charge._id) },
683
+ '_id': { $ne: transaction._id },
684
+ $or: [
685
+ {
686
+ $and: [
687
+ { 'date': { $eq: transaction.date } },
688
+ { 'order': { $lt: transaction.order } },
689
+ ],
690
+ },
691
+ {
692
+ 'date': { $lt: transaction.date },
693
+ },
694
+ ],
695
+ },
696
+ }, {
697
+ $sort: {
698
+ 'date': -1,
699
+ 'order': -1,
700
+ },
701
+ }, {
702
+ $limit: 1,
703
+ },
704
+ ]);
705
+ if (!lastTransactions || lastTransactions.length === 0) {
706
+ return null;
707
+ }
708
+ return lastTransactions[0];
709
+ }
710
+
711
+ async getTransactionsFromDate(productId: string, startDate: Date) {
712
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
713
+ return LoanStatementTransactionModel.aggregate<ILoanStatementTransactionDoc>([
714
+ {
715
+ $match: {
716
+ 'chargeId': { $in: charges.map((charge) => charge._id) },
717
+ 'date': { $gte: startDate },
718
+ },
719
+ }, {
720
+ $sort: {
721
+ 'date': 1,
722
+ 'order': 1,
723
+ },
724
+ },
725
+ ]);
726
+ }
727
+
728
+ async getTransactionsForPeriod(productId: string, period: { start: Date, end: Date }) {
729
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
730
+ return LoanStatementTransactionModel.aggregate<ILoanStatementTransactionDoc>([
731
+ {
732
+ $match: {
733
+ $and: [
734
+ { 'chargeId': { $in: charges.map((charge) => charge._id) } },
735
+ { 'date': { $gte: period.start } },
736
+ { 'date': { $lt: period.end } },
737
+ ],
738
+ },
739
+ }, {
740
+ $sort: {
741
+ 'date': 1,
742
+ 'order': 1,
743
+ },
744
+ },
745
+ ]);
746
+ }
747
+
748
+ async recalculateAllBalances(borrowerIds: string[], userId: string): Promise<void> {
749
+ for (const borrowerId of borrowerIds) {
750
+ const allProducts = await LoanProduct.find({ borrowerId }).lean();
751
+ for (const product of allProducts) {
752
+
753
+ const firstTransaction = await this.loanTransactionsService.getFirstTransaction(product._id.toString());
754
+ if (firstTransaction) {
755
+ try {
756
+ await this.loanTransactionsService.updateLoanTransaction(firstTransaction);
757
+ } catch (e) {
758
+ console.error(e);
759
+ }
760
+ }
761
+
762
+ const firstStatementTransaction = await this.getFirstStatementTransaction(product._id.toString());
763
+ if (firstStatementTransaction) {
764
+ try {
765
+ await this.updateStatementTransaction(firstStatementTransaction as Partial<ILoanStatementTransactionWithId>);
766
+ } catch (e) {
767
+ console.error(e);
768
+ }
769
+ }
770
+
771
+ const allPayments = await LoanPaymentModel.find({ productId: product._id.toString() }).lean();
772
+ for (const payment of allPayments) {
773
+ await this.loanPaymentsService.updateLoanPayment(payment as unknown as Partial<ILoanPaymentWithId>, userId);
774
+ }
775
+
776
+ }
777
+ }
778
+ }
779
+
780
+ async recalculateAllStatements(selectedPeriod: SelectablePeriod, borrowerIds: string[]): Promise<void> {
781
+ for (const borrowerId of borrowerIds) {
782
+ try {
783
+ await this.calculateBorrowerStatement(borrowerId, selectedPeriod);
784
+ } catch (e) {
785
+ console.error('recalculateAllStatements - ERROR, borrowerId', borrowerId, e);
786
+ }
787
+ }
788
+ }
789
+
790
+ async recalculateAllTermLoans(borrowerIds: string[]): Promise<void> {
791
+ for (const borrowerId of borrowerIds) {
792
+ try {
793
+ const products = await this.loanChargesService.getLoanProducts(borrowerId);
794
+ const termProducts = products.filter((product) => product.type === ELoanTypes.TERM);
795
+ await Promise.all(termProducts.map(async (termProduct) => {
796
+ await this.termLoanService.calculateTermLoan(termProduct._id.toString(), true);
797
+ }));
798
+ } catch (e) {
799
+ console.error('recalculateAllTermLoans - ERROR, borrowerId', borrowerId, e);
800
+ }
801
+ }
802
+ }
803
+
804
+ async getStatementBorrowersTransactions(start: string, end: string, selectedPeriod: string, selectedBorrowerIds: string[], ledgerType: ELedgerReportType) {
805
+ return getStatementBorrowersTransactions(start, end, selectedPeriod, selectedBorrowerIds, ledgerType);
806
+ }
807
+
808
+ async getBorrowerComplianceData(borrowerId: string, date: Date) {
809
+ const end = dayjs(date).utcOffset(0).subtract(1, 'month').endOf('month');
810
+ const start = end.startOf('month');
811
+ const products = await this.loanChargesService.getLoanProducts(borrowerId);
812
+ const data = {
813
+ productTotals: products.reduce((acc, product) => ({ ...acc, [product._id.toString()]: 0 }), <{
814
+ [productId: string]: number
815
+ }>{}),
816
+ total: 0,
817
+ totalPaid: 0,
818
+ dueDate: null,
819
+ };
820
+ await Promise.all(products.map(async (product) => {
821
+ const lastTransaction = await this.loanStatementBalanceService.getLastStatementTransactionForDate(product._id.toString(), end.toDate());
822
+ const transactions = await this.getTransactionsForPeriod(product._id.toString(), {
823
+ start: start.toDate(),
824
+ end: end.toDate(),
825
+ });
826
+ data.totalPaid = transactions.reduce((acc, t) => new Decimal(acc).add(t.amountPaid).toNumber(), 0);
827
+ if (lastTransaction) {
828
+ data.productTotals[product._id.toString()] = lastTransaction.balance ?? 0;
829
+ }
830
+ if (product.type === ELoanTypes.TERM) {
831
+ const termLoan = await this.termLoanService.getTermLoan(product._id.toString(), true);
832
+ if (termLoan) {
833
+ const lastDate = termLoan.calculated.slice().reverse().find((c) => dayjs(c.relevantStatement).isBefore(date));
834
+ if (lastDate) {
835
+ const calculatedResultsForDate = termLoan.calculated.filter((c) => c.relevantStatement === lastDate.relevantStatement);
836
+ const totalPrincipal = calculatedResultsForDate.reduce((acc, c) => new Decimal(acc).add(c.monthlyPrincipal).toNumber(), 0);
837
+ const totalPrincipalPaid = calculatedResultsForDate.reduce((totalAcc, c) => {
838
+ const totalMonthPaid = c.payments
839
+ ? c.payments.reduce((acc, p) => new Decimal(acc).add(p.amount).toNumber(), 0)
840
+ : 0;
841
+ return new Decimal(totalAcc).add(totalMonthPaid).toNumber();
842
+ }, 0);
843
+ data.productTotals[product._id.toString()] = new Decimal(data.productTotals[product._id.toString()]).add(totalPrincipal).sub(totalPrincipalPaid).toNumber();
844
+ }
845
+ }
846
+ }
847
+ }));
848
+ data.total = Object.values(data.productTotals).reduce((acc, productDue) => new Decimal(acc).add(productDue).toNumber(), 0);
849
+ return data;
850
+ }
851
+
852
+ private async getOutstandingStatements(borrowerIds: string[], selectedDate: Date, fullMonth: boolean) {
853
+ const results = await Promise.all(borrowerIds.map(async (borrowerId) => {
854
+ const borrower = await BorrowerModel.findById(borrowerId).lean();
855
+ const products = await this.loanChargesService.getLoanProducts(borrowerId);
856
+ return await Promise.all(products.map(async (product) => {
857
+ const relevantStatementEndMonth = fullMonth
858
+ ? dayjs(selectedDate).utcOffset(0).startOf('month').subtract(1, 'seconds')
859
+ : dayjs(selectedDate).utcOffset(0).endOf('month');
860
+ const relevantStatementEnd = fullMonth
861
+ ? dayjs(selectedDate).utcOffset(0).startOf('month').subtract(1, 'seconds')
862
+ : dayjs(selectedDate).utcOffset(0).endOf('day');
863
+ const relevantStatementStart = dayjs(selectedDate).utcOffset(0).startOf('month').subtract(1, 'seconds').startOf('month');
864
+
865
+ const paymentFilterEnd = dayjs(selectedDate).utcOffset(0).endOf('day');
866
+ const paymentFilterStart = relevantStatementStart;
867
+
868
+ const lastTransaction = await this.loanTransactionsService.getLastTransactionForDate(product._id.toString(), dayjs(selectedDate).endOf('day').toDate());
869
+ let actualBalance = 0;
870
+ if (lastTransaction) {
871
+ actualBalance = new Decimal(lastTransaction.balance).toDP(2).toNumber();
872
+ }
873
+
874
+ const statementFilter = {
875
+ productIds: [product._id.toString()],
876
+ start: relevantStatementStart.toDate(),
877
+ end: relevantStatementEnd.toDate(),
878
+ };
879
+ const paymentFilter = {
880
+ productIds: [product._id.toString()],
881
+ start: paymentFilterStart.toDate(),
882
+ end: paymentFilterEnd.toDate(),
883
+ };
884
+ const statementTransactions = await this.loanChargesService.getLoanStatementsForForProductWithCharges(statementFilter);
885
+ const statementPayments = await this.loanPaymentsService.getLoanPayments(paymentFilter, null);
886
+ const paidStatementIds = statementPayments.reduce((acc, s) => [...acc, ...s.paid.map((p) => p.statementId.toString())], []);
887
+ const fees = {
888
+ [ELoanChargeType.INTEREST_FEE]: 0,
889
+ [ELoanChargeType.ADMIN_FEE]: 0,
890
+ [ELoanChargeType.UNUSED_LINE_FEE]: 0,
891
+ [ELoanChargeType.WIRE_FEE]: 0,
892
+ [ELoanChargeType.OTHER]: 0,
893
+ [ELoanChargeType.RECOVERABLE]: 0,
894
+ [ELoanChargeType.ANNUAL_LINE_FEE]: 0,
895
+ };
896
+ const sumResults = statementTransactions.reduce((acc, t) => {
897
+ const amountPaid = paidStatementIds.includes(t._id.toString()) ? t.amountPaid : 0;
898
+ return {
899
+ ...acc,
900
+ [t.charge.chargeType]: new Decimal(acc[t.charge.chargeType] ?? 0).add(t.amount ?? 0).sub(amountPaid ?? 0).toNumber(),
901
+ };
902
+ }, fees);
903
+
904
+ let principal = 0;
905
+ let principalExpectedDate = null;
906
+ const termLoan = await TermLoanModel.findOne({ productId: product._id.toString(), actual: true });
907
+ if (termLoan) {
908
+ const calculatedTermLoan = await TermLoanCalculatedModel.findOne({
909
+ termLoanId: termLoan._id.toString(),
910
+ relevantStatement: relevantStatementEndMonth.format('YYYY-MM-DD'),
911
+ });
912
+ if (calculatedTermLoan) {
913
+ if (calculatedTermLoan.payments && calculatedTermLoan.payments.length > 0) {
914
+ const totalPayment = calculatedTermLoan.payments.reduce((acc, p) => new Decimal(acc ?? 0).add(p.amount ?? 0).toNumber(), 0);
915
+ principal = new Decimal(calculatedTermLoan.monthlyPrincipal).sub(totalPayment).toDP(2).toNumber();
916
+ } else {
917
+ principal = calculatedTermLoan.monthlyPrincipal;
918
+ principalExpectedDate = calculatedTermLoan.paymentDueDate;
919
+ }
920
+ }
921
+ }
922
+
923
+ return {
924
+ borrowerCode: borrower.code,
925
+ ...product,
926
+ actualBalance,
927
+ ...sumResults,
928
+ principal,
929
+ principalExpectedDate,
930
+ };
931
+ }));
932
+ }));
933
+ return results
934
+ .filter((res) => !!res)
935
+ .reduce((acc, res) => ([...acc, ...res]), [])
936
+ .sort((a, b) => (b.code.toUpperCase() < a.code.toUpperCase() ? 1 : -1));
937
+ }
938
+
939
+ async getOutstandingStatementsExcel(borrowerIds: string[], selectedDate: Date, fullMonth: boolean) {
940
+ const results = await this.getOutstandingStatements(borrowerIds, selectedDate, fullMonth);
941
+ const header = [
942
+ ['Date', dayjs(selectedDate).format(defaultDateFormat)],
943
+ ['', '', 'Outstanding statement'],
944
+ ['client ID', 'product ID', 'product name', 'balance', 'interest', 'admin', 'unused', 'wire & other', 'recoverable expenses', 'principal'],
945
+ ];
946
+ const onlyData = results.map((res) => {
947
+ return [
948
+ res.borrowerCode,
949
+ res.code,
950
+ res.name,
951
+ res.actualBalance,
952
+ res[ELoanChargeType.INTEREST_FEE],
953
+ res[ELoanChargeType.ADMIN_FEE],
954
+ res[ELoanChargeType.UNUSED_LINE_FEE],
955
+ new Decimal(res[ELoanChargeType.WIRE_FEE]).add(res[ELoanChargeType.OTHER]).add(res[ELoanChargeType.ANNUAL_LINE_FEE]).toNumber(),
956
+ res[ELoanChargeType.RECOVERABLE],
957
+ res.principal,
958
+ ];
959
+ });
960
+ return await this.uploadsService.convertDataToFile([{ data: [...header, ...onlyData] }]);
961
+ }
962
+
963
+ async getBorrowerProductTotals(borrowerId: string) {
964
+ return await this.getBorrowerComplianceData(borrowerId, new Date());
965
+ }
966
+
967
+ async getProductsTotals(productIds: string[]) {
968
+ const totals: { [productId: string]: number } = {};
969
+ await Promise.all(productIds.map(async (productId) => {
970
+ const product = await LoanProduct.findById(productId);
971
+ if (product) {
972
+ const borrowerData = await this.getBorrowerComplianceData(product.borrowerId.toString(), new Date());
973
+ Object.entries(borrowerData.productTotals).forEach(([id, value]) => {
974
+ if (productIds.includes(id)) {
975
+ totals[id] = value;
976
+ }
977
+ });
978
+ }
979
+ }));
980
+ return totals;
981
+ }
982
+
983
+ private getAccruedStatement(lastStatements: { amountPaid: number, amount: number }[]) {
984
+ const totals = lastStatements.reduce((acc, s) => {
985
+ return {
986
+ amountPaid: new Decimal(acc.amountPaid).add(s.amountPaid).toNumber(),
987
+ amount: new Decimal(acc.amount).add(s.amount).toNumber(),
988
+ };
989
+ }, { amountPaid: 0, amount: 0 });
990
+ return new Decimal(totals.amount).sub(totals.amountPaid).toNumber();
991
+ }
992
+
993
+ async calculateAccruedStatementForBBC(bbcDateId: string) {
994
+ const bbcDate = await BBCDateModel.findById(bbcDateId);
995
+ if (!bbcDate) {
996
+ return 0;
997
+ }
998
+ const charges = await this.loanChargesService.getLoanCharges(bbcDate.borrowerId.toString());
999
+ const chargesId = charges.map((c) => c._id);
1000
+
1001
+ const lastStatements = await LoanStatementTransactionModel.aggregate<{ amountPaid: number, amount: number }>([
1002
+ {
1003
+ $match: {
1004
+ 'date': { $lt: bbcDate.bbcDate },
1005
+ 'chargeId': { $in: chargesId },
1006
+ $expr: { $gt: ['$amount', '$amountPaid'] },
1007
+ },
1008
+ }, {
1009
+ $project: {
1010
+ _id: 0,
1011
+ amount: 1,
1012
+ amountPaid: 1,
1013
+ },
1014
+ },
1015
+ ]);
1016
+ return this.getAccruedStatement(lastStatements);
1017
+ }
1018
+
1019
+ async calculateAccruedStatementForBorrower(borrowerId: string) {
1020
+ const charges = await this.loanChargesService.getLoanCharges(borrowerId);
1021
+ const chargesId = charges.map((c) => c._id);
1022
+
1023
+ const lastStatements = await LoanStatementTransactionModel.aggregate<{ amountPaid: number, amount: number }>([
1024
+ {
1025
+ $match: {
1026
+ 'date': { $lt: new Date() },
1027
+ 'chargeId': { $in: chargesId },
1028
+ $expr: { $gt: ['$amount', '$amountPaid'] },
1029
+ },
1030
+ }, {
1031
+ $project: {
1032
+ _id: 0,
1033
+ amount: 1,
1034
+ amountPaid: 1,
1035
+ },
1036
+ },
1037
+ ]);
1038
+ return this.getAccruedStatement(lastStatements);
1039
+ }
1040
+ }