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.
- package/classes/bank-transaction-item.d.ts +17 -0
- package/classes/bank-transaction-item.js +64 -0
- package/classes/bank-transaction-item.ts +66 -0
- package/classes/bank-uploaded-transaction.d.ts +17 -0
- package/classes/bank-uploaded-transaction.js +35 -0
- package/classes/bank-uploaded-transaction.ts +35 -0
- package/classes/inventory-item.d.ts +41 -0
- package/classes/inventory-item.js +44 -0
- package/classes/inventory-item.ts +63 -0
- package/classes/payable-account-item.d.ts +22 -0
- package/classes/payable-account-item.js +27 -0
- package/classes/payable-account-item.ts +35 -0
- package/classes/quickbook-item.d.ts +37 -0
- package/classes/quickbook-item.js +51 -0
- package/classes/quickbook-item.ts +59 -0
- package/classes/receivable-item.d.ts +26 -0
- package/classes/receivable-item.js +28 -0
- package/classes/receivable-item.ts +38 -0
- package/constants/date-formats.contsants.d.ts +1 -0
- package/constants/date-formats.contsants.js +4 -0
- package/constants/date-formats.contsants.ts +1 -0
- package/db/brokers.db.d.ts +185 -0
- package/db/brokers.db.js +35 -2
- package/db/brokers.db.ts +34 -1
- package/db/collateral-adjustments.db.d.ts +34 -0
- package/db/collateral-adjustments.db.js +52 -0
- package/db/collateral-adjustments.db.ts +54 -0
- package/db/collaterals.db.d.ts +1 -1
- package/db/equipment.db.d.ts +40 -0
- package/db/equipment.db.js +55 -0
- package/db/equipment.db.ts +56 -0
- package/db/financial-spreading.db.ts +2 -1
- package/db/groups.d.ts +5 -0
- package/db/groups.js +57 -0
- package/db/groups.ts +52 -0
- package/db/inventories.d.ts +91 -0
- package/db/inventories.js +449 -0
- package/db/inventories.ts +481 -0
- package/db/inventory-availability.d.ts +3 -0
- package/db/inventory-availability.js +103 -0
- package/db/inventory-availability.ts +113 -0
- package/db/new-summary.d.ts +31 -0
- package/db/new-summary.js +1295 -0
- package/db/new-summary.ts +1509 -0
- package/db/payable-accounts.d.ts +30 -0
- package/db/payable-accounts.js +55 -0
- package/db/payable-accounts.ts +50 -0
- package/db/reserve.db.d.ts +34 -0
- package/db/reserve.db.js +52 -0
- package/db/reserve.db.ts +48 -0
- package/db/uploads.db.d.ts +2 -0
- package/db/uploads.db.js +29 -0
- package/db/uploads.db.ts +24 -0
- package/helpers/main.helper.d.ts +31 -0
- package/helpers/main.helper.js +63 -0
- package/helpers/main.helper.ts +63 -0
- package/models/AccountPayableItem.model.d.ts +6 -6
- package/models/AllocatedBankTransaction.model.d.ts +54 -0
- package/models/AllocatedBankTransaction.model.js +70 -0
- package/models/AllocatedBankTransaction.model.ts +94 -0
- package/models/AllocatedData.model.d.ts +33 -0
- package/models/AllocatedData.model.js +19 -0
- package/models/AllocatedData.model.ts +24 -0
- package/models/BBCDate.model.d.ts +3 -3
- package/models/BBCSheet.model.d.ts +3 -3
- package/models/Banks.model.d.ts +3 -3
- package/models/Borrower.model.d.ts +3 -3
- package/models/BorrowerData.model.d.ts +3 -3
- package/models/BorrowerDataInsurance.model.d.ts +3 -3
- package/models/BorrowerDataTerm.model.d.ts +3 -3
- package/models/BorrowerSummary.model.js +1 -1
- package/models/BorrowerSummary.model.ts +1 -1
- package/models/CalandarDay.model.d.ts +40 -0
- package/models/CalandarDay.model.js +47 -0
- package/models/CalandarDay.model.ts +61 -0
- package/models/CashAllocationProduct.model.d.ts +119 -0
- package/models/CashAllocationProduct.model.js +102 -0
- package/models/CashAllocationProduct.model.ts +112 -0
- package/models/CashAllocationReference.model.d.ts +37 -0
- package/models/CashAllocationReference.model.js +27 -0
- package/models/CashAllocationReference.model.ts +40 -0
- package/models/CollateralAdjustment.model.d.ts +51 -0
- package/models/CollateralAdjustment.model.js +61 -0
- package/models/CollateralAdjustment.model.ts +98 -0
- package/models/Company.model.d.ts +35 -0
- package/models/Company.model.js +18 -0
- package/models/Company.model.ts +29 -0
- package/models/CustomerAPGroup.model.d.ts +32 -0
- package/models/CustomerAPGroup.model.js +24 -0
- package/models/CustomerAPGroup.model.ts +31 -0
- package/models/Equipment.model.d.ts +53 -0
- package/models/Equipment.model.js +140 -0
- package/models/Equipment.model.ts +172 -0
- package/models/FinancialCompliance.model.d.ts +39 -0
- package/models/FinancialCompliance.model.js +64 -0
- package/models/FinancialCompliance.model.ts +78 -0
- package/models/FinancialComplianceBorrower.model.d.ts +58 -0
- package/models/FinancialComplianceBorrower.model.js +82 -0
- package/models/FinancialComplianceBorrower.model.ts +118 -0
- package/models/FinancialIndexes.model.d.ts +36 -0
- package/models/FinancialIndexes.model.js +27 -0
- package/models/FinancialIndexes.model.ts +37 -0
- package/models/Inventory.model.d.ts +18 -18
- package/models/InventoryAvailability.model.d.ts +21 -21
- package/models/InventoryAvailabilityItem.model.d.ts +6 -6
- package/models/InventoryItem.model.d.ts +24 -24
- package/models/InventoryManualEntry.model.d.ts +9 -9
- package/models/InventorySeasonalRates.model.d.ts +3 -3
- package/models/LoanBroker.model.d.ts +3 -3
- package/models/LoanCharges.model.d.ts +12 -12
- package/models/LoanProducts.model.d.ts +9 -9
- package/models/LoanStatementStatus.model.d.ts +35 -0
- package/models/LoanStatementStatus.model.js +34 -0
- package/models/LoanStatementStatus.model.ts +45 -0
- package/models/LoanStatementTransaction.model.d.ts +9 -9
- package/models/LoanTransactionFile.model.d.ts +41 -0
- package/models/LoanTransactionFile.model.js +44 -0
- package/models/LoanTransactionFile.model.ts +61 -0
- package/models/MappedGroup.model.d.ts +37 -0
- package/models/MappedGroup.model.js +33 -0
- package/models/MappedGroup.model.ts +46 -0
- package/models/MonthEndData.Model.d.ts +41 -0
- package/models/MonthEndData.Model.js +42 -0
- package/models/MonthEndData.Model.ts +53 -0
- package/models/OrganizationEmails.model.d.ts +44 -0
- package/models/OrganizationEmails.model.js +40 -0
- package/models/OrganizationEmails.model.ts +54 -0
- package/models/ProductBroker.model.d.ts +9 -9
- package/models/QuickbooksAccount.model.d.ts +39 -0
- package/models/QuickbooksAccount.model.js +43 -0
- package/models/QuickbooksAccount.model.ts +57 -0
- package/models/Receivable.model.d.ts +12 -12
- package/models/ReceivableAvailability.model.d.ts +54 -54
- package/models/ReceivableAvailabilityItem.model.d.ts +57 -57
- package/models/ReceivableItem.model.d.ts +6 -6
- package/models/Reserve.model.d.ts +51 -0
- package/models/Reserve.model.js +96 -0
- package/models/Reserve.model.ts +125 -0
- package/models/TermLoan.model.d.ts +3 -3
- package/models/TermLoanCalculated.model.d.ts +6 -6
- package/models/TransactionAttachedFile.Model.d.ts +35 -0
- package/models/TransactionAttachedFile.Model.js +37 -0
- package/models/TransactionAttachedFile.Model.ts +48 -0
- package/models/UploadedBankTransaction.model.d.ts +56 -0
- package/models/UploadedBankTransaction.model.js +78 -0
- package/models/UploadedBankTransaction.model.ts +110 -0
- package/models/UploadedData.model.d.ts +36 -0
- package/models/UploadedData.model.js +23 -0
- package/models/UploadedData.model.ts +35 -0
- package/models/UploadedFile.model.d.ts +40 -0
- package/models/UploadedFile.model.js +41 -0
- package/models/UploadedFile.model.ts +57 -0
- package/models/UploadedSheet.model.d.ts +46 -0
- package/models/UploadedSheet.model.js +27 -0
- package/models/UploadedSheet.model.ts +51 -0
- package/package.json +10 -1
- package/repositories/globals.repository.d.ts +8 -0
- package/repositories/globals.repository.js +24 -0
- package/repositories/globals.repository.ts +21 -0
- package/services/attached-files.service.d.ts +57 -0
- package/services/attached-files.service.js +103 -0
- package/services/attached-files.service.ts +123 -0
- package/services/availability.service.d.ts +77 -0
- package/services/availability.service.js +897 -0
- package/services/availability.service.ts +1034 -0
- package/services/bank-uploaded-transactions.service.d.ts +33 -0
- package/services/bank-uploaded-transactions.service.js +430 -0
- package/services/bank-uploaded-transactions.service.ts +475 -0
- package/services/banks.service.d.ts +36 -0
- package/services/banks.service.js +91 -0
- package/services/banks.service.ts +95 -0
- package/services/borrower-summary.service.d.ts +35 -0
- package/services/borrower-summary.service.js +310 -0
- package/services/borrower-summary.service.ts +334 -0
- package/services/borrowers.service.d.ts +103 -0
- package/services/borrowers.service.js +268 -0
- package/services/borrowers.service.ts +302 -0
- package/services/brokers.service.d.ts +212 -0
- package/services/brokers.service.js +160 -0
- package/services/brokers.service.ts +200 -0
- package/services/calendar.service.d.ts +53 -0
- package/services/calendar.service.js +108 -0
- package/services/calendar.service.ts +128 -0
- package/services/cash-allocation.service.d.ts +40 -0
- package/services/cash-allocation.service.js +92 -0
- package/services/cash-allocation.service.ts +105 -0
- package/services/collateral-adjustments.service.d.ts +38 -0
- package/services/collateral-adjustments.service.js +82 -0
- package/services/collateral-adjustments.service.ts +95 -0
- package/services/collaterals.service.d.ts +69 -0
- package/services/collaterals.service.js +279 -0
- package/services/collaterals.service.ts +319 -0
- package/services/companies.service.d.ts +5 -0
- package/services/companies.service.js +21 -0
- package/services/companies.service.ts +23 -0
- package/services/compliance-borrowers.service.d.ts +152 -0
- package/services/compliance-borrowers.service.js +569 -0
- package/services/compliance-borrowers.service.ts +617 -0
- package/services/equipment.service.d.ts +42 -0
- package/services/equipment.service.js +120 -0
- package/services/equipment.service.ts +149 -0
- package/services/file-manager.service.d.ts +44 -0
- package/services/file-manager.service.js +120 -0
- package/services/file-manager.service.ts +146 -0
- package/services/financial-compliance.service.d.ts +58 -0
- package/services/financial-compliance.service.js +281 -0
- package/services/financial-compliance.service.ts +309 -0
- package/services/financial-indexes.service.d.ts +20 -0
- package/services/financial-indexes.service.js +241 -0
- package/services/financial-indexes.service.ts +257 -0
- package/services/financial-spreading.service.d.ts +74 -0
- package/services/financial-spreading.service.js +450 -0
- package/services/financial-spreading.service.ts +517 -0
- package/services/globals.service.d.ts +5 -0
- package/services/globals.service.js +11 -0
- package/services/globals.service.ts +8 -0
- package/services/groups.service.d.ts +39 -0
- package/services/groups.service.js +65 -0
- package/services/groups.service.ts +64 -0
- package/services/inventory-availability.service.d.ts +13 -0
- package/services/inventory-availability.service.js +170 -0
- package/services/inventory-availability.service.ts +187 -0
- package/services/inventory.service.d.ts +118 -0
- package/services/inventory.service.js +239 -0
- package/services/inventory.service.ts +276 -0
- package/services/loan-charges.service.d.ts +83 -0
- package/services/loan-charges.service.js +343 -0
- package/services/loan-charges.service.ts +396 -0
- package/services/loan-payments.service.d.ts +94 -0
- package/services/loan-payments.service.js +485 -0
- package/services/loan-payments.service.ts +541 -0
- package/services/loan-products.service.d.ts +12 -0
- package/services/loan-products.service.js +55 -0
- package/services/loan-products.service.ts +58 -0
- package/services/loan-statement-balance.service.d.ts +16 -0
- package/services/loan-statement-balance.service.js +106 -0
- package/services/loan-statement-balance.service.ts +113 -0
- package/services/loan-statement-effects.service.d.ts +8 -0
- package/services/loan-statement-effects.service.js +42 -0
- package/services/loan-statement-effects.service.ts +41 -0
- package/services/loan-statement-status.service.d.ts +208 -0
- package/services/loan-statement-status.service.js +159 -0
- package/services/loan-statement-status.service.ts +177 -0
- package/services/loan-statement.service.d.ts +186 -0
- package/services/loan-statement.service.js +935 -0
- package/services/loan-statement.service.ts +1040 -0
- package/services/loan-transactions.service.d.ts +169 -0
- package/services/loan-transactions.service.js +941 -0
- package/services/loan-transactions.service.ts +1042 -0
- package/services/lock.service.d.ts +6 -0
- package/services/lock.service.js +45 -0
- package/services/lock.service.ts +45 -0
- package/services/manual-entry.service.d.ts +20 -0
- package/services/manual-entry.service.js +186 -0
- package/services/manual-entry.service.ts +201 -0
- package/services/month-end-data.service.d.ts +34 -0
- package/services/month-end-data.service.js +30 -0
- package/services/month-end-data.service.ts +35 -0
- package/services/nodemailer.service.d.ts +96 -0
- package/services/nodemailer.service.js +689 -0
- package/services/nodemailer.service.ts +774 -0
- package/services/organization-emails.service.d.ts +31 -0
- package/services/organization-emails.service.js +10 -0
- package/services/organization-emails.service.ts +7 -0
- package/services/organizations.service.d.ts +34 -0
- package/services/organizations.service.js +74 -0
- package/services/organizations.service.ts +84 -0
- package/services/pdf.service.d.ts +61 -0
- package/services/pdf.service.js +547 -0
- package/services/pdf.service.ts +642 -0
- package/services/quickbooks.service.d.ts +99 -0
- package/services/quickbooks.service.js +640 -0
- package/services/quickbooks.service.ts +734 -0
- package/services/reports/investor-summary.service.d.ts +28 -0
- package/services/reports/investor-summary.service.js +136 -0
- package/services/reports/investor-summary.service.ts +159 -0
- package/services/reports.service.d.ts +126 -0
- package/services/reports.service.js +584 -0
- package/services/reports.service.ts +702 -0
- package/services/reserve.service.d.ts +37 -0
- package/services/reserve.service.js +76 -0
- package/services/reserve.service.ts +79 -0
- package/services/sentry.service.d.ts +11 -0
- package/services/sentry.service.js +49 -0
- package/services/sentry.service.ts +33 -0
- package/services/signs.service.d.ts +69 -0
- package/services/signs.service.js +230 -0
- package/services/signs.service.ts +260 -0
- package/services/term-loan.service.d.ts +30 -0
- package/services/term-loan.service.js +614 -0
- package/services/term-loan.service.ts +696 -0
- package/services/uploads.service.d.ts +134 -0
- package/services/uploads.service.js +587 -0
- package/services/uploads.service.ts +643 -0
- package/services/user-logs.service.d.ts +23 -0
- package/services/user-logs.service.js +160 -0
- package/services/user-logs.service.ts +177 -0
- package/services/users.service.d.ts +4 -4
- package/services/yield.service.d.ts +46 -0
- package/services/yield.service.js +42 -12
- package/services/yield.service.ts +38 -8
- package/tsconfig.json +5 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import nodemailer from 'nodemailer';
|
|
5
|
+
import { Buffer } from 'buffer';
|
|
6
|
+
|
|
7
|
+
import { IMulterFile } from '../interfaces/multer.interface';
|
|
8
|
+
import { IKeycloakUser } from '../interfaces/keycloak-user.interface';
|
|
9
|
+
import { IEmail } from '../interfaces/email.interface';
|
|
10
|
+
import { ELoanTypes } from '../enums/loan-types.enum';
|
|
11
|
+
import { Organization } from '../models/Organization.model';
|
|
12
|
+
import { IComplianceBorrowerDocumentFull } from '../models/BorrowerCompliance.model';
|
|
13
|
+
|
|
14
|
+
import { IFinancialComplianceBorrowerDocumentWithMails } from '../models/FinancialComplianceBorrower.model';
|
|
15
|
+
import { BorrowerWithFiles, FileManagerService } from './file-manager.service';
|
|
16
|
+
import FinancialCompliance, { IFinancialComplianceSettingsDocument } from '../models/FinancialCompliance.model';
|
|
17
|
+
import { OrganizationEmailsType } from '../models/OrganizationEmails.model';
|
|
18
|
+
import { BorrowerService } from './borrowers.service';
|
|
19
|
+
import { ComplianceBorrowersService } from './compliance-borrowers.service';
|
|
20
|
+
import { FinancialComplianceService } from './financial-compliance.service';
|
|
21
|
+
import { LoanChargesService } from './loan-charges.service';
|
|
22
|
+
import { OrganizationEmailsService } from './organization-emails.service';
|
|
23
|
+
import { OrganizationsService } from './organizations.service';
|
|
24
|
+
import { SentryService } from './sentry.service';
|
|
25
|
+
|
|
26
|
+
export enum ESenderType {
|
|
27
|
+
REGULAR,
|
|
28
|
+
FINANCIAL,
|
|
29
|
+
FINANCIAL_ONLY,
|
|
30
|
+
GOAT,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type EmailWithItems = IEmail & {
|
|
34
|
+
recipientName: string,
|
|
35
|
+
hasPastDueItems: boolean,
|
|
36
|
+
complianceBorrowerId: string;
|
|
37
|
+
items: { itemName: string, dateDue: Date, status: string }[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type EmailWithFinancial = IEmail & {
|
|
41
|
+
borrowers: IFinancialComplianceBorrowerDocumentWithMails[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const getSenderSignatureFileName = (senderType: ESenderType) => {
|
|
45
|
+
switch (senderType) {
|
|
46
|
+
case ESenderType.REGULAR:
|
|
47
|
+
return 'signature.html';
|
|
48
|
+
case ESenderType.FINANCIAL:
|
|
49
|
+
return 'signature-financial.html';
|
|
50
|
+
case ESenderType.FINANCIAL_ONLY:
|
|
51
|
+
return 'signature-financial-only.html';
|
|
52
|
+
case ESenderType.GOAT:
|
|
53
|
+
return 'signature-goat.html';
|
|
54
|
+
default:
|
|
55
|
+
return 'signature.html';
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
interface INodemailerServiceConfig {
|
|
60
|
+
appTitle: string;
|
|
61
|
+
sender: string;
|
|
62
|
+
senderFinancial: string;
|
|
63
|
+
hiddenRecipient: string[];
|
|
64
|
+
mailerConfig: {
|
|
65
|
+
host: string,
|
|
66
|
+
port: number,
|
|
67
|
+
auth: {
|
|
68
|
+
user: string,
|
|
69
|
+
pass: string,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class NodemailerService {
|
|
75
|
+
|
|
76
|
+
private readonly config: INodemailerServiceConfig;
|
|
77
|
+
private senders: { [key in ESenderType]?: { senderName: string, senderEmail: string } };
|
|
78
|
+
private transporter: nodemailer.Transporter;
|
|
79
|
+
|
|
80
|
+
constructor(
|
|
81
|
+
config: INodemailerServiceConfig,
|
|
82
|
+
private readonly borrowerService: BorrowerService,
|
|
83
|
+
private readonly getComplianceBorrowersService: () => ComplianceBorrowersService,
|
|
84
|
+
private readonly fileManagerService: FileManagerService,
|
|
85
|
+
private readonly financialComplianceService: FinancialComplianceService,
|
|
86
|
+
private readonly loanChargesService: LoanChargesService,
|
|
87
|
+
private readonly organizationEmailsService: OrganizationEmailsService,
|
|
88
|
+
private readonly organizationsService: OrganizationsService,
|
|
89
|
+
private readonly sentryService: SentryService,
|
|
90
|
+
) {
|
|
91
|
+
this.config = config;
|
|
92
|
+
this.senders = {
|
|
93
|
+
[ESenderType.REGULAR]: {
|
|
94
|
+
senderName: this.config.appTitle,
|
|
95
|
+
senderEmail: this.config.sender,
|
|
96
|
+
},
|
|
97
|
+
[ESenderType.FINANCIAL]: {
|
|
98
|
+
senderName: `${this.config.appTitle} Finance Department`,
|
|
99
|
+
senderEmail: this.config.senderFinancial,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
this.transporter = nodemailer.createTransport(this.config.mailerConfig);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private getSender(senderType: ESenderType) {
|
|
106
|
+
switch (senderType) {
|
|
107
|
+
case ESenderType.REGULAR:
|
|
108
|
+
return `${this.senders[senderType].senderName} <${this.senders[senderType].senderEmail}>`;
|
|
109
|
+
case ESenderType.FINANCIAL:
|
|
110
|
+
return `${this.senders[senderType].senderName} <${this.senders[senderType].senderEmail}>`;
|
|
111
|
+
default:
|
|
112
|
+
return `"${this.config.appTitle}>" <${this.config.sender}>`;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
private async senderHandler(err, info) {
|
|
117
|
+
if (err) {
|
|
118
|
+
this.sentryService.catchErrorBySentry(null, err);
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
console.log(info.envelope);
|
|
122
|
+
console.log(info.messageId);
|
|
123
|
+
console.log('Message sent: %s', info.messageId);
|
|
124
|
+
console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getSignatureAsync(senderType: ESenderType, organizationSubfolder?: string) {
|
|
128
|
+
const signaturePath = path.resolve(__dirname, '../public/emails', organizationSubfolder ? organizationSubfolder : '', getSenderSignatureFileName(senderType));
|
|
129
|
+
return await fs.promises.readFile(signaturePath, 'utf8');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async collectItemsAndNotify(borrowerId: string) {
|
|
133
|
+
const complianceBorrowersService = this.getComplianceBorrowersService();
|
|
134
|
+
const borrower: IComplianceBorrowerDocumentFull = await complianceBorrowersService.getFullComplianceBorrowerById(borrowerId);
|
|
135
|
+
if (!borrower.isEmailingActive) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let hasPastDueItems = false;
|
|
139
|
+
let allInReview = true;
|
|
140
|
+
const fullItems = borrower.items.reduce((accI, item) => {
|
|
141
|
+
return [...accI, ...item.instances.reduce((accIns, instance) => {
|
|
142
|
+
if (instance.status === 'ACCEPTED') {
|
|
143
|
+
return accIns;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const currentDate = dayjs();
|
|
147
|
+
const dueDate = dayjs(instance.nextDate);
|
|
148
|
+
const dateToNotify = dayjs(instance.nextDate).subtract(item.item.notificationDays, 'day');
|
|
149
|
+
|
|
150
|
+
const diff = currentDate.diff(dueDate);
|
|
151
|
+
const addresses = [
|
|
152
|
+
...borrower.mainEmails.reduce((acc, address) => address.isActive === true ? [...acc, address.email] : acc, []),
|
|
153
|
+
...item.emailAddresses.reduce((acc, address) => address.isActive === true ? [...acc, address.email] : acc, [])];
|
|
154
|
+
if (diff > 0) {
|
|
155
|
+
hasPastDueItems = instance.status !== 'IN_REVIEW';
|
|
156
|
+
allInReview = allInReview && instance.status === 'IN_REVIEW';
|
|
157
|
+
return [...accIns, {
|
|
158
|
+
itemName: item.item.name,
|
|
159
|
+
dateDue: instance.nextDate,
|
|
160
|
+
status: instance.status === 'IN_REVIEW' ? 'In review' : 'Past due',
|
|
161
|
+
addresses,
|
|
162
|
+
}];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const notificationDiff = currentDate.diff(dateToNotify);
|
|
166
|
+
if (notificationDiff > 0 && instance.status !== 'IN_REVIEW') {
|
|
167
|
+
allInReview = allInReview && false;
|
|
168
|
+
return [...accIns, {
|
|
169
|
+
itemName: item.item.name,
|
|
170
|
+
dateDue: instance.nextDate,
|
|
171
|
+
status: 'Waiting for submission',
|
|
172
|
+
addresses,
|
|
173
|
+
}];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return accIns;
|
|
177
|
+
}, [])];
|
|
178
|
+
}, []);
|
|
179
|
+
if (!allInReview && fullItems.length) {
|
|
180
|
+
const uniqEmails = new Set<string>();
|
|
181
|
+
fullItems.forEach((item) => item.addresses.forEach((address) => uniqEmails.add(address)));
|
|
182
|
+
|
|
183
|
+
await Promise.all(Array.from(uniqEmails).map(async (emailAddress) => {
|
|
184
|
+
const email: EmailWithItems = {
|
|
185
|
+
recipientName: borrower.borrower.name,
|
|
186
|
+
complianceBorrowerId: String(borrower._id),
|
|
187
|
+
addresses: [emailAddress],
|
|
188
|
+
subject: 'Reminder',
|
|
189
|
+
text: '',
|
|
190
|
+
hasPastDueItems,
|
|
191
|
+
items: fullItems.filter((item) => item.addresses.includes(emailAddress)),
|
|
192
|
+
};
|
|
193
|
+
await this.sendEmailNotification(email, borrower.borrower._id.toString());
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async sendEmailNotification(email: EmailWithItems, borrowerId: string) {
|
|
199
|
+
const organizations = await this.organizationsService.getOrganizationForBorrower(borrowerId);
|
|
200
|
+
const senderEmail = await this.organizationEmailsService.getEmail(organizations._id.toString(), OrganizationEmailsType.SENDER);
|
|
201
|
+
const hiddenRecipientEmail = await this.organizationEmailsService.getEmail(organizations._id.toString(), OrganizationEmailsType.HIDDEN_RECIPIENT);
|
|
202
|
+
fs.readFile(
|
|
203
|
+
path.resolve(__dirname, '../public/emails', organizations.subfolder, 'notification.html'),
|
|
204
|
+
'utf8',
|
|
205
|
+
async (error, data) => {
|
|
206
|
+
if (error) {
|
|
207
|
+
console.error({ error });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const signature = await this.getSignatureAsync(ESenderType.REGULAR, organizations.subfolder);
|
|
212
|
+
|
|
213
|
+
const tableRows = email.items.reduce((acc, item) => {
|
|
214
|
+
return `${acc}
|
|
215
|
+
<tr>
|
|
216
|
+
<td style="border: 1px solid black">${item.itemName}</td>
|
|
217
|
+
<td style="border: 1px solid black">${item.dateDue.toLocaleDateString()}</td>
|
|
218
|
+
<td style="border: 1px solid black">${item.status}</td>
|
|
219
|
+
</tr>`;
|
|
220
|
+
}, '');
|
|
221
|
+
|
|
222
|
+
const tableHtml = `
|
|
223
|
+
<table style="width:100%; border: 1px solid black; border-spacing: 0" >
|
|
224
|
+
<tr>
|
|
225
|
+
<th style="border: 2px solid black">Compliance Item</th>
|
|
226
|
+
<th style="border: 2px solid black">Date Due</th>
|
|
227
|
+
<th style="border: 2px solid black">Status</th>
|
|
228
|
+
</tr>
|
|
229
|
+
${tableRows}
|
|
230
|
+
</table>`;
|
|
231
|
+
|
|
232
|
+
const hasPastDueItemsWarning = email.hasPastDueItems
|
|
233
|
+
? `<p><span style="color: #ff0000;"><strong>You have past due items which is a breach of the obligations under the loan agreement. Failure to submit the above documents may cause a delay in funding and an event of default. If you cannot submit the documents please contact us urgently.</strong></span></p>`
|
|
234
|
+
: '';
|
|
235
|
+
|
|
236
|
+
const replacedData = data
|
|
237
|
+
.replace('${recipientName}', email.recipientName)
|
|
238
|
+
.replace('${items}', tableHtml)
|
|
239
|
+
.replace('${hasPastDueItems}', hasPastDueItemsWarning)
|
|
240
|
+
.replace('${signature}', signature);
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
this.transporter.sendMail({
|
|
244
|
+
from: `${senderEmail.title} <${senderEmail.address}>`,
|
|
245
|
+
to: email.addresses,
|
|
246
|
+
bcc: `${hiddenRecipientEmail.title} <${hiddenRecipientEmail.address}>`,
|
|
247
|
+
subject: email.subject,
|
|
248
|
+
text: 'non-html',
|
|
249
|
+
html: replacedData,
|
|
250
|
+
}, async (err, info) => {
|
|
251
|
+
await this.senderHandler(err, info);
|
|
252
|
+
const complianceBorrowersService = this.getComplianceBorrowersService();
|
|
253
|
+
await complianceBorrowersService.updateLastEmailSent(email.complianceBorrowerId, new Date());
|
|
254
|
+
});
|
|
255
|
+
} catch (e) {
|
|
256
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async sendPasswordEmail(user: IKeycloakUser, password: string) {
|
|
262
|
+
fs.readFile(
|
|
263
|
+
path.resolve(__dirname, '../public/emails', 'password-reset.html'),
|
|
264
|
+
'utf8',
|
|
265
|
+
async (error, data) => {
|
|
266
|
+
if (error) {
|
|
267
|
+
console.error({ error });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const signature = await this.getSignatureAsync(ESenderType.REGULAR);
|
|
272
|
+
|
|
273
|
+
const replacedData = data
|
|
274
|
+
.replace('${username}', user.username)
|
|
275
|
+
.replace('${password}', password.toString())
|
|
276
|
+
.replace('${signature}', signature);
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
this.transporter.sendMail({
|
|
280
|
+
from: this.getSender(ESenderType.REGULAR),
|
|
281
|
+
to: user.email,
|
|
282
|
+
bcc: this.config.hiddenRecipient,
|
|
283
|
+
subject: `Welcome to ${this.config.appTitle}`,
|
|
284
|
+
text: 'non-html',
|
|
285
|
+
html: replacedData,
|
|
286
|
+
}, async (err, info) => {
|
|
287
|
+
await this.senderHandler(err, info);
|
|
288
|
+
});
|
|
289
|
+
} catch (e) {
|
|
290
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async sendDailyUpdates() {
|
|
296
|
+
fs.readFile(
|
|
297
|
+
path.resolve(__dirname, '../public/emails', 'daily-updates.html'),
|
|
298
|
+
'utf8',
|
|
299
|
+
async (error, data) => {
|
|
300
|
+
if (error) {
|
|
301
|
+
console.error({ error });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const signature = await this.getSignatureAsync(ESenderType.REGULAR);
|
|
306
|
+
|
|
307
|
+
const borrowerWithFiles: BorrowerWithFiles[] = await this.fileManagerService.getFilesNewerThanDate(dayjs().startOf('day').toDate());
|
|
308
|
+
|
|
309
|
+
if (!borrowerWithFiles.length) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const borrowerTables = borrowerWithFiles.reduce((allBorrowers, borrower) => {
|
|
314
|
+
|
|
315
|
+
const tableRows = borrower.files.reduce((borrowerAcc, file) => {
|
|
316
|
+
return `
|
|
317
|
+
${borrowerAcc}
|
|
318
|
+
<tr>
|
|
319
|
+
<td style="border: 1px solid black">${file.name}</td>
|
|
320
|
+
<td style="border: 1px solid black">${file.uploadedAt.toLocaleDateString()}</td>
|
|
321
|
+
</tr>`;
|
|
322
|
+
}, '');
|
|
323
|
+
|
|
324
|
+
const borrowerLink = `https://analytics.traxabl.com/compliance/borrowers/${borrower._id}`;
|
|
325
|
+
|
|
326
|
+
return `
|
|
327
|
+
${allBorrowers}
|
|
328
|
+
<br>
|
|
329
|
+
<a href="${borrowerLink}">${borrower.borrower.name}</a>
|
|
330
|
+
<table style="width:100%; border: 1px solid black; border-spacing: 0">
|
|
331
|
+
<tr>
|
|
332
|
+
<th style="border: 2px solid black">File name</th>
|
|
333
|
+
<th style="border: 2px solid black">Uploaded at</th>
|
|
334
|
+
</tr>
|
|
335
|
+
${tableRows}
|
|
336
|
+
</table>`;
|
|
337
|
+
}, '');
|
|
338
|
+
|
|
339
|
+
const financialSettings: IFinancialComplianceSettingsDocument = await FinancialCompliance.findOne({});
|
|
340
|
+
const financialEmails = financialSettings.financialEmails
|
|
341
|
+
.reduce((acc, email) => email.isActive ? [...acc, email.email] : acc, []);
|
|
342
|
+
|
|
343
|
+
const replacedData = data
|
|
344
|
+
.replace('${borrowerTables}', borrowerTables)
|
|
345
|
+
.replace('${signature}', signature);
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
this.transporter.sendMail({
|
|
349
|
+
from: this.getSender(ESenderType.REGULAR),
|
|
350
|
+
to: financialEmails,
|
|
351
|
+
bcc: [],
|
|
352
|
+
subject: `${this.config.appTitle} / Daily uploads`,
|
|
353
|
+
text: 'non-html',
|
|
354
|
+
html: replacedData,
|
|
355
|
+
}, async (err, info) => {
|
|
356
|
+
await this.senderHandler(err, info);
|
|
357
|
+
});
|
|
358
|
+
} catch (e) {
|
|
359
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async sendSimpleNotificationMail(email: IEmail) {
|
|
365
|
+
this.transporter.sendMail({
|
|
366
|
+
from: this.getSender(ESenderType.REGULAR),
|
|
367
|
+
to: email.addresses,
|
|
368
|
+
bcc: this.config.hiddenRecipient,
|
|
369
|
+
subject: email.subject,
|
|
370
|
+
text: email.text,
|
|
371
|
+
html: `<b>${email.text}</b>`,
|
|
372
|
+
}, async (err, info) => {
|
|
373
|
+
await this.senderHandler(err, info);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async sendFinancialExternal(email: IEmail, organizationId: string, stopFunding = false) {
|
|
378
|
+
const organization = await Organization.findById(organizationId).lean();
|
|
379
|
+
const senderEmail = await this.organizationEmailsService.getEmail(organizationId, OrganizationEmailsType.SENDER);
|
|
380
|
+
const financialEmail = await this.organizationEmailsService.getEmail(organizationId, OrganizationEmailsType.FINANCIAL);
|
|
381
|
+
fs.readFile(
|
|
382
|
+
path.resolve(__dirname, '../public/emails', organization.subfolder, stopFunding ? 'financial-external-on-stop.html' : 'financial-external-until-stop_date.html'),
|
|
383
|
+
'utf8',
|
|
384
|
+
async (error, data) => {
|
|
385
|
+
if (error) {
|
|
386
|
+
console.error({ error });
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const financialSettings = await this.financialComplianceService.getFinancialComplianceSettings();
|
|
391
|
+
const financialEmails = financialSettings.financialEmails
|
|
392
|
+
.reduce((acc, email) => email.isActive ? [...acc, email.email] : acc, []);
|
|
393
|
+
|
|
394
|
+
const signature = await this.getSignatureAsync(ESenderType.FINANCIAL, organization.subfolder);
|
|
395
|
+
|
|
396
|
+
const borrower: IFinancialComplianceBorrowerDocumentWithMails = email.customData;
|
|
397
|
+
|
|
398
|
+
const replyText = `I confirm that we have sufficient availability and request to add the statement balance of ${new Intl.NumberFormat('us-US').format(borrower.balanceOutstanding)} to the loan.`;
|
|
399
|
+
|
|
400
|
+
const mailTo: string[] = [senderEmail.address, financialEmail.address];
|
|
401
|
+
|
|
402
|
+
const replyLink = `<a href="mailto:${mailTo.join(',')}?subject=Re:%20Email%20Answer&body=${replyText.replace(' ', '%20')}">by reply to this email</a>`;
|
|
403
|
+
|
|
404
|
+
const replacedData = data
|
|
405
|
+
.replaceAll('${BORROWER_NAME}', borrower.borrower.title)
|
|
406
|
+
.replaceAll('${REMINDER_COUNTER}', (borrower.reminderCounter + 1).toString())
|
|
407
|
+
.replaceAll('${ORIGINAL_BALANCE}', new Intl.NumberFormat('us-US').format(borrower.amountOwed))
|
|
408
|
+
.replaceAll('${AMOUNT_OUTSTANDING}', new Intl.NumberFormat('us-US').format(borrower.balanceOutstanding))
|
|
409
|
+
.replaceAll('${DUE_DATE}', dayjs(borrower.dueDate).format('M/D/YYYY'))
|
|
410
|
+
.replaceAll('${REPLY_LINK}', replyLink)
|
|
411
|
+
.replaceAll('${signature}', signature);
|
|
412
|
+
|
|
413
|
+
return this.transporter.sendMail({
|
|
414
|
+
from: `${senderEmail.title} <${senderEmail.address}>`,
|
|
415
|
+
to: email.addresses,
|
|
416
|
+
bcc: financialEmails,
|
|
417
|
+
subject: email.subject,
|
|
418
|
+
text: 'non-html',
|
|
419
|
+
html: replacedData,
|
|
420
|
+
}, async (err, info) => {
|
|
421
|
+
await this.senderHandler(err, info);
|
|
422
|
+
await this.financialComplianceService.updateLastReminder(String(borrower._id));
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async sendFinancialInternal(email: EmailWithFinancial, stopFunding = false) {
|
|
428
|
+
fs.readFile(
|
|
429
|
+
path.resolve(__dirname, '../public/emails', 'financial-internal.html'),
|
|
430
|
+
'utf8',
|
|
431
|
+
async (error, data) => {
|
|
432
|
+
if (error) {
|
|
433
|
+
console.error({ error });
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const signature = await this.getSignatureAsync(ESenderType.FINANCIAL);
|
|
438
|
+
|
|
439
|
+
const text = stopFunding
|
|
440
|
+
? `<p style="color: #ff0000;"><b>The table below sets out the status of borrowers who have failed to paid their interest statement.</b></p>`
|
|
441
|
+
: `<p><b>The following Borrowers have not paid in, or requested to add their statement to the loan:</b></p>`;
|
|
442
|
+
|
|
443
|
+
const borrowerTables = email.borrowers.reduce((allBorrowers, borrower) => {
|
|
444
|
+
|
|
445
|
+
return `
|
|
446
|
+
${allBorrowers}
|
|
447
|
+
<tr>
|
|
448
|
+
<td style="border: 1px solid black; text-align: center">${borrower.borrower.name}</td>
|
|
449
|
+
<td style="border: 1px solid black; text-align: right">${new Intl.NumberFormat('us-US').format(borrower.amountOwed)}</td>
|
|
450
|
+
<td style="border: 1px solid black; text-align: right">${new Intl.NumberFormat('us-US').format(borrower.amountReceived)}</td>
|
|
451
|
+
<td style="border: 1px solid black; text-align: right">${new Intl.NumberFormat('us-US').format(borrower.balanceOutstanding)}</td>
|
|
452
|
+
<td style="border: 1px solid black; text-align: center">${dayjs(new Date(borrower.lastReminderSentAt)).format('M/D/YYYY')}</td>
|
|
453
|
+
</tr>`;
|
|
454
|
+
|
|
455
|
+
}, '');
|
|
456
|
+
|
|
457
|
+
const borrowersTable = `
|
|
458
|
+
<br>
|
|
459
|
+
<table style="width:100%; border: 1px solid black; border-spacing: 0">
|
|
460
|
+
<tr>
|
|
461
|
+
<th style="border: 2px solid black; text-align: center">Borrower Name</th>
|
|
462
|
+
<th style="border: 2px solid black; text-align: right">Original Amount</th>
|
|
463
|
+
<th style="border: 2px solid black; text-align: right">Amount Paid</th>
|
|
464
|
+
<th style="border: 2px solid black; text-align: right">Amount Due</th>
|
|
465
|
+
<th style="border: 2px solid black; text-align: center">Last notification</th>
|
|
466
|
+
</tr>
|
|
467
|
+
${borrowerTables}
|
|
468
|
+
</table>`;
|
|
469
|
+
|
|
470
|
+
const replacedData = data
|
|
471
|
+
.replace('${text}', text)
|
|
472
|
+
.replace('${borrowerTable}', borrowersTable)
|
|
473
|
+
.replace('${signature}', signature);
|
|
474
|
+
|
|
475
|
+
try {
|
|
476
|
+
this.transporter.sendMail({
|
|
477
|
+
from: this.getSender(ESenderType.FINANCIAL),
|
|
478
|
+
to: email.addresses,
|
|
479
|
+
bcc: this.config.hiddenRecipient,
|
|
480
|
+
subject: email.subject,
|
|
481
|
+
text: 'non-html',
|
|
482
|
+
html: replacedData,
|
|
483
|
+
}, async (err, info) => {
|
|
484
|
+
await this.senderHandler(err, info);
|
|
485
|
+
});
|
|
486
|
+
} catch (e) {
|
|
487
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private async getEmailStatementParams(borrowerId: string) {
|
|
493
|
+
const financialSettings = await this.financialComplianceService.getFinancialComplianceSettings();
|
|
494
|
+
const financialEmails = financialSettings.financialEmails
|
|
495
|
+
.reduce((acc, email) => email.isActive ? [...acc, email.email] : acc, []);
|
|
496
|
+
|
|
497
|
+
const borrower = await this.borrowerService.getBorrowerById(borrowerId);
|
|
498
|
+
const organization = await this.organizationsService.getOrganizationForBorrower(borrowerId);
|
|
499
|
+
const products = await this.loanChargesService.getLoanProducts(borrower._id.toString());
|
|
500
|
+
const borrowerHasRevolver = products.filter((product) => product.type === ELoanTypes.REVOLVER && product.active).length > 0;
|
|
501
|
+
const textPath = path.resolve(__dirname, '../public/emails', organization.subfolder, borrowerHasRevolver ? 'interest-statement.html' : 'interest-statement-only-term.html');
|
|
502
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
503
|
+
const signature = await this.getSignatureAsync(ESenderType.FINANCIAL, organization.subfolder);
|
|
504
|
+
|
|
505
|
+
const borrowerRepresentation = borrower
|
|
506
|
+
? borrower.title
|
|
507
|
+
: borrower.code;
|
|
508
|
+
|
|
509
|
+
const complianceBorrowersService = this.getComplianceBorrowersService();
|
|
510
|
+
const borrowerEmails = await complianceBorrowersService.getBorrowerEmailsGrouped(String(borrower._id));
|
|
511
|
+
|
|
512
|
+
const mappedEmails = [
|
|
513
|
+
...borrowerEmails.mainEmails.map((email) => email.email),
|
|
514
|
+
...borrowerEmails.financialEmails.map((email) => email.email),
|
|
515
|
+
];
|
|
516
|
+
|
|
517
|
+
const replacedText = text
|
|
518
|
+
.replaceAll('${BORROWER_NAME}', borrowerRepresentation)
|
|
519
|
+
.replaceAll('${MONTH}', dayjs().subtract(1, 'month').format('MMMM'))
|
|
520
|
+
.replaceAll('${signature}', signature);
|
|
521
|
+
|
|
522
|
+
return { financialEmails, mappedEmails, replacedText };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async sendEmailWithAttach(borrowerCode: string, files: IMulterFile[]) {
|
|
526
|
+
try {
|
|
527
|
+
const attachments = files.map((file) => ({
|
|
528
|
+
filename: file.originalname,
|
|
529
|
+
path: path.resolve(__dirname, '../uploads', file.filename),
|
|
530
|
+
}));
|
|
531
|
+
|
|
532
|
+
const { financialEmails, mappedEmails, replacedText } = await this.getEmailStatementParams(borrowerCode);
|
|
533
|
+
|
|
534
|
+
this.transporter.sendMail({
|
|
535
|
+
from: this.getSender(ESenderType.FINANCIAL),
|
|
536
|
+
to: mappedEmails,
|
|
537
|
+
bcc: financialEmails,
|
|
538
|
+
subject: `${borrowerCode} INTEREST STATEMENT`,
|
|
539
|
+
text: 'non-html',
|
|
540
|
+
html: replacedText,
|
|
541
|
+
attachments,
|
|
542
|
+
}, async (err, info) => {
|
|
543
|
+
await this.senderHandler(err, info);
|
|
544
|
+
await Promise.all(attachments.map(async (file) => {
|
|
545
|
+
fs.unlink(file.path, (err) => {
|
|
546
|
+
if (err) {
|
|
547
|
+
console.error(err);
|
|
548
|
+
} else {
|
|
549
|
+
console.log('deleted');
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}));
|
|
553
|
+
});
|
|
554
|
+
} catch (e) {
|
|
555
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async sendEmailWithStatement(borrowerId: string, statement: { filename: string; content: Buffer }) {
|
|
560
|
+
try {
|
|
561
|
+
const borrower = await this.borrowerService.getBorrowerById(borrowerId);
|
|
562
|
+
if (!borrower) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const organization = await this.organizationsService.getOrganizationForBorrower(borrowerId);
|
|
566
|
+
const sender = await this.organizationEmailsService.getEmail(organization._id.toString(), OrganizationEmailsType.FINANCIAL);
|
|
567
|
+
|
|
568
|
+
const attachments = [statement];
|
|
569
|
+
|
|
570
|
+
const { financialEmails, mappedEmails, replacedText } = await this.getEmailStatementParams(borrowerId);
|
|
571
|
+
|
|
572
|
+
this.transporter.sendMail({
|
|
573
|
+
from: `${sender.title} <${sender.address}>`,
|
|
574
|
+
to: mappedEmails,
|
|
575
|
+
bcc: financialEmails,
|
|
576
|
+
subject: `${borrower.code} INTEREST STATEMENT`,
|
|
577
|
+
text: 'non-html',
|
|
578
|
+
html: replacedText,
|
|
579
|
+
attachments,
|
|
580
|
+
}, async (err, info) => {
|
|
581
|
+
await this.senderHandler(err, info);
|
|
582
|
+
});
|
|
583
|
+
} catch (e) {
|
|
584
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async sendEmailWithSimpleAttachments(email: IEmail, attachments: any[]) {
|
|
589
|
+
const textPath = path.resolve(__dirname, '../public/emails', 'simple-mail.html');
|
|
590
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
591
|
+
const signature = await this.getSignatureAsync(ESenderType.GOAT);
|
|
592
|
+
const replacedData = text
|
|
593
|
+
.replace('${emailText}', '<p>Here’s your file:</p>')
|
|
594
|
+
.replace('${signature}', signature);
|
|
595
|
+
try {
|
|
596
|
+
this.transporter.sendMail({
|
|
597
|
+
from: this.getSender(ESenderType.REGULAR),
|
|
598
|
+
to: email.addresses,
|
|
599
|
+
bcc: this.config.hiddenRecipient,
|
|
600
|
+
subject: email.subject,
|
|
601
|
+
text: email.subject,
|
|
602
|
+
html: replacedData,
|
|
603
|
+
attachments,
|
|
604
|
+
}, async (err, info) => {
|
|
605
|
+
await this.senderHandler(err, info);
|
|
606
|
+
});
|
|
607
|
+
} catch (e) {
|
|
608
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async sendLoanUploadReport(email: IEmail, attachments: any[], borrowerId: string) {
|
|
613
|
+
const organization = await this.organizationsService.getOrganizationForBorrower(borrowerId);
|
|
614
|
+
const textPath = path.resolve(__dirname, '../public/emails', organization.subfolder, 'loan-upload.html');
|
|
615
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
616
|
+
const signature = await this.getSignatureAsync(ESenderType.GOAT, organization.subfolder);
|
|
617
|
+
const senderEmail = await this.organizationEmailsService.getEmail(organization._id.toString(), OrganizationEmailsType.FINANCIAL);
|
|
618
|
+
const replacedData = text
|
|
619
|
+
.replace('${emailText}', `<p>${email.text}</p>`)
|
|
620
|
+
.replace('${signature}', signature);
|
|
621
|
+
try {
|
|
622
|
+
this.transporter.sendMail({
|
|
623
|
+
from: `${senderEmail.title} <${senderEmail.address}>`,
|
|
624
|
+
to: email.addresses,
|
|
625
|
+
subject: email.subject,
|
|
626
|
+
cc: `${senderEmail.title} <${senderEmail.address}>`,
|
|
627
|
+
text: email.subject,
|
|
628
|
+
html: replacedData,
|
|
629
|
+
attachments,
|
|
630
|
+
}, async (err, info) => {
|
|
631
|
+
await this.senderHandler(err, info);
|
|
632
|
+
});
|
|
633
|
+
} catch (e) {
|
|
634
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async sendPlaidReminder(email: IEmail) {
|
|
639
|
+
const textPath = path.resolve(__dirname, '../public/emails', 'plaid-reminder.html');
|
|
640
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
641
|
+
const signature = await this.getSignatureAsync(ESenderType.FINANCIAL);
|
|
642
|
+
const replacedData = text
|
|
643
|
+
.replace('${signature}', signature);
|
|
644
|
+
try {
|
|
645
|
+
this.transporter.sendMail({
|
|
646
|
+
from: this.getSender(ESenderType.REGULAR),
|
|
647
|
+
to: email.addresses,
|
|
648
|
+
subject: `🔴 ${email.subject}`,
|
|
649
|
+
cc: this.getSender(ESenderType.REGULAR),
|
|
650
|
+
text: email.subject,
|
|
651
|
+
html: replacedData,
|
|
652
|
+
headers: {
|
|
653
|
+
'priority': 'high',
|
|
654
|
+
},
|
|
655
|
+
attachments: [{
|
|
656
|
+
filename: 'plaid-reminder-instruction.png',
|
|
657
|
+
path: process.cwd() + '/public/images/plaid-reminder-instruction.png',
|
|
658
|
+
cid: 'plaid-instruction',
|
|
659
|
+
}],
|
|
660
|
+
}, async (err, info) => {
|
|
661
|
+
await this.senderHandler(err, info);
|
|
662
|
+
});
|
|
663
|
+
} catch (e) {
|
|
664
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async sendEmailFTP(email: Partial<IEmail>, attachments: any[], success: boolean) {
|
|
669
|
+
const textPath = path.resolve(__dirname, '../public/emails', 'simple-mail.html');
|
|
670
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
671
|
+
const signature = await this.getSignatureAsync(ESenderType.GOAT);
|
|
672
|
+
const emailText = success ? 'the attached file was uploaded to the FTP site' : 'the attached file was NOT uploaded to the FTP site';
|
|
673
|
+
const replacedData = text
|
|
674
|
+
.replace('${emailText}', `<p>${emailText}</p>`)
|
|
675
|
+
.replace('${signature}', signature);
|
|
676
|
+
try {
|
|
677
|
+
this.transporter.sendMail({
|
|
678
|
+
from: this.getSender(ESenderType.FINANCIAL),
|
|
679
|
+
to: this.config.hiddenRecipient,
|
|
680
|
+
subject: email.subject,
|
|
681
|
+
text: emailText,
|
|
682
|
+
html: replacedData,
|
|
683
|
+
attachments,
|
|
684
|
+
}, async (err, info) => {
|
|
685
|
+
await this.senderHandler(err, info);
|
|
686
|
+
});
|
|
687
|
+
} catch (e) {
|
|
688
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async sendBrokerEmail(email: Partial<IEmail>, attachments: any[], replacements: { broker: string; month: string }) {
|
|
693
|
+
const textPath = path.resolve(__dirname, '../public/emails', 'broker-report.html');
|
|
694
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
695
|
+
const signature = await this.getSignatureAsync(ESenderType.FINANCIAL_ONLY);
|
|
696
|
+
const replacedData = text
|
|
697
|
+
.replace('${BORROWER_NAME}', replacements.broker)
|
|
698
|
+
.replace('${MONTH}', replacements.month)
|
|
699
|
+
.replace('${signature}', signature);
|
|
700
|
+
try {
|
|
701
|
+
this.transporter.sendMail({
|
|
702
|
+
from: this.getSender(ESenderType.FINANCIAL),
|
|
703
|
+
to: [...this.config.hiddenRecipient, ...email.addresses],
|
|
704
|
+
cc: this.getSender(ESenderType.FINANCIAL),
|
|
705
|
+
subject: email.subject,
|
|
706
|
+
text: replacedData,
|
|
707
|
+
html: replacedData,
|
|
708
|
+
attachments,
|
|
709
|
+
}, async (err, info) => {
|
|
710
|
+
await this.senderHandler(err, info);
|
|
711
|
+
});
|
|
712
|
+
} catch (e) {
|
|
713
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async sendProspectForm(email: Partial<IEmail>, replacements: { link: string, contactName: string }) {
|
|
718
|
+
const textPath = path.resolve(__dirname, '../public/emails', 'prospect-form.html');
|
|
719
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
720
|
+
const signature = await this.getSignatureAsync(ESenderType.FINANCIAL_ONLY);
|
|
721
|
+
const replacedData = text
|
|
722
|
+
.replaceAll('${LINK}', replacements.link)
|
|
723
|
+
.replaceAll('${CONTACT_NAME}', replacements.contactName)
|
|
724
|
+
.replace('${signature}', signature);
|
|
725
|
+
try {
|
|
726
|
+
this.transporter.sendMail({
|
|
727
|
+
from: this.getSender(ESenderType.FINANCIAL),
|
|
728
|
+
to: [...this.config.hiddenRecipient, ...email.addresses],
|
|
729
|
+
cc: this.getSender(ESenderType.FINANCIAL),
|
|
730
|
+
subject: email.subject,
|
|
731
|
+
text: replacedData,
|
|
732
|
+
html: replacedData,
|
|
733
|
+
}, async (err, info) => {
|
|
734
|
+
await this.senderHandler(err, info);
|
|
735
|
+
});
|
|
736
|
+
} catch (e) {
|
|
737
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async sendProspectReminder(email: Partial<IEmail>, replacements: {
|
|
742
|
+
link: string,
|
|
743
|
+
contactName: string,
|
|
744
|
+
hasEmptyFields: boolean,
|
|
745
|
+
requiredGroups: string[]
|
|
746
|
+
}) {
|
|
747
|
+
const textPath = path.resolve(__dirname, '../public/emails', 'prospect-form-reminder.html');
|
|
748
|
+
const text = await fs.promises.readFile(textPath, 'utf8');
|
|
749
|
+
const signature = await this.getSignatureAsync(ESenderType.FINANCIAL_ONLY);
|
|
750
|
+
const fileInfo = replacements.requiredGroups.length > 0
|
|
751
|
+
? ` and upload missing documents under the following sections: ${replacements.requiredGroups.join(', ')}`
|
|
752
|
+
: '';
|
|
753
|
+
const replacedData = text
|
|
754
|
+
.replaceAll('${LINK}', replacements.link)
|
|
755
|
+
.replaceAll('${CONTACT_NAME}', replacements.contactName)
|
|
756
|
+
.replace('${HAS_EMPTY_FIELDS}', replacements.hasEmptyFields ? ' and complete all sections of the form' : '')
|
|
757
|
+
.replace('${FILE_INFO}', fileInfo)
|
|
758
|
+
.replace('${signature}', signature);
|
|
759
|
+
try {
|
|
760
|
+
this.transporter.sendMail({
|
|
761
|
+
from: this.getSender(ESenderType.FINANCIAL),
|
|
762
|
+
to: [...this.config.hiddenRecipient, ...email.addresses],
|
|
763
|
+
cc: this.getSender(ESenderType.FINANCIAL),
|
|
764
|
+
subject: email.subject,
|
|
765
|
+
text: replacedData,
|
|
766
|
+
html: replacedData,
|
|
767
|
+
}, async (err, info) => {
|
|
768
|
+
await this.senderHandler(err, info);
|
|
769
|
+
});
|
|
770
|
+
} catch (e) {
|
|
771
|
+
this.sentryService.catchErrorBySentry(null, e);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|