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,1042 @@
1
+ import _ from 'lodash';
2
+ import mongoose from 'mongoose';
3
+ import dayjs from 'dayjs';
4
+ import Decimal from 'decimal.js';
5
+
6
+ import { IEmail } from '../interfaces/email.interface';
7
+ import { calculateTimeZone } from '../helpers/date.helper';
8
+ import { roundToXDigits } from '../helpers/numbers.helper';
9
+ import { createFilteredObject } from '../helpers/common.helper';
10
+ import { IPaginatorOptions } from '../interfaces/collaterals.interface';
11
+ import { BBCDateModel } from '../models/BBCDate.model';
12
+ import { ELoanTypes } from '../enums/loan-types.enum';
13
+ import {
14
+ ELoanTransactionTypes,
15
+ ILoanTransaction,
16
+ ILoanTransactionDoc,
17
+ ILoanTransactionUpload,
18
+ ILoanTransactionView,
19
+ ILoanTransactionViewWithBank,
20
+ ILoanTransactionWithId,
21
+ LOAN_TRANSACTION_FIELDS,
22
+ LoanTransaction,
23
+ } from '../models/LoanTransaction.model';
24
+ import { ITEMS_PAGINATION } from '../db/collaterals.db';
25
+ import {
26
+ IPostponedTransaction,
27
+ PostponedTransaction,
28
+ } from '../models/PostponedTransactions.model';
29
+ import { LoanProduct } from '../models/LoanProducts.model';
30
+ import { ILoanProductDoc } from '../models/LoanProducts.model';
31
+ import { BorrowerModel } from '../models/Borrower.model';
32
+ import { BorrowerCompliance, IComplianceBorrowerDocument } from '../models/BorrowerCompliance.model';
33
+ import {
34
+ ILoanStatementTransaction,
35
+ LOAN_STATEMENT_TRANSACTION_FIELDS,
36
+ LoanStatementTransactionModel,
37
+ } from '../models/LoanStatementTransaction.model';
38
+ import { ILoanPaymentWithId, LoanPaymentModel } from '../models/LoanPayment.model';
39
+ import { fieldsToUnset, getPostponedTransactions } from '../db/loan-products.db';
40
+ import { getLastTransactionForDate } from '../db/loan-transactions.db';
41
+
42
+ import { IUploadOption, UploadsService } from './uploads.service';
43
+ import { ILoanTransactionFileDoc, LoanTransactionFileModel } from '../models/LoanTransactionFile.model';
44
+ import { AttachedFilesService } from './attached-files.service';
45
+ import { BanksService } from './banks.service';
46
+ import { BorrowerService } from './borrowers.service';
47
+ import { CalendarService } from './calendar.service';
48
+ import { LoanChargesService } from './loan-charges.service';
49
+ import { LoanPaymentsService } from './loan-payments.service';
50
+ import { LoanStatementService } from './loan-statement.service';
51
+ import { LoanStatementStatusService } from './loan-statement-status.service';
52
+ import { NodemailerService } from './nodemailer.service';
53
+ import { TermLoanService } from './term-loan.service';
54
+ import { LockService } from './lock.service';
55
+
56
+ interface ILoanTransactionsServiceConfig {
57
+ portfolioEmail: string;
58
+ }
59
+
60
+ export interface ITransactionsFilter {
61
+ periodStart: Date;
62
+ periodEnd: Date;
63
+ borrowerId: string;
64
+ productId: string;
65
+ amount?: number;
66
+ reference?: string;
67
+ }
68
+
69
+ export class LoanTransactionsService {
70
+
71
+ private readonly config: ILoanTransactionsServiceConfig;
72
+
73
+ constructor(
74
+ config: ILoanTransactionsServiceConfig,
75
+ private readonly attachedFilesService: AttachedFilesService,
76
+ private readonly banksService: BanksService,
77
+ private readonly borrowerService: BorrowerService,
78
+ private readonly calendarService: CalendarService,
79
+ private readonly loanChargesService: LoanChargesService,
80
+ private readonly getLoanPaymentsService: () => LoanPaymentsService,
81
+ private readonly getLoanStatementService: () => LoanStatementService,
82
+ private readonly loanStatementStatusService: LoanStatementStatusService,
83
+ private readonly getNodemailerService: () => NodemailerService,
84
+ private readonly lockService: LockService,
85
+ private readonly termLoanService: TermLoanService,
86
+ private readonly uploadsService: UploadsService,
87
+ ) {
88
+ }
89
+
90
+ async getTransactionsById(transactionId: string) {
91
+ return LoanTransaction.findById(transactionId).lean();
92
+ }
93
+
94
+ async getLoanTransactions<T extends boolean>(transactionsFilter: ITransactionsFilter, paginatorOptions: IPaginatorOptions, withBanks: T, isReverse = false, isMinified = false) {
95
+ const borrowerProducts = await this.loanChargesService.getLoanProducts(transactionsFilter.borrowerId);
96
+ const productIds = borrowerProducts
97
+ .filter((product) => transactionsFilter.productId ? product._id.toString() === transactionsFilter.productId : true)
98
+ .map((product) => product._id);
99
+
100
+ const optionalFilters = [];
101
+ if (transactionsFilter.amount) {
102
+ optionalFilters.push({ 'amount': transactionsFilter.amount });
103
+ }
104
+ if (transactionsFilter.reference) {
105
+ optionalFilters.push({ 'reference': { $regex: transactionsFilter.reference, $options: 'i' } });
106
+ }
107
+ const bankLookup = !withBanks
108
+ ? []
109
+ : [
110
+ {
111
+ $lookup: {
112
+ from: 'banks',
113
+ localField: 'bankId',
114
+ foreignField: '_id',
115
+ as: 'bank',
116
+ },
117
+ }, {
118
+ $unwind: {
119
+ path: '$bank',
120
+ preserveNullAndEmptyArrays: true,
121
+ },
122
+ },
123
+ ];
124
+
125
+ const projectedFields = isMinified
126
+ ? [{ $project: { _id: 1, date: 1, amount: 1, reference: 1, balance: 1, transactionType: 1 } }]
127
+ : [];
128
+
129
+ return LoanTransaction.aggregate<T extends true ? ILoanTransactionViewWithBank : ILoanTransactionView>([
130
+ {
131
+ $match: {
132
+ $and: [
133
+ { 'date': { $gte: transactionsFilter.periodStart } },
134
+ { 'date': { $lte: transactionsFilter.periodEnd } },
135
+ { 'productId': { $in: productIds } },
136
+ ...optionalFilters,
137
+ ],
138
+ },
139
+ }, {
140
+ $sort: {
141
+ 'date': isReverse ? -1 : 1,
142
+ 'order': isReverse ? -1 : 1,
143
+ 'createdAt': isReverse ? -1 : 1,
144
+ },
145
+ },
146
+ ...ITEMS_PAGINATION(paginatorOptions),
147
+ ...bankLookup,
148
+ {
149
+ $unset: fieldsToUnset,
150
+ },
151
+ ...projectedFields,
152
+ ]);
153
+ }
154
+
155
+ async getLoanTransactionsForFilter(periodStart: Date, periodEnd: Date, productId: string) {
156
+ return LoanTransaction.aggregate<ILoanTransactionView>([
157
+ {
158
+ $match: {
159
+ $and: [
160
+ { 'date': { $gte: periodStart } },
161
+ { 'date': { $lte: periodEnd } },
162
+ { 'productId': new mongoose.Types.ObjectId(productId) },
163
+ ],
164
+ },
165
+ }, {
166
+ $sort: {
167
+ 'date': -1,
168
+ 'order': -1,
169
+ 'createdAt': -1,
170
+ },
171
+ }, {
172
+ $unset: fieldsToUnset,
173
+ },
174
+ ]);
175
+ }
176
+
177
+ async getLoanTransactionsForProduct(productId: string) {
178
+ return LoanTransaction.aggregate<ILoanTransactionView>([
179
+ {
180
+ $match: {
181
+ $and: [
182
+ { 'productId': new mongoose.Types.ObjectId(productId) },
183
+ ],
184
+ },
185
+ }, {
186
+ $sort: {
187
+ 'date': -1,
188
+ 'order': -1,
189
+ 'createdAt': -1,
190
+ },
191
+ }, {
192
+ $unset: fieldsToUnset,
193
+ },
194
+ ]);
195
+ }
196
+
197
+ async getLoanTransactionFiles() {
198
+ const transactionFiles = await LoanTransactionFileModel.aggregate<ILoanTransactionFileDoc>([
199
+ {
200
+ $sort: {
201
+ 'effectiveDate': -1,
202
+ 'createdAt': 1,
203
+ },
204
+ }, {
205
+ $unset: [...fieldsToUnset.filter((field) => field !== 'createdAt')],
206
+ },
207
+ ]);
208
+ return await Promise.all(transactionFiles.map(async (file) => {
209
+ const effectiveDate = dayjs(file.effectiveDate).format('YYYY-MM-DD');
210
+ const attachedFiles = await this.attachedFilesService.getAttachedFilesByEffectiveDate(effectiveDate);
211
+ return { ...file, attachedFileIds: attachedFiles.map((attachedFile) => attachedFile._id.toString()) };
212
+ }));
213
+ }
214
+
215
+ async getDisbursementTransactions(productId: string, date: Date) {
216
+ return LoanTransaction.countDocuments(
217
+ {
218
+ date: date,
219
+ productId: new mongoose.Types.ObjectId(productId),
220
+ transactionType: ELoanTransactionTypes.DISBURSEMENT,
221
+ bankId: { $ne: null },
222
+ },
223
+ );
224
+ }
225
+
226
+ async getTotalLoanTransactions(transactionsFilter: ITransactionsFilter) {
227
+ const borrowerProducts = await this.loanChargesService.getLoanProducts(transactionsFilter.borrowerId);
228
+ const productIds = borrowerProducts
229
+ .filter((product) => transactionsFilter.productId ? product._id.toString() === transactionsFilter.productId : true)
230
+ .map((product) => product._id);
231
+ return LoanTransaction.countDocuments(
232
+ {
233
+ $and: [
234
+ { 'date': { $gte: transactionsFilter.periodStart } },
235
+ { 'date': { $lte: transactionsFilter.periodEnd } },
236
+ { 'productId': { $in: productIds } },
237
+ ],
238
+ },
239
+ );
240
+ }
241
+
242
+ async uploadLoanTransactions(uploadedItems: ILoanTransactionUpload[], uploadOption: IUploadOption, userId: string, shift = 0) {
243
+ const orderedItems = uploadedItems.map((item, index) => ({
244
+ ...item,
245
+ order: index + shift,
246
+ }));
247
+ const transactionsToRecalculate: { productId: string, date: Date, order: number, transactionId: string }[] = [];
248
+ const statementToSave: string[] = [];
249
+ await Promise.all(orderedItems.map(async (item) => {
250
+ const foundBank = await this.banksService.findBankByNumber(item.bankAccountNumber);
251
+
252
+ const productId = await this.loanChargesService.getLoanProductIdByCode(item.ledgerAccountCode);
253
+ if (productId) {
254
+ const itemToSave: Partial<ILoanTransaction> = {
255
+ ...(_.pick(item, LOAN_TRANSACTION_FIELDS)),
256
+ date: calculateTimeZone(item.date),
257
+ bankId: new mongoose.Types.ObjectId(String(foundBank._id)),
258
+ productId: new mongoose.Types.ObjectId(productId),
259
+ balance: 0,
260
+ floatedBalance: 0,
261
+ effectiveDate: await this.calculateEffectiveDate(item),
262
+ userId: new mongoose.Types.ObjectId(userId),
263
+ };
264
+ const newLoanTransaction = new LoanTransaction({
265
+ order: item.order,
266
+ ...itemToSave,
267
+ });
268
+ await newLoanTransaction.save();
269
+ transactionsToRecalculate.push({
270
+ productId: newLoanTransaction.productId.toString(),
271
+ date: newLoanTransaction.date,
272
+ transactionId: newLoanTransaction._id.toString(),
273
+ order: newLoanTransaction.order,
274
+ });
275
+ }
276
+
277
+ const chargeId = await this.loanChargesService.getLoanChargeIdByCode(item.ledgerAccountCode);
278
+ if (chargeId) {
279
+ const itemToSave: Partial<ILoanStatementTransaction> = {
280
+ ...(_.pick(item, LOAN_STATEMENT_TRANSACTION_FIELDS)),
281
+ date: calculateTimeZone(item.date),
282
+ chargeId: new mongoose.Types.ObjectId(chargeId),
283
+ memo: item.reference,
284
+ isSystem: false,
285
+ amountPaid: 0,
286
+ };
287
+ const loanStatementService = this.getLoanStatementService();
288
+ const lastOrder = await loanStatementService.getLastTransactionOrder(itemToSave);
289
+ itemToSave.order = lastOrder + 1;
290
+ const newLoanStatement = new LoanStatementTransactionModel(itemToSave);
291
+ await newLoanStatement.save();
292
+ statementToSave.push(newLoanStatement._id.toString());
293
+ }
294
+ }));
295
+
296
+ const newLoanTransactionFile = new LoanTransactionFileModel({
297
+ userUploaded: '',
298
+ transactionsId: transactionsToRecalculate.map((transaction) => new mongoose.Types.ObjectId(transaction.transactionId)),
299
+ statementIds: statementToSave.map((statement) => new mongoose.Types.ObjectId(statement)),
300
+ fileName: uploadOption.fileName,
301
+ sheetName: uploadOption.sheetName,
302
+ effectiveDate: uploadOption.effectiveDate,
303
+ });
304
+ await newLoanTransactionFile.save();
305
+
306
+ const earliestTransactionIds = new Map<string, { transactionId: string, date: Date }>();
307
+
308
+ transactionsToRecalculate.forEach((transaction) => {
309
+ const { productId, transactionId, date } = transaction;
310
+ if (earliestTransactionIds.has(productId)) {
311
+ if (date < earliestTransactionIds.get(productId).date) {
312
+ earliestTransactionIds.set(productId, { transactionId, date });
313
+ }
314
+ } else {
315
+ earliestTransactionIds.set(productId, { transactionId, date });
316
+ }
317
+ });
318
+
319
+ const earliestTransactionIdsList = Array
320
+ .from(earliestTransactionIds.values())
321
+ .map((entry) => entry.transactionId);
322
+
323
+ await Promise.all(earliestTransactionIdsList.map(async (transactionId) => {
324
+ setTimeout(async () => await this.recalculateBalance(transactionId));
325
+ }));
326
+ }
327
+
328
+ async normalizeLoanTransaction(transaction: ILoanTransactionDoc, userId: string) {
329
+ transaction.effectiveDate = await this.calculateEffectiveDate(transaction);
330
+ transaction.date = new Date(transaction.date);
331
+ switch (ELoanTransactionTypes[transaction.transactionType]) {
332
+ case ELoanTransactionTypes.DISBURSEMENT:
333
+ transaction.amount = new Decimal(transaction.amount).abs().toNumber();
334
+ break;
335
+ case ELoanTransactionTypes.COLLECTION:
336
+ transaction.amount = new Decimal(transaction.amount).abs().neg().toNumber();
337
+ break;
338
+ }
339
+ transaction.userId = new mongoose.Types.ObjectId(userId);
340
+ return transaction;
341
+ }
342
+
343
+ async createLoanTransaction(transaction: Partial<ILoanTransactionWithId>) {
344
+ const pureTransactions = createFilteredObject<ILoanTransactionView>(transaction, LOAN_TRANSACTION_FIELDS);
345
+ const product = await this.loanChargesService.getLoanProductById(pureTransactions.productId.toString());
346
+ if (product) {
347
+ const bank = await this.banksService.findBankByAccountCode(product.code);
348
+ if (bank) {
349
+ pureTransactions.bankId = bank._id;
350
+ }
351
+ }
352
+ pureTransactions.customerId = '';
353
+ return await this.saveLoanTransaction(pureTransactions);
354
+ }
355
+
356
+ async updateLoanTransaction(transaction: ILoanTransactionDoc) {
357
+ const pureTransactions = createFilteredObject<ILoanTransactionView>(transaction, LOAN_TRANSACTION_FIELDS);
358
+ await this.saveLoanTransaction(pureTransactions);
359
+ }
360
+
361
+ async saveLoanTransaction(transaction: Partial<ILoanTransactionWithId>): Promise<ILoanTransactionDoc> {
362
+ let startId: string;
363
+ let updatedTransaction: ILoanTransactionDoc;
364
+ if (!transaction._id) {
365
+ delete transaction._id;
366
+ const newTransaction = new LoanTransaction(transaction);
367
+ startId = newTransaction._id.toString();
368
+ updatedTransaction = await newTransaction.save();
369
+ } else {
370
+ startId = transaction._id.toString();
371
+ updatedTransaction = await LoanTransaction.findByIdAndUpdate(transaction._id, transaction, { new: true });
372
+ }
373
+ if (startId) {
374
+ await this.recalculateBalance(startId);
375
+ }
376
+ const statementDate = this.loanStatementStatusService.getStatementDateForDate(new Date(transaction.date));
377
+ await this.loanStatementStatusService.updateStatementStatus(transaction.productId.toString(), statementDate, false);
378
+ await this.termLoanService.setTermLoanExpected(transaction.productId.toString());
379
+ return updatedTransaction;
380
+ }
381
+
382
+ async recalculateProduct(productId: string) {
383
+ const firstTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
384
+ {
385
+ '$match': {
386
+ 'productId': new mongoose.Types.ObjectId(productId),
387
+ },
388
+ }, {
389
+ '$sort': {
390
+ 'date': 1,
391
+ },
392
+ }, {
393
+ '$limit': 1,
394
+ },
395
+ ]);
396
+
397
+ if (firstTransactions.length > 0) {
398
+ await this.recalculateBalance(firstTransactions[0]._id.toString());
399
+ }
400
+ }
401
+
402
+ async recalculateBalance(transactionId: string) {
403
+ const changedTransaction = await LoanTransaction.findById<ILoanTransactionDoc & {
404
+ createdAt: Date
405
+ }>(transactionId);
406
+ if (!changedTransaction) {
407
+ console.error(`no transactions with id ${transactionId}`);
408
+ return;
409
+ }
410
+ setTimeout(async () => {
411
+ await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isBalanceActual: false });
412
+ const previousTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
413
+ {
414
+ $match: {
415
+ 'productId': new mongoose.Types.ObjectId(changedTransaction.productId.toString()),
416
+ 'date': { $lt: new Date(changedTransaction.date) },
417
+ },
418
+ }, {
419
+ $sort: {
420
+ 'date': -1,
421
+ 'order': -1,
422
+ 'createdAt': -1,
423
+ },
424
+ }, {
425
+ $limit: 1,
426
+ },
427
+ ]);
428
+ let currentBalance = previousTransaction.length
429
+ ? previousTransaction[0].balance ?? 0
430
+ : 0;
431
+ const recalculatedTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
432
+ {
433
+ $match: {
434
+ 'productId': new mongoose.Types.ObjectId(changedTransaction.productId.toString()),
435
+ 'date': { $gte: new Date(changedTransaction.date) },
436
+ },
437
+ }, {
438
+ $sort: {
439
+ 'date': 1,
440
+ 'order': 1,
441
+ 'createdAt': 1,
442
+ },
443
+ },
444
+ ]);
445
+ for (const transaction of recalculatedTransactions) {
446
+ currentBalance = new Decimal(currentBalance).add(transaction.amount).toNumber();
447
+ await LoanTransaction.findByIdAndUpdate(transaction._id, { balance: currentBalance });
448
+ }
449
+ await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isBalanceActual: true });
450
+ });
451
+ setTimeout(async () => {
452
+ await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isFloatedBalanceActual: false });
453
+ const previousEqualBalancesTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
454
+ {
455
+ $match: {
456
+ 'productId': new mongoose.Types.ObjectId(changedTransaction.productId.toString()),
457
+ 'date': { $lt: new Date(changedTransaction.date) },
458
+ $expr: { $eq: ['$balance', '$floatedBalance'] },
459
+ },
460
+ }, {
461
+ $sort: {
462
+ 'date': 1,
463
+ 'order': 1,
464
+ 'createdAt': 1,
465
+ },
466
+ }, {
467
+ $limit: 1,
468
+ },
469
+ ]);
470
+ let currentFloatedBalance = 0;
471
+ if (previousEqualBalancesTransaction.length) {
472
+ const previousTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
473
+ {
474
+ $match: {
475
+ 'productId': new mongoose.Types.ObjectId(previousEqualBalancesTransaction[0].productId.toString()),
476
+ 'date': { $lt: new Date(previousEqualBalancesTransaction[0].date) },
477
+ },
478
+ }, {
479
+ $sort: {
480
+ 'date': -1,
481
+ 'order': -1,
482
+ 'createdAt': -1,
483
+ },
484
+ }, {
485
+ $limit: 1,
486
+ },
487
+ ]);
488
+ if (previousTransaction.length) {
489
+ currentFloatedBalance = previousTransaction[0].floatedBalance;
490
+ }
491
+ }
492
+ const initTransaction = previousEqualBalancesTransaction.length ? previousEqualBalancesTransaction[0] : changedTransaction;
493
+ const recalculatedTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
494
+ {
495
+ $match: {
496
+ 'productId': new mongoose.Types.ObjectId(initTransaction.productId.toString()),
497
+ 'date': { $gte: new Date(initTransaction.date) },
498
+ },
499
+ }, {
500
+ $sort: {
501
+ 'date': 1,
502
+ 'order': 1,
503
+ 'createdAt': 1,
504
+ },
505
+ },
506
+ ]);
507
+ for (const transaction of recalculatedTransactions) {
508
+ const totalPostponed = await getPostponedTransactions(transaction.date, transaction.productId.toString(), true);
509
+ switch (transaction.transactionType) {
510
+ case ELoanTransactionTypes.COLLECTION:
511
+ await this.cleanRecalculated(transaction._id.toString());
512
+ await this.createPostponedTransaction(transaction);
513
+ currentFloatedBalance = roundToXDigits(currentFloatedBalance + totalPostponed);
514
+ break;
515
+ case ELoanTransactionTypes.DISBURSEMENT:
516
+ currentFloatedBalance = roundToXDigits(currentFloatedBalance + transaction.amount + totalPostponed);
517
+ break;
518
+ case ELoanTransactionTypes.ADJUSTMENT:
519
+ currentFloatedBalance = roundToXDigits(currentFloatedBalance + transaction.amount + totalPostponed);
520
+ break;
521
+ }
522
+ await LoanTransaction.findByIdAndUpdate(transaction._id, { floatedBalance: currentFloatedBalance });
523
+ }
524
+ await LoanProduct.findByIdAndUpdate(changedTransaction.productId, { isFloatedBalanceActual: true });
525
+ }, 1_000);
526
+ }
527
+
528
+ async deleteLoanTransaction(transactionId: string, updateLoanPayment: boolean, userId = null) {
529
+ const currentTransaction = await LoanTransaction.findById(transactionId).lean();
530
+ if (!currentTransaction) {
531
+ return;
532
+ }
533
+ const nextTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
534
+ {
535
+ $match: {
536
+ 'productId': new mongoose.Types.ObjectId(currentTransaction.productId.toString()),
537
+ 'date': { $gte: new Date(currentTransaction.date) },
538
+ '_id': { $ne: new mongoose.Types.ObjectId(transactionId) },
539
+ },
540
+ }, {
541
+ $sort: {
542
+ 'date': 1,
543
+ 'order': 1,
544
+ 'createdAt': 1,
545
+ },
546
+ }, {
547
+ $limit: 1,
548
+ },
549
+ ]);
550
+ await LoanTransaction.findByIdAndDeleteWithUserId(transactionId, userId);
551
+ await PostponedTransaction.findOneAndDelete({ transactionId });
552
+ if (currentTransaction.loanPaymentId && updateLoanPayment) {
553
+ const payment = await LoanPaymentModel.findById(currentTransaction.loanPaymentId).lean();
554
+ if (payment) {
555
+ const loanPaymentsService = this.getLoanPaymentsService();
556
+ await loanPaymentsService.updateLoanPayment(payment as unknown as Partial<ILoanPaymentWithId>, userId);
557
+ }
558
+ }
559
+ if (nextTransaction.length) {
560
+ await this.recalculateBalance(nextTransaction[0]._id.toString());
561
+ }
562
+ const statementDate = this.loanStatementStatusService.getStatementDateForDate(new Date(currentTransaction.date));
563
+ await this.loanStatementStatusService.updateStatementStatus(currentTransaction.productId.toString(), statementDate, false);
564
+ await this.termLoanService.setTermLoanExpected(currentTransaction.productId.toString());
565
+ }
566
+
567
+ async createPostponedTransaction(transaction: ILoanTransactionDoc) {
568
+ const currentDay = dayjs(new Date(transaction.date)).format('YYYY-MM-DD');
569
+ const workingDate = await this.calendarService.getNextWorkingDay(currentDay);
570
+ const postponedTransaction: IPostponedTransaction = {
571
+ postponedToDate: workingDate,
572
+ transactionId: new mongoose.Types.ObjectId(String(transaction._id)),
573
+ productId: new mongoose.Types.ObjectId(String(transaction.productId)),
574
+ };
575
+ await PostponedTransaction.create(postponedTransaction);
576
+ }
577
+
578
+ async cleanRecalculated(transactionId: string) {
579
+ await PostponedTransaction.deleteMany({ transactionId });
580
+ }
581
+
582
+ async deleteLoanTransactionFile(transactionFileId: string, userId: string) {
583
+ const foundFile = await LoanTransactionFileModel.findById(transactionFileId).lean();
584
+ if (!foundFile) {
585
+ return null;
586
+ }
587
+ await Promise.all(foundFile.transactionsId.map(async (transactionId) => {
588
+ await this.deleteLoanTransaction(transactionId.toString(), true, userId);
589
+ }));
590
+ await Promise.all(foundFile.statementIds.map(async (statementId) => {
591
+ await LoanStatementTransactionModel.findByIdAndDelete(statementId);
592
+ }));
593
+ await LoanTransactionFileModel.findByIdAndDelete(transactionFileId);
594
+ }
595
+
596
+ async getFirstTransaction(productId: string, excludeAdjustments = false) {
597
+ const excludeFilter = excludeAdjustments
598
+ ? {
599
+ 'transactionType': {
600
+ $nin: ['ADJUSTMENT'],
601
+ },
602
+ }
603
+ : {};
604
+ const lastTransaction = await LoanTransaction.aggregate<ILoanTransactionDoc>([
605
+ {
606
+ $match: {
607
+ 'productId': new mongoose.Types.ObjectId(productId),
608
+ ...excludeFilter,
609
+ },
610
+ }, {
611
+ $sort: {
612
+ 'date': 1,
613
+ 'order': 1,
614
+ 'createdAt': 1,
615
+ },
616
+ }, {
617
+ $limit: 1,
618
+ },
619
+ ]);
620
+ if (!lastTransaction.length) {
621
+ return null;
622
+ }
623
+ return lastTransaction[0];
624
+ }
625
+
626
+ async getLastTransactionForDate(productId: string, date: Date = new Date(), excludeAdjustments = false) {
627
+ return getLastTransactionForDate(productId, date, excludeAdjustments);
628
+ }
629
+
630
+ async getLastDisbursementTransactionForDate(productId: string, startDate: Date, endDate: Date) {
631
+ return LoanTransaction.aggregate<ILoanTransactionDoc>([
632
+ {
633
+ $match: {
634
+ $and: [
635
+ { 'date': { $gte: startDate } },
636
+ { 'date': { $lte: endDate } },
637
+ { 'productId': new mongoose.Types.ObjectId(productId) },
638
+ { 'transactionType': { $ne: ELoanTransactionTypes.COLLECTION } },
639
+ ],
640
+ },
641
+ }, {
642
+ $sort: {
643
+ 'date': -1,
644
+ 'order': -1,
645
+ 'createdAt': -1,
646
+ },
647
+ },
648
+ ]);
649
+ }
650
+
651
+ async getPaymentsForPeriod(productId: string, startDate: Date, endDate: Date) {
652
+ return LoanTransaction.aggregate<ILoanTransactionDoc>([
653
+ {
654
+ $match: {
655
+ $and: [
656
+ { 'date': { $gte: startDate } },
657
+ { 'date': { $lte: endDate } },
658
+ { 'productId': new mongoose.Types.ObjectId(productId) },
659
+ { 'transactionType': ELoanTransactionTypes.COLLECTION },
660
+ ],
661
+ },
662
+ }, {
663
+ $sort: {
664
+ 'date': -1,
665
+ 'order': -1,
666
+ 'createdAt': -1,
667
+ },
668
+ },
669
+ ]);
670
+ }
671
+
672
+ async getLoanBalanceForBBCDateId(BBCDateId: string) {
673
+ const bbcDate = await BBCDateModel.findById(BBCDateId);
674
+ const loanProducts = await LoanProduct.aggregate<ILoanProductDoc>([
675
+ {
676
+ '$match': {
677
+ 'borrowerId': new mongoose.Types.ObjectId(bbcDate.borrowerId),
678
+ },
679
+ },
680
+ ]);
681
+ const balances = {
682
+ [ELoanTypes.TERM]: 0,
683
+ [ELoanTypes.REVOLVER]: 0,
684
+ };
685
+ await Promise.all(loanProducts.map(async (product) => {
686
+ const lastTransaction = await this.getLastTransactionForDate(product._id.toString(), dayjs(bbcDate.bbcDate).utcOffset(0).endOf('day').toDate());
687
+ if (lastTransaction) {
688
+ balances[ELoanTypes[product.type]] = new Decimal(balances[ELoanTypes[product.type]]).add(lastTransaction.balance).toNumber();
689
+ }
690
+ }));
691
+ return balances;
692
+ }
693
+
694
+ async getLoanBalanceForBBCDateIdPerProduct(BBCDateId: string, productType?: ELoanTypes): Promise<{
695
+ [productId: string]: { balance: number, product: ILoanProductDoc }
696
+ }> {
697
+ const bbcDate = await BBCDateModel.findById(BBCDateId);
698
+ const productFilter = productType ? { 'type': productType } : {};
699
+ const loanProducts = await LoanProduct.aggregate<ILoanProductDoc>([
700
+ {
701
+ '$match': {
702
+ 'borrowerId': new mongoose.Types.ObjectId(bbcDate.borrowerId),
703
+ ...productFilter,
704
+ },
705
+ },
706
+ ]);
707
+ const balances = {};
708
+ await Promise.all(loanProducts.map(async (product) => {
709
+ const lastTransaction = await this.getLastTransactionForDate(product._id.toString(), dayjs(bbcDate.bbcDate).utcOffset(0).endOf('day').toDate());
710
+ balances[product._id.toString()] = { balance: lastTransaction.balance, product };
711
+ }, {}));
712
+ return balances;
713
+ }
714
+
715
+ async getLoanBalanceForBorrowerId(borrowerId: string) {
716
+ const products = await this.loanChargesService.getLoanProducts(borrowerId);
717
+ const extraInfo = {
718
+ [ELoanTypes.REVOLVER]: {
719
+ balance: 0,
720
+ lastActivityDate: null,
721
+ },
722
+ [ELoanTypes.TERM]: {
723
+ balance: 0,
724
+ lastActivityDate: null,
725
+ },
726
+ accruedStatement: {
727
+ balance: 0,
728
+ lastActivityDate: null,
729
+ },
730
+ effectiveLoanBalance: {
731
+ balance: 0,
732
+ lastActivityDate: null,
733
+ },
734
+ };
735
+ if (products.length === 0) {
736
+ return extraInfo;
737
+ }
738
+ for (const product of products) {
739
+ try {
740
+ const lastTransaction = await this.getLastTransactionForDate(product._id.toString());
741
+ if (lastTransaction) {
742
+ extraInfo[product.type].balance = new Decimal(extraInfo[product.type].balance).add(lastTransaction.balance).toNumber();
743
+ }
744
+ const latRegularTransaction = await this.getLastTransactionForDate(product._id.toString(), new Date(), true);
745
+ if (latRegularTransaction) {
746
+ if (!extraInfo[product.type].lastActivityDate || latRegularTransaction.date > extraInfo[product.type].lastActivityDate) {
747
+ extraInfo[product.type].lastActivityDate = latRegularTransaction.date;
748
+ }
749
+ }
750
+ } catch (error) {
751
+ console.error('Error:', error);
752
+ }
753
+ }
754
+ const loanStatementService = this.getLoanStatementService();
755
+ extraInfo.accruedStatement.balance = await loanStatementService.calculateAccruedStatementForBorrower(borrowerId);
756
+ extraInfo.effectiveLoanBalance.balance = new Decimal(extraInfo[ELoanTypes.REVOLVER].balance).add(extraInfo.accruedStatement.balance).toNumber();
757
+ return extraInfo;
758
+ }
759
+
760
+ async getUnsentTransactionForDate(borrowerId: string, date: Date) {
761
+ const products = await this.loanChargesService.getBorrowersLoanProducts(borrowerId);
762
+ return LoanTransaction.aggregate<ILoanTransactionDoc>([
763
+ {
764
+ $match:
765
+ {
766
+ 'productId': { $in: products.map((product) => product._id) },
767
+ 'isSent': { $in: [false, null] },
768
+ 'date': { $gte: date },
769
+ },
770
+ }, {
771
+ $lookup: {
772
+ from: 'loan_products',
773
+ localField: 'productId',
774
+ foreignField: '_id',
775
+ as: 'product',
776
+ },
777
+ }, {
778
+ $unwind: {
779
+ path: '$product',
780
+ },
781
+ },
782
+ ]);
783
+ }
784
+
785
+ async getTransactionReport(transactionIds: string[], borrowerId: string, effectiveDate: Date) {
786
+ const transactionIdsObjects = transactionIds.map((t) => new mongoose.Types.ObjectId(t));
787
+ const uploadedTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
788
+ {
789
+ $match: {
790
+ '_id': {
791
+ $in: transactionIdsObjects,
792
+ },
793
+ },
794
+ }, {
795
+ $lookup: {
796
+ from: 'loan_products',
797
+ localField: 'productId',
798
+ foreignField: '_id',
799
+ as: 'product',
800
+ },
801
+ }, {
802
+ $unwind: {
803
+ path: '$product',
804
+ },
805
+ }, {
806
+ $match: {
807
+ 'product.borrowerId': new mongoose.Types.ObjectId(borrowerId),
808
+ },
809
+ },
810
+ ]);
811
+
812
+ const products = await this.loanChargesService.getBorrowersLoanProducts(borrowerId);
813
+
814
+ const backDatedTransactions = await LoanTransaction.aggregate<ILoanTransactionDoc>([
815
+ {
816
+ $match:
817
+ {
818
+ '_id': { $nin: transactionIdsObjects },
819
+ 'productId': { $in: products.map((product) => product._id) },
820
+ 'isSent': { $in: [false, null] },
821
+ 'date': { $lte: effectiveDate },
822
+ },
823
+ }, {
824
+ $lookup: {
825
+ from: 'loan_products',
826
+ localField: 'productId',
827
+ foreignField: '_id',
828
+ as: 'product',
829
+ },
830
+ }, {
831
+ $unwind: {
832
+ path: '$product',
833
+ },
834
+ },
835
+ ]);
836
+
837
+ const productGroups = products
838
+ .reduce((acc, product) => {
839
+ acc[product._id.toString()] = [];
840
+ return acc;
841
+ }, {});
842
+
843
+ const headerRow = [
844
+ 'date',
845
+ 'amount',
846
+ 'reference',
847
+ 'transaction type',
848
+ ];
849
+
850
+ const backDatedTransactionsTitle = [[''], ['Backdated transactions']];
851
+
852
+ const transactionIdsToMark = [...uploadedTransactions, ...backDatedTransactions].map((tr) => tr._id.toString());
853
+
854
+ const mappedTransactions: { [productId: string]: (string | number | Date)[] } = uploadedTransactions
855
+ .filter((t) => !!t)
856
+ .reduce((acc, t) => {
857
+ const reportRow = [
858
+ t.date,
859
+ t.amount,
860
+ t.reference,
861
+ t.transactionType,
862
+ ];
863
+ acc[t.productId.toString()] = [...acc[t.productId.toString()], reportRow];
864
+ return acc;
865
+ }, _.cloneDeep(productGroups));
866
+
867
+ const mappedBackDatedTransactions: { [productId: string]: (string | number | Date)[] } = backDatedTransactions
868
+ .filter((t) => !!t)
869
+ .reduce((acc, t) => {
870
+ const reportRow = [
871
+ t.date,
872
+ t.amount,
873
+ t.reference,
874
+ t.transactionType,
875
+ ];
876
+ acc[t.productId.toString()] = [...acc[t.productId.toString()], reportRow];
877
+ return acc;
878
+ }, _.cloneDeep(productGroups));
879
+
880
+ const transactions = products.map((product) => {
881
+ const uploaded = mappedTransactions[product._id.toString()];
882
+ const backdated = mappedBackDatedTransactions[product._id.toString()];
883
+ return {
884
+ [product.name]: [
885
+ headerRow,
886
+ ...uploaded,
887
+ ...backDatedTransactionsTitle,
888
+ headerRow,
889
+ ...backdated,
890
+ ],
891
+ };
892
+ });
893
+ return { transactionIdsToMark, transactions };
894
+ }
895
+
896
+ async getBorrowerIdsForFile(transactionFileId: string) {
897
+ const transactionFile = await LoanTransactionFileModel.findById(transactionFileId).lean();
898
+ if (!transactionFile) {
899
+ console.log('unknown file id', transactionFileId);
900
+ return;
901
+ }
902
+
903
+ const productIdsSet = new Set<string>();
904
+ await Promise.all(transactionFile.transactionsId.map(async (transactionId) => {
905
+ const transaction = await LoanTransaction.findById(transactionId);
906
+ if (transaction) {
907
+ productIdsSet.add(transaction.productId.toString());
908
+ }
909
+ }));
910
+ const productIds = Array.from(productIdsSet.values());
911
+
912
+ const borrowerIdsSet = new Map<string, boolean>();
913
+ await Promise.all(productIds.map(async (productId) => {
914
+ const product = await LoanProduct.findById(productId);
915
+ if (product) {
916
+ const foundBorrower: IComplianceBorrowerDocument = await BorrowerCompliance.findOne({ borrower: product.borrowerId.toString() });
917
+ const isActive = foundBorrower.isEmailingActive && foundBorrower.isDailyTransactionsEmailingActive;
918
+ borrowerIdsSet.set(product.borrowerId.toString(), isActive);
919
+ }
920
+ }));
921
+ return Object.fromEntries(borrowerIdsSet);
922
+ }
923
+
924
+ async getEmailBorrowers(transactionFileId: string) {
925
+ return await this.getBorrowerIdsForFile(transactionFileId);
926
+ }
927
+
928
+ async sendReport(transactionFileId: string, selectedBorrowerIds: string[]) {
929
+ const transactionFile = await LoanTransactionFileModel.findById(transactionFileId).lean();
930
+ if (!transactionFile) {
931
+ console.log('unknown file id', transactionFileId);
932
+ return;
933
+ }
934
+ const borrowerIds = await this.getBorrowerIdsForFile(transactionFileId);
935
+ const transactionIds = transactionFile.transactionsId.map((id) => id.toString());
936
+
937
+ await Promise.all(Object.entries(borrowerIds).map(async ([borrowerId, active]) => {
938
+ if (!selectedBorrowerIds.includes(borrowerId)) {
939
+ return;
940
+ }
941
+ if (!active) {
942
+ return;
943
+ }
944
+ const borrower = await BorrowerModel.findById(borrowerId).lean();
945
+ if (!borrower) {
946
+ console.log('unknown borrower', borrowerId);
947
+ return;
948
+ }
949
+ const foundBorrower: IComplianceBorrowerDocument = await BorrowerCompliance.findOne({ borrower: borrowerId });
950
+ if (!foundBorrower) {
951
+ return;
952
+ }
953
+ const addresses = [...foundBorrower.mainEmails, ...foundBorrower.financialEmails].reduce((acc, email) => email.isActive ? [...acc, email.email] : acc, <string[]>[]);
954
+ const title = `${borrower.code} ACTIVITY REPORT: ${dayjs(transactionFile.createdAt).format('MM-DD-YYYY')}`;
955
+ const fileName = title;
956
+ const subject = title;
957
+ let text = 'Attached is your daily activity report. Please log into the portal to view your account balance.';
958
+ const {
959
+ transactions,
960
+ transactionIdsToMark,
961
+ } = await this.getTransactionReport(transactionIds, borrowerId, new Date(transactionFile.effectiveDate));
962
+ const attachedFile = await this.uploadsService.convertDataToFile(transactions);
963
+ const attachment = { filename: `${fileName}.xlsx`, content: attachedFile };
964
+ const attachedPDFs = await this.attachedFilesService.getAttachedRealFilesByTransactionFileId(borrowerId, transactionFile.effectiveDate);
965
+ if (attachedPDFs.length > 0) {
966
+ text = text + ' Uploaded PDF files are available for downloading.';
967
+ }
968
+ const email: IEmail = { addresses: [...addresses, this.config.portfolioEmail], subject, text };
969
+ const nodemailerService = this.getNodemailerService();
970
+ await nodemailerService.sendLoanUploadReport(email, [attachment], borrowerId);
971
+ await Promise.all(transactionIdsToMark.map(async (id) => {
972
+ await this.switchTransactionSentStatus(id, true);
973
+ }));
974
+ }));
975
+ }
976
+
977
+ async sendAllReport(effectiveDate: Date) {
978
+ const borrowers = await this.borrowerService.getActiveBorrowers();
979
+ await Promise.all(borrowers.map(async (borrower) => {
980
+ if (!borrower.active) {
981
+ return;
982
+ }
983
+ const foundBorrower: IComplianceBorrowerDocument = await BorrowerCompliance.findOne({ borrower: borrower._id.toString() });
984
+ if (!foundBorrower) {
985
+ return;
986
+ }
987
+ const addresses = [...foundBorrower.mainEmails, ...foundBorrower.financialEmails].reduce((acc, email) => email.isActive ? [...acc, email.email] : acc, <string[]>[]);
988
+ const title = `${borrower.code} ACTIVITY REPORT: ${dayjs(effectiveDate).format('MM-DD-YYYY')}`;
989
+ const fileName = title;
990
+ const subject = title;
991
+ const dailyTransactions = await this.getUnsentTransactionForDate(borrower._id.toString(), effectiveDate);
992
+ const transactionIds = dailyTransactions.map((tr) => tr._id.toString());
993
+ const {
994
+ transactions,
995
+ transactionIdsToMark,
996
+ } = await this.getTransactionReport(transactionIds, borrower._id.toString(), effectiveDate);
997
+ if (transactions.length === 0 || transactionIdsToMark.length === 0) {
998
+ return;
999
+ }
1000
+ const attachedFile = await this.uploadsService.convertDataToFile(transactions);
1001
+ const attachment = { filename: `${fileName}.xlsx`, content: attachedFile };
1002
+ const attachedPDFs = await this.attachedFilesService.getAttachedRealFilesByTransactionFileId(borrower._id.toString(), dayjs(effectiveDate).format('YYYY-MM-DD'));
1003
+ let text = 'Attached is your daily activity report. Please log into the portal to view your account balance.';
1004
+ if (attachedPDFs.length > 0) {
1005
+ text = text + ' Uploaded PDF files are available for downloading.';
1006
+ }
1007
+ const email: IEmail = { addresses: [...addresses, this.config.portfolioEmail], subject, text };
1008
+ const nodemailerService = this.getNodemailerService();
1009
+ await nodemailerService.sendLoanUploadReport(email, [attachment], borrower._id.toString());
1010
+ await Promise.all(transactionIdsToMark.map(async (id) => {
1011
+ await this.switchTransactionSentStatus(id, true);
1012
+ }));
1013
+ }));
1014
+ }
1015
+
1016
+ async calculateEffectiveDate(transaction: Partial<ILoanTransactionDoc>): Promise<Date> {
1017
+ if (transaction.transactionType !== ELoanTransactionTypes.COLLECTION) {
1018
+ return new Date(transaction.date);
1019
+ }
1020
+ const nextDate = await this.calendarService.getNextWorkingDay(dayjs(transaction.date).format('YYYY-MM-DD'));
1021
+ return new Date(nextDate);
1022
+ }
1023
+
1024
+ async switchTransactionSentStatus(transactionId: string, isSent?: boolean) {
1025
+ if (isSent) {
1026
+ await LoanTransaction.findByIdAndUpdate(transactionId, { isSent });
1027
+ } else {
1028
+ const transaction = await LoanTransaction.findById(transactionId).lean();
1029
+ if (transaction) {
1030
+ await LoanTransaction.findByIdAndUpdate(transactionId, { isSent: !transaction.isSent ?? false });
1031
+ }
1032
+ }
1033
+ }
1034
+
1035
+ async checkLockStatus(transaction: ILoanTransactionDoc) {
1036
+ const product = await this.loanChargesService.getLoanProductById(transaction.productId.toString());
1037
+ if (product) {
1038
+ return await this.lockService.isDateLocked(new Date(transaction.date), product.borrowerId.toString());
1039
+ }
1040
+ return false;
1041
+ };
1042
+ }