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,642 @@
1
+ import Decimal from 'decimal.js';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import pdfMake from 'pdfmake/build/pdfmake';
5
+ import vfsFonts from 'pdfmake/build/vfs_fonts';
6
+ import { TDocumentDefinitions } from 'pdfmake/interfaces';
7
+ import dayjs from 'dayjs';
8
+
9
+ import { LoanProduct } from '../models/LoanProducts.model';
10
+ import { BorrowerModel } from '../models/Borrower.model';
11
+ import { ELoanChargeType } from '../enums/loan-charge-type.enum';
12
+ import { formatNumbers } from '../helpers/numbers.helper';
13
+ import { ELoanTypes } from '../enums/loan-types.enum';
14
+ import { CUSTOMER_NUMBER_SEPARATOR } from '../db/receivables.db';
15
+
16
+ import { EFinancialIndex, FinancialIndexesService } from './financial-indexes.service';
17
+ import { LoanChargesService } from './loan-charges.service';
18
+ import { LoanStatementService } from './loan-statement.service';
19
+ import { LoanStatementBalanceService } from './loan-statement-balance.service';
20
+ import { LoanTransactionsService } from './loan-transactions.service';
21
+ import { OrganizationsService } from './organizations.service';
22
+ import { TermLoanService } from './term-loan.service';
23
+
24
+ interface IPdfServiceConfig {
25
+ rootDir: string;
26
+ title: string;
27
+ }
28
+
29
+ const mappedChargeTypes = {
30
+ [ELoanChargeType.INTEREST_FEE]: 'Interest',
31
+ [ELoanChargeType.ADMIN_FEE]: 'Loan Administration Fee',
32
+ [ELoanChargeType.UNUSED_LINE_FEE]: 'Unused Line Fee',
33
+ [ELoanChargeType.WIRE_FEE]: 'Wire fee',
34
+ [ELoanChargeType.ANNUAL_LINE_FEE]: 'Annual Line Fee',
35
+ [ELoanChargeType.OTHER]: 'Other',
36
+ [ELoanChargeType.RECOVERABLE]: 'Recoverable',
37
+ };
38
+
39
+ export const defaultPDFPageMarginSize = 30;
40
+
41
+ export const defaultPDFPageSize = {
42
+ width: 841.890, // Page width in points for A4 size (8.27 inches)
43
+ height: 595.276, // Page height in points for A4 size (11.69 inches)
44
+ };
45
+
46
+ export const defaultPDFPageSettings = {
47
+ margin: defaultPDFPageMarginSize,
48
+ docWidth: defaultPDFPageSize.width - defaultPDFPageMarginSize * 2,
49
+ docHeight: defaultPDFPageSize.width - defaultPDFPageMarginSize * 2,
50
+ };
51
+
52
+ const defaultDateFormat = 'MM/DD/YYYY';
53
+
54
+ export class PdfService {
55
+ private readonly config: IPdfServiceConfig;
56
+ margin = defaultPDFPageMarginSize;
57
+ docWidth: number;
58
+ docHeight: number;
59
+
60
+ constructor(
61
+ config: IPdfServiceConfig,
62
+ private readonly financialIndexesService: FinancialIndexesService,
63
+ private readonly loanChargesService: LoanChargesService,
64
+ private readonly loanStatementBalanceService: LoanStatementBalanceService,
65
+ private readonly loanStatementService: LoanStatementService,
66
+ private readonly loanTransactionsService: LoanTransactionsService,
67
+ private readonly organizationsService: OrganizationsService,
68
+ private readonly termLoanService: TermLoanService,
69
+ ) {
70
+ this.config = config;
71
+ }
72
+
73
+ public addHeaderNew(organizationSubfolder: string) {
74
+
75
+ const imageData = (imageUrl: string) => {
76
+ const imagePath = path.join(this.config.rootDir, imageUrl);
77
+ const imageBuffer = fs.readFileSync(imagePath);
78
+ const imageBase64 = Buffer.from(imageBuffer).toString('base64');
79
+ return `data:image/png;base64,${imageBase64}`;
80
+ };
81
+
82
+ const logoElement = {
83
+ image: imageData(`./public/images/${organizationSubfolder}/logo.png`),
84
+ width: 150,
85
+ };
86
+
87
+ const gradientElement = {
88
+ image: imageData(`./public/images/${organizationSubfolder}/gradient.png`),
89
+ width: this.docWidth * 0.8,
90
+ height: 60,
91
+ };
92
+
93
+ return {
94
+ alignment: 'justify',
95
+ columns: [
96
+ logoElement,
97
+ gradientElement,
98
+ ],
99
+ };
100
+ }
101
+
102
+ private async getProductStatementData(productId: string, start: string, end: string) {
103
+ const product = await LoanProduct.findById(productId).lean();
104
+ const borrower = await BorrowerModel.findById(product.borrowerId).lean();
105
+ const charges = await this.loanChargesService.getLoanChargeForProduct(productId);
106
+ const periodStart = dayjs(start);
107
+
108
+ const getInterestFee = (adminFeePercent: number) => {
109
+ const fullFee = new Decimal(primeIndex).mul(100).add(adminFeePercent ?? 0).toDP(4).toNumber();
110
+ if (product.type === ELoanTypes.TERM) {
111
+ const minPercent = (product.minPercent ?? 0) === 0 ? 0 : new Decimal(product.minPercent).mul(100).toNumber() ?? 0;
112
+ const maxPercent = (product.maxPercent ?? 0) === 0 ? 100 : new Decimal(product.maxPercent).mul(100).toNumber();
113
+ return Math.min(Math.max(fullFee, minPercent), maxPercent);
114
+ }
115
+ return fullFee;
116
+ };
117
+
118
+ const getAdminFeeMin = () => {
119
+ const adminFee = charges.find((charge) => charge.chargeType === ELoanChargeType.ADMIN_FEE);
120
+ if (!adminFee) {
121
+ return '';
122
+ }
123
+ if (!!adminFee.minimumAmount && adminFee.minimumAmount > 0) {
124
+ return `(min = ${adminFee.minimumAmount}$)`;
125
+ }
126
+ return '';
127
+ };
128
+
129
+ const {
130
+ groupedData,
131
+ otherStatements,
132
+ transactions,
133
+ } = await this.loanStatementService.getProductStatementPdfData(productId, start, end);
134
+
135
+ const chargeInfo = (chargeType: ELoanChargeType) => {
136
+ const foundCharge = charges.find((charge) => charge.chargeType === chargeType);
137
+ if (!foundCharge) {
138
+ return { percent: null, minimumAmount: null };
139
+ }
140
+ return { percent: foundCharge.percent * 100, minimumAmount: foundCharge.minimumAmount };
141
+ };
142
+ const primeIndex = await this.financialIndexesService.getFinancialIndexValue(EFinancialIndex.PRIME_RATE) ?? 0;
143
+
144
+ const chargeMap: { [charge: string]: { title: string, explanation: string } } = {
145
+ [ELoanChargeType.INTEREST_FEE]: {
146
+ title: mappedChargeTypes[ELoanChargeType.INTEREST_FEE],
147
+ explanation: `${getInterestFee(chargeInfo(ELoanChargeType.INTEREST_FEE).percent)}% - daily charge`,
148
+ },
149
+ [ELoanChargeType.ADMIN_FEE]: {
150
+ title: mappedChargeTypes[ELoanChargeType.ADMIN_FEE],
151
+ explanation: `${chargeInfo(ELoanChargeType.ADMIN_FEE).percent}% - per month, average monthly balance ${getAdminFeeMin()}`,
152
+ },
153
+ [ELoanChargeType.UNUSED_LINE_FEE]: {
154
+ title: mappedChargeTypes[ELoanChargeType.UNUSED_LINE_FEE],
155
+ explanation: `${chargeInfo(ELoanChargeType.UNUSED_LINE_FEE).percent}% - daily charge on unused amount`,
156
+ },
157
+ [ELoanChargeType.WIRE_FEE]: {
158
+ title: mappedChargeTypes[ELoanChargeType.WIRE_FEE],
159
+ explanation: `${chargeInfo(ELoanChargeType.WIRE_FEE).minimumAmount}$ per disbursement`,
160
+ },
161
+ [ELoanChargeType.ANNUAL_LINE_FEE]: {
162
+ title: mappedChargeTypes[ELoanChargeType.ANNUAL_LINE_FEE],
163
+ explanation: `${chargeInfo(ELoanChargeType.ANNUAL_LINE_FEE).percent}% of commitment`,
164
+ },
165
+ [ELoanChargeType.OTHER]: {
166
+ title: mappedChargeTypes[ELoanChargeType.OTHER],
167
+ explanation: '',
168
+ },
169
+ [ELoanChargeType.RECOVERABLE]: {
170
+ title: mappedChargeTypes[ELoanChargeType.RECOVERABLE],
171
+ explanation: '',
172
+ },
173
+ };
174
+
175
+ const preparedStatement = Object.entries(groupedData).map(([chargeType, amount]) => {
176
+ if (amount === 0) {
177
+ return null;
178
+ }
179
+ return [
180
+ chargeMap[chargeType].title,
181
+ { text: formatNumbers(amount), alignment: 'right' },
182
+ { text: chargeMap[chargeType].explanation, noWrap: true },
183
+ ];
184
+ });
185
+
186
+ const groupedOtherStatements: {
187
+ [chargeName: string]: { memo: string, amount: number, chargeName: string }
188
+ } = otherStatements.reduce((acc, otherStatement) => {
189
+ const groupName = `${otherStatement.chargeName}${CUSTOMER_NUMBER_SEPARATOR}${otherStatement.memo}`;
190
+ const foundValue = acc[groupName];
191
+ if (foundValue) {
192
+ acc[groupName].amount = new Decimal(foundValue.amount).add(otherStatement.amount).toNumber();
193
+ return acc;
194
+ }
195
+ return {
196
+ ...acc,
197
+ [groupName]: {
198
+ amount: otherStatement.amount,
199
+ chargeName: otherStatement.chargeName,
200
+ memo: otherStatement.memo,
201
+ },
202
+ };
203
+ }, <{ [chargeName: string]: { memo: string, amount: number, chargeName: string } }>{});
204
+
205
+ Object
206
+ .values(groupedOtherStatements)
207
+ .sort((values1, values2) => values1.memo.localeCompare(values2.memo))
208
+ .sort((values1, values2) => values1.chargeName.localeCompare(values2.chargeName))
209
+ .forEach((values) => {
210
+ preparedStatement.push([
211
+ { text: values.chargeName, noWrap: false },
212
+ { text: formatNumbers(values.amount), alignment: 'right' },
213
+ { text: values.memo, noWrap: false },
214
+ ]);
215
+ });
216
+
217
+ const previousStatementTransaction = await this.loanStatementBalanceService.getLastStatementTransactionForDate(productId, new Date(periodStart.format('YYYY-MM-DD')));
218
+ const startStatementBalance = previousStatementTransaction ? previousStatementTransaction.balance : 0;
219
+
220
+ let monthlyTermPrincipal = null;
221
+ if (product.type === ELoanTypes.TERM) {
222
+ const { monthlyPrincipal } = await this.getTermLoan(product._id.toString(), new Date(start));
223
+ monthlyTermPrincipal = monthlyPrincipal;
224
+ }
225
+
226
+ const totalOther = Object.values(groupedOtherStatements).reduce((acc, st) => new Decimal(acc).add(st.amount).toNumber(), 0);
227
+ const other = { other: totalOther };
228
+ const totalAmountDue = Object.values({ ...groupedData, ...other }).reduce((acc, value) => new Decimal(acc).add(value || 0).toNumber(), 0);
229
+
230
+ const fullStatementTable = [
231
+ [{ text: '' }, { text: 'AMOUNT', bold: true, alignment: 'right' }, { text: 'BASIS FOR CHARGE', bold: true }],
232
+ ...preparedStatement.filter((statement) => !!statement),
233
+ monthlyTermPrincipal ? [{ text: 'Principal' }, {
234
+ text: formatNumbers(monthlyTermPrincipal),
235
+ alignment: 'right',
236
+ }, { text: '' }] : [],
237
+ [
238
+ { text: 'Past due amount from previous statement', bold: true, border: [false], noWrap: true },
239
+ {
240
+ text: formatNumbers(startStatementBalance),
241
+ bold: true,
242
+ border: [false],
243
+ alignment: 'right',
244
+ },
245
+ { text: '', border: [false] },
246
+ ],
247
+ [{
248
+ text: 'TOTAL AMOUNT DUE',
249
+ bold: true,
250
+ color: 'red',
251
+ border: [false],
252
+ noWrap: true,
253
+ }, {
254
+ text: formatNumbers(new Decimal(totalAmountDue).add(startStatementBalance).add(monthlyTermPrincipal ? monthlyTermPrincipal : 0).toNumber()),
255
+ bold: true,
256
+ color: 'red',
257
+ border: [false],
258
+ alignment: 'right',
259
+ }, { text: '', border: [false] }],
260
+ ].filter((row) => row.length > 0);
261
+
262
+ const productInfoTable = [
263
+ [{ text: '' }, { text: '' }],
264
+ [{ text: 'Product start date' }, {
265
+ text: dayjs(product.startDate).format(defaultDateFormat),
266
+ alignment: 'right',
267
+ }],
268
+ [{ text: 'Product maturity date' }, {
269
+ text: dayjs(product.maturityDate).format(defaultDateFormat),
270
+ alignment: 'right',
271
+ }],
272
+ [{ text: 'Commitment amount' }, { text: formatNumbers(product.commitment), alignment: 'right' }],
273
+ ];
274
+
275
+ const preparedStatementHeader = [
276
+ { text: `STATEMENT FOR PRODUCT: ${product.name}`, bold: true },
277
+ { text: `Client: ${borrower.code}`, bold: true },
278
+ {
279
+ text: `PERIOD FROM ${dayjs(start.slice(0, 10)).format(defaultDateFormat)} PERIOD TO ${dayjs(end.slice(0, 10)).format(defaultDateFormat)}`,
280
+ bold: true,
281
+ },
282
+ {
283
+ table: { widths: [120, 140], body: productInfoTable },
284
+ layout: 'lightHorizontalLines',
285
+ },
286
+ ];
287
+
288
+ const previousTransaction = transactions.length > 0
289
+ ? await this.loanTransactionsService.getLastTransactionForDate(transactions[0].productId.toString(), dayjs(transactions[0].date).subtract(1, 'second').toDate())
290
+ : await this.loanTransactionsService.getLastTransactionForDate(productId, dayjs(periodStart).subtract(1, 'second').toDate());
291
+
292
+ const lastTransaction = transactions.length > 0
293
+ ? transactions[transactions.length - 1]
294
+ : previousTransaction;
295
+
296
+ const preparedTransactionsHeader = [
297
+ { text: `LOAN TRANSACTIONS: ${product.name}` },
298
+ ];
299
+
300
+ const preparedTransactions = transactions.map((transaction) => {
301
+ return [
302
+ { text: dayjs(transaction.date).format(defaultDateFormat) },
303
+ { text: transaction.transactionType },
304
+ { text: formatNumbers(transaction.amount), alignment: 'right' },
305
+ { text: transaction.reference },
306
+ { text: formatNumbers(transaction.balance), alignment: 'right' },
307
+ ];
308
+ });
309
+
310
+ const fullTransactionsTable = [
311
+ [
312
+ { text: 'Date' },
313
+ { text: 'Type' },
314
+ { text: 'Amount', alignment: 'right' },
315
+ { text: 'Reference' },
316
+ { text: 'Balance', alignment: 'right' },
317
+ ],
318
+ [
319
+ { text: '' },
320
+ { text: '' },
321
+ { text: '' },
322
+ { text: 'OPENING BALANCE' },
323
+ { text: `${previousTransaction ? formatNumbers(previousTransaction.balance) : 0}`, alignment: 'right' },
324
+ ],
325
+ ...preparedTransactions,
326
+ [
327
+ { text: '' },
328
+ { text: '' },
329
+ { text: '' },
330
+ { text: 'CLOSING BALANCE' },
331
+ { text: `${lastTransaction ? formatNumbers(lastTransaction.balance) : 0}`, alignment: 'right' },
332
+ ],
333
+ ];
334
+
335
+ return {
336
+ startStatementBalance,
337
+ preparedStatementHeader,
338
+ preparedStatement: {
339
+ table: {
340
+ widths: ['auto', 100, '*'],
341
+ body: fullStatementTable,
342
+ },
343
+ layout: 'lightHorizontalLines',
344
+ },
345
+ preparedTransactionsHeader,
346
+ preparedTransactions: {
347
+ table: {
348
+ widths: [80, 100, 80, '*', 80],
349
+ body: fullTransactionsTable,
350
+ },
351
+ layout: 'lightHorizontalLines',
352
+ },
353
+ groupedData,
354
+ otherStatements,
355
+ };
356
+ }
357
+
358
+ async getTermLoan(productId: string, start: Date) {
359
+ const termLoan = await this.termLoanService.getTermLoan(productId, true);
360
+ let isMatch = false;
361
+
362
+ const tableHeader = [
363
+ { text: 'Relevant statement', bold: true },
364
+ { text: 'Due date', bold: true },
365
+ { text: 'Interest rate', bold: true, alignment: 'right' },
366
+ { text: 'Closing Balance', bold: true, alignment: 'right' },
367
+ { text: 'Principal', bold: true, alignment: 'right' },
368
+ { text: 'Interest', bold: true, alignment: 'right' },
369
+ { text: 'EMI', bold: true, alignment: 'right' },
370
+ { text: 'Admin Fee', bold: true, alignment: 'right' },
371
+ { text: 'Total Payment', bold: true, alignment: 'right' },
372
+ ];
373
+
374
+ let tableData = [];
375
+
376
+ let monthlyPrincipal: number = null;
377
+ let dueDate: Date = null;
378
+
379
+ const cellParams = { border: [false], noWrap: true, fontSize: 12 };
380
+ const numberCellParams = { ...cellParams, alignment: 'right' };
381
+
382
+ if (termLoan) {
383
+ tableData = termLoan.calculated.reduce((acc, calculatedRow) => {
384
+ isMatch = isMatch || dayjs(calculatedRow.relevantStatement).endOf('month').isAfter(dayjs(start));
385
+ if (!isMatch) {
386
+ return acc;
387
+ }
388
+ const EMI = new Decimal(calculatedRow.monthlyPrincipal).add(calculatedRow.monthlyInterest).toNumber();
389
+ const totalPayment = new Decimal(EMI).add(calculatedRow.adminFee).toNumber();
390
+ if (monthlyPrincipal === null) {
391
+ monthlyPrincipal = calculatedRow.monthlyPrincipal;
392
+ dueDate = calculatedRow.paymentDueDate;
393
+ }
394
+ return [
395
+ ...acc,
396
+ [
397
+ { text: dayjs(calculatedRow.relevantStatement).format(defaultDateFormat), ...cellParams },
398
+ { text: dayjs(calculatedRow.paymentDueDate).format(defaultDateFormat), ...cellParams },
399
+ { text: formatNumbers(new Decimal(calculatedRow.interestRate).mul(100).toDP(2).toNumber() + '%'), ...numberCellParams },
400
+ { text: formatNumbers(calculatedRow.closingBalance), ...numberCellParams },
401
+ { text: formatNumbers(calculatedRow.monthlyPrincipal), ...numberCellParams },
402
+ { text: formatNumbers(calculatedRow.monthlyInterest), ...numberCellParams },
403
+ { text: formatNumbers(EMI), ...numberCellParams },
404
+ { text: formatNumbers(calculatedRow.adminFee), ...numberCellParams },
405
+ { text: formatNumbers(totalPayment), ...numberCellParams },
406
+ ],
407
+ ];
408
+ }, []);
409
+ }
410
+ return {
411
+ monthlyPrincipal,
412
+ dueDate,
413
+ termLoanTableData: [tableHeader, ...tableData],
414
+ };
415
+ }
416
+
417
+ async createStatementDoc<T extends pdfMake.TCreatedPdf | {
418
+ total: number;
419
+ dueDate: Date,
420
+ productTotals: { [productId: string]: number }
421
+ }>(params: {
422
+ borrowerId: string,
423
+ start: string,
424
+ end: string
425
+ }, getOnlyTotal = false): Promise<T> {
426
+
427
+ const { borrowerId, start, end } = params;
428
+ const products = await this.loanChargesService.getLoanProducts(borrowerId);
429
+ const borrower = await BorrowerModel.findById(borrowerId).lean();
430
+
431
+ const organization = await this.organizationsService.getOrganizationForBorrower(borrowerId);
432
+
433
+ pdfMake.vfs = (vfsFonts as any).pdfMake.vfs;
434
+
435
+ const pageSize = {
436
+ width: 841.890, // Page width in points for A4 size (8.27 inches)
437
+ height: 595.276, // Page height in points for A4 size (11.69 inches)
438
+ };
439
+
440
+ this.docWidth = defaultPDFPageSize.width - this.margin * 2;
441
+ this.docHeight = defaultPDFPageSize.height - this.margin * 2;
442
+
443
+ const emptyString = { text: ' ', margin: 5 };
444
+ const pageBreaker = { text: '', pageBreak: 'after' };
445
+
446
+ const totalGroupedData = {
447
+ [ELoanChargeType.INTEREST_FEE]: 0,
448
+ [ELoanChargeType.ADMIN_FEE]: 0,
449
+ [ELoanChargeType.UNUSED_LINE_FEE]: 0,
450
+ [ELoanChargeType.WIRE_FEE]: 0,
451
+ [ELoanChargeType.ANNUAL_LINE_FEE]: 0,
452
+ [ELoanChargeType.OTHER]: 0,
453
+ [ELoanChargeType.RECOVERABLE]: 0,
454
+ };
455
+
456
+ let totalOverDueStatementBalance = 0;
457
+
458
+ const productTotals: { [productId: string]: number } = products.reduce((acc, p) => ({
459
+ ...acc,
460
+ [p._id.toString()]: 0,
461
+ }), {});
462
+
463
+ const docContent = await Promise.all(products.map(async (product, index) => {
464
+ const {
465
+ startStatementBalance,
466
+ preparedStatementHeader,
467
+ preparedStatement,
468
+ preparedTransactionsHeader,
469
+ preparedTransactions,
470
+ groupedData,
471
+ otherStatements,
472
+ } = await this.getProductStatementData(product._id.toString(), start, end);
473
+
474
+ totalOverDueStatementBalance = new Decimal(totalOverDueStatementBalance).add(startStatementBalance).toNumber();
475
+
476
+ let totalPerProduct = 0;
477
+ Object.entries(groupedData).forEach(([chargeType, amount]) => {
478
+ totalGroupedData[chargeType] = new Decimal(totalGroupedData[chargeType]).add(amount).toNumber();
479
+ totalPerProduct = new Decimal(totalPerProduct).add(amount).toNumber();
480
+ });
481
+ otherStatements.forEach((otherStatement) => {
482
+ totalGroupedData[otherStatement.chargeType] = new Decimal(totalGroupedData[otherStatement.chargeType]).add(otherStatement.amount).toNumber();
483
+ totalPerProduct = new Decimal(totalPerProduct).add(otherStatement.amount).toNumber();
484
+ });
485
+ productTotals[product._id.toString()] = new Decimal(startStatementBalance).add(totalPerProduct).toNumber();
486
+
487
+ const warningText = product.type === ELoanTypes.REVOLVER
488
+ ? `AMOUNT WILL BE ADDED TO THE LOAN ON 1ST. ${organization.name.toUpperCase()} RESERVES THE RIGHT TO STOP FUNDING IF THERE IS INSUFFICIENT AVAILABILITY TO ADD IT TO THE LOAN.`
489
+ : `PAYABLE BY 7TH. ${organization.name.toUpperCase()} RESERVES THE RIGHT TO STOP FUNDING IF THE STATEMENT IS NOT PAID.`;
490
+
491
+ return [
492
+ this.addHeaderNew(organization.subfolder), // Add the image element to the content
493
+ emptyString,
494
+ ...preparedStatementHeader,
495
+ emptyString,
496
+ preparedStatement,
497
+ emptyString,
498
+ {
499
+ text: warningText,
500
+ color: 'red',
501
+ },
502
+ {
503
+ text: `PAYABLE BY DIRECT TRANSFER, OR BORROWING BASE SUBMISSION CONFIRMING AVAILABILITY. ${organization.name.toUpperCase()} RESERVES THE RIGHT TO ACH FROM YOUR ACCOUNT`,
504
+ bold: true,
505
+ },
506
+ emptyString,
507
+ ...preparedTransactionsHeader,
508
+ preparedTransactions,
509
+ products.length === index + 1 ? emptyString : pageBreaker,
510
+ ];
511
+ }));
512
+
513
+ let totalStatementValue = 0;
514
+ const preparedTotal = Object.entries(totalGroupedData).map(([chargeType, amount]) => {
515
+ totalStatementValue = new Decimal(totalStatementValue).add(amount).toNumber();
516
+ return [
517
+ { text: mappedChargeTypes[ELoanChargeType[chargeType]] },
518
+ { text: formatNumbers(amount), alignment: 'right' },
519
+ { text: '' },
520
+ ];
521
+ });
522
+
523
+ const totalOverDueStatement = totalOverDueStatementBalance > 0
524
+ ? [
525
+ { text: 'Past due amount from previous statement' },
526
+ { text: formatNumbers(totalOverDueStatementBalance), alignment: 'right' },
527
+ { text: '' }]
528
+ : [];
529
+
530
+ const principalData = [];
531
+ let paymentDueDate: Date = null;
532
+ const termLoanProducts = products.filter((product) => product.type === ELoanTypes.TERM);
533
+ await Promise.all(termLoanProducts.map(async (product) => {
534
+ const {
535
+ termLoanTableData,
536
+ monthlyPrincipal,
537
+ dueDate,
538
+ } = await this.getTermLoan(product._id.toString(), new Date(start));
539
+
540
+ if (monthlyPrincipal !== null) {
541
+ totalStatementValue = new Decimal(totalStatementValue).add(monthlyPrincipal).toNumber();
542
+ principalData.push([
543
+ { text: 'Principal' },
544
+ { text: formatNumbers(monthlyPrincipal), alignment: 'right' },
545
+ { text: '' },
546
+ ]);
547
+ paymentDueDate = dueDate;
548
+ productTotals[product._id.toString()] = new Decimal(productTotals[product._id.toString()]).add(monthlyPrincipal).toNumber();
549
+ }
550
+
551
+ docContent.push([
552
+ pageBreaker,
553
+ this.addHeaderNew(organization.subfolder),
554
+ emptyString,
555
+ <any>{ text: `Term loan schedule: `, fontSize: 15, bold: true },
556
+ emptyString,
557
+ {
558
+ table: { widths: ['*', '*', '*', '*', '*', '*', '*', '*', '*'], body: termLoanTableData },
559
+ layout: 'lightHorizontalLines',
560
+ },
561
+ ]);
562
+ }));
563
+
564
+ const totalOwed = new Decimal(totalStatementValue).add(totalOverDueStatementBalance).toNumber();
565
+
566
+ if (getOnlyTotal) {
567
+ return <T>{ total: totalOwed, dueDate: paymentDueDate, productTotals };
568
+ }
569
+
570
+ const bankAccounts = {
571
+ 'Black Feather': [
572
+ [{ text: 'Bank name' }, { text: 'Chase' }],
573
+ [{ text: 'ABA' }, { text: '322271627' }],
574
+ [{ text: 'Account' }, { text: '80009785264' }],
575
+ ],
576
+ 'GemCap': [
577
+ [{ text: 'Bank name' }, { text: 'CIBC Bank USA' }],
578
+ [{ text: 'ABA' }, { text: '0710-0648-6' }],
579
+ [{ text: 'SWIFT' }, { text: 'PVTBUS44' }],
580
+ [{ text: 'Account' }, { text: '2583852' }],
581
+ ],
582
+ };
583
+
584
+ const paymentInstruction = [
585
+ [{ text: 'Please remit payment to', fillColor: '#44464b', color: 'white' }, { text: '', fillColor: '#44464b' }],
586
+ ...bankAccounts[organization.name],
587
+ [{ text: 'Account name' }, { text: `${this.config.title} Solutions Master Concentration` }],
588
+ [{ text: 'Reference' }, { text: `${borrower.code} - STATEMENT PAYMENT` }],
589
+ [{ text: 'The reference is required to ensure timely settlement of your account.', color: 'red' }, {}],
590
+ ];
591
+
592
+ const totalStatementTable = [
593
+ [{ text: '' }, { text: 'AMOUNT', bold: true, alignment: 'right' }, { text: '' }],
594
+ ...preparedTotal,
595
+ ...principalData,
596
+ totalOverDueStatement,
597
+ [
598
+ { text: 'TOTAL', color: 'red' },
599
+ {
600
+ text: formatNumbers(totalOwed),
601
+ bold: true,
602
+ color: 'red',
603
+ alignment: 'right',
604
+ },
605
+ { text: paymentDueDate ? `due on ${dayjs(paymentDueDate).format(defaultDateFormat)}` : '', color: 'red' },
606
+ ],
607
+ ];
608
+
609
+ docContent.unshift([
610
+ this.addHeaderNew(organization.subfolder),
611
+ emptyString,
612
+ <any>{ text: 'Total Statement Amount', fontSize: 18, bold: true },
613
+ { text: `Client: ${borrower.name}`, fontSize: 15, bold: true },
614
+ {
615
+ table: { widths: [300, 100, '*'], body: totalStatementTable.filter((row) => row.length !== 0) },
616
+ layout: 'lightHorizontalLines',
617
+ },
618
+ emptyString,
619
+ {
620
+ table: { widths: ['*', '*'], body: paymentInstruction, styles: 'section' },
621
+ layout: 'lightHorizontalLines',
622
+ },
623
+ pageBreaker,
624
+ ]);
625
+
626
+ const docDefinition: TDocumentDefinitions = {
627
+ content: docContent as any,
628
+ footer: (currentPage, pageCount) => {
629
+ return {
630
+ text: 'Page ' + currentPage.toString() + ' of ' + pageCount,
631
+ alignment: 'center',
632
+ };
633
+ },
634
+ pageOrientation: 'landscape',
635
+ pageSize,
636
+ pageMargins: this.margin,
637
+ };
638
+
639
+ return <T>pdfMake.createPdf(docDefinition);
640
+ }
641
+
642
+ }