gemcap-be-common 1.3.63 → 1.3.64

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.
@@ -1,1327 +0,0 @@
1
- // import dayjs from 'dayjs';
2
- // import _ from 'lodash';
3
- // import Decimal from 'decimal.js';
4
- // import ExcelJS from 'exceljs';
5
- //
6
- // import { reorderObject } from '../helpers/common.helper';
7
- // import { getLastTransactionForDate, getLoanTransactions } from '../db/loan-transactions.db';
8
- // import { BorrowerModel, IBorrowerDocument } from '../models/Borrower.model';
9
- // import { ILoanProductDoc, LoanProduct } from '../models/LoanProducts.model';
10
- // import { getAverageActualBalance } from '../db/loan-products.db';
11
- // import { LoanStatementTransactionModel } from '../models/LoanStatementTransaction.model';
12
- // import { getLatestSignedBBCDateDoc } from '../db/bbcDates.db';
13
- // import { EReserveTypes } from '../enums/reserve-types.enum';
14
- // import { EComplianceItemStatus } from '../models/ComplianceItem.model';
15
- // import { IExcelJsCell } from '../helpers/excel.helper';
16
- // import { ILoanChargeDoc, LoanCharge } from '../models/LoanCharges.model';
17
- // import { ELoanTransactionTypes } from '../models/LoanTransaction.model';
18
- //
19
- // import { BorrowerCompliance, IComplianceBorrowerDocument } from '../models/BorrowerCompliance.model';
20
- // import { BorrowerSummary } from '../models/BorrowerSummary.model';
21
- // import {
22
- // EFinanceSpreadingBSTotal,
23
- // EFinanceSpreadingPLTotal,
24
- // EFinancialSpreadingType, financialSpreadingTotalDictionary,
25
- // } from '../models/FinancialSpreadingSheet.model';
26
- // import { CRMProspectIndustry } from '../models/ProspectIndustry.model';
27
- // import { FinancialSpreadingDTO } from '../models/FinancialSpreading.model';
28
- //
29
- // import {
30
- // _availabilityService,
31
- // _borrowerService,
32
- // _borrowerSummaryService,
33
- // _complianceBorrowersService,
34
- // _financialSpreadingService,
35
- // _loanChargesService,
36
- // _loanTransactionsService,
37
- // } from '../index';
38
- // import { getShiftedMonth } from '../helpers/date.helper';
39
- // import { ETotalType, YIELD_TOTALS_MAP } from '../models/Yield.model';
40
- // import { getLoanChargeForProduct } from '../db/loan-charges.db';
41
- // import { getFinancialSpreadingData } from '../db/financial-spreading.db';
42
- // import { YieldService } from '../services/yield.service';
43
- // import { SignsService } from '../services/signs.service';
44
- //
45
- // type DataSheet = {
46
- // [p: string]: IExcelJsCell[][]
47
- // }
48
- //
49
- // type ReportRow = {
50
- // borrowerName: string;
51
- // productName: string;
52
- // maturityDate: string;
53
- // payOffDate: string;
54
- // commitmentAmount: string | number;
55
- // endingBalance: string | number;
56
- // endingParticipationBalance: string | number;
57
- // netExposure: string | number;
58
- // lastSignedBBCDate: string | number;
59
- // compliance: string;
60
- // averageBalanceSinceInception: string | number;
61
- // totalIncomeReceived: string | number;
62
- // loanLifeIRR: string | number;
63
- // MOIC: string | number;
64
- // }
65
- //
66
- // const numericFields_ReportRow: string[] = [
67
- // 'commitmentAmount',
68
- // 'endingBalance',
69
- // 'endingParticipationBalance',
70
- // 'netExposure',
71
- // 'averageBalanceSinceInception',
72
- // 'totalIncomeReceived',
73
- // 'loanLifeIRR',
74
- // 'MOIC',
75
- // ];
76
- //
77
- // const percentageFields_ReportRow: string[] = [
78
- // 'loanLifeIRR',
79
- // 'MOIC',
80
- // ];
81
- //
82
- // type BorrowerSummaryRow = {
83
- // title: string;
84
- // grossValue: string | number;
85
- // ineligible: string | number;
86
- // netValue: string | number;
87
- // advanceRate: string | number;
88
- // availability: string | number;
89
- // }
90
- //
91
- // type ComplianceItemRow = {
92
- // name: string;
93
- // dueDate: string;
94
- // progress: string;
95
- // submittedDate: string;
96
- // status: string;
97
- // }
98
- //
99
- // type FinancialIndexOptions = {
100
- // format: string;
101
- // title: string;
102
- // addEmptyRow: boolean;
103
- // style?: object,
104
- // }
105
- //
106
- // const styles = {
107
- // whiteOnBlack: {
108
- // fill: {
109
- // fgColor: { argb: 'FF000000' },
110
- // },
111
- // font: {
112
- // bold: true,
113
- // color: { argb: 'FFFFFFFF' },
114
- // },
115
- // },
116
- // whiteOnGray: {
117
- // fill: {
118
- // fgColor: { argb: 'FF7F7F7F' },
119
- // },
120
- // font: {
121
- // bold: true,
122
- // color: { argb: 'FFFFFFFF' },
123
- // },
124
- // },
125
- // };
126
- //
127
- // const numberFormat = {
128
- // fullNumber: '#,##0.00',
129
- // shortNumber: '#,##0',
130
- // xNumber: '0.00"x"',
131
- // xNumber1DP: '0.0"x"',
132
- // thousands: '0.0,',
133
- // percent: '0.00%',
134
- // percent1DP: '0.0%',
135
- // };
136
- //
137
- // const borrowerTitleLength = 12;
138
- // const dateFormat = 'MM-DD-YYYY';
139
- //
140
- // const productsDataMap = new Map<string, ReportRow>();
141
- // const borrowersMap = new Map<string, IBorrowerDocument>();
142
- // const productsMap = new Map<string, ILoanProductDoc>();
143
- // const chargesMap = new Map<string, ILoanChargeDoc>();
144
- // const complianceBorrowersMap = new Map<string, IComplianceBorrowerDocument>();
145
- //
146
- // const borrowerSummaryRowFields = {
147
- // title: {
148
- // t: 's',
149
- // },
150
- // 'grossValue': {
151
- // t: 'n',
152
- // z: numberFormat.thousands,
153
- // },
154
- // 'ineligible': {
155
- // t: 'n',
156
- // z: numberFormat.thousands,
157
- // },
158
- // 'netValue': {
159
- // t: 'n',
160
- // z: numberFormat.thousands,
161
- // },
162
- // 'advanceRate': {
163
- // t: 'n',
164
- // z: numberFormat.percent,
165
- // },
166
- // 'availability': {
167
- // t: 'n',
168
- // z: numberFormat.thousands,
169
- // },
170
- // };
171
- //
172
- // const emptyRow: IExcelJsCell[] = [{ v: '', t: 's' }];
173
- // const negativeCell: IExcelJsCell = { v: 'NA', t: 's', s: { alignment: { horizontal: 'right' } } };
174
- // const rightAlign: {
175
- // alignment: Partial<ExcelJS.Alignment>;
176
- // } = {
177
- // alignment: { horizontal: 'right' },
178
- // };
179
- //
180
- //
181
- // export class ExcelReport {
182
- // constructor(
183
- // private readonly yieldService: YieldService,
184
- // private readonly signsService: SignsService,
185
- // ) {
186
- // }
187
- //
188
- // private async getMainData(borrowerIds: string[], start: Date, end: Date): Promise<DataSheet> {
189
- // const periodDays = 360;
190
- //
191
- // const header: ReportRow = {
192
- // borrowerName: 'Client name',
193
- // productName: 'Product name',
194
- // maturityDate: 'Maturity date',
195
- // payOffDate: 'PayOff Date',
196
- // commitmentAmount: 'Commitment amount',
197
- // endingBalance: 'Ending Balance',
198
- // endingParticipationBalance: 'Ending Participation balance',
199
- // netExposure: 'Net Exposure',
200
- // lastSignedBBCDate: 'Last Signed BBC',
201
- // compliance: 'Compliance',
202
- // averageBalanceSinceInception: 'Average Balance Since Inception',
203
- // totalIncomeReceived: 'Total Income Received',
204
- // loanLifeIRR: 'Loan Life IRR',
205
- // MOIC: 'MOIC',
206
- // };
207
- //
208
- // const headerKeys = Object.keys(header);
209
- //
210
- // const allBorrowersSummary: ReportRow[] = [];
211
- //
212
- // for (const borrowerId of borrowerIds) {
213
- // let periodEnd = end;
214
- // const borrower = borrowersMap.get(borrowerId);
215
- // if (!borrower) {
216
- // continue;
217
- // }
218
- // const products = [...productsMap.values()].filter((product) => product.borrowerId.toString() === borrowerId);
219
- // const complianceBorrower = [...complianceBorrowersMap.values()].find((complianceBorrower) => complianceBorrower.borrower.toString() === borrowerId);
220
- // for (const product of products) {
221
- // if (product.deactivationDate) {
222
- // continue;
223
- // }
224
- // if (dayjs(product.payoffDate).isBefore(dayjs(periodEnd))) {
225
- // periodEnd = product.payoffDate;
226
- // }
227
- // const daysHeld = dayjs(periodEnd).diff(dayjs(product.startDate), 'day');
228
- // const lastTransaction = await getLastTransactionForDate(product._id.toString(), periodEnd);
229
- // const endingBalance = lastTransaction ? lastTransaction.balance : 0;
230
- // const endingParticipationBalance = product.isParticipant ? endingBalance : 0;
231
- // const netExposure = new Decimal(endingBalance).sub(endingParticipationBalance).toNumber();
232
- // const lastSignedBBC = await this.signsService.getLatestSignedBBCDate(borrowerId, periodEnd);
233
- // const lastSignedBBCDate = lastSignedBBC ? dayjs(lastSignedBBC.bbcDate).format(dateFormat) : '';
234
- // const averageBalanceSinceInception = new Decimal(await getAverageActualBalance(product._id.toString(), {
235
- // start: dayjs(product.startDate),
236
- // end: dayjs(periodEnd),
237
- // })).toDP(2).toNumber();
238
- //
239
- // const totalIncomeReceived = await this.getPaidAmount(product._id.toString());
240
- // const MOIC = averageBalanceSinceInception ? new Decimal(totalIncomeReceived).add(Math.abs(averageBalanceSinceInception)).div(Math.abs(averageBalanceSinceInception)).toDP(2).toNumber() : 0;
241
- // const loanLifeIRRMul = daysHeld ? new Decimal(periodDays).div(daysHeld).toNumber() : 0;
242
- // const loanLifeIRR = MOIC ? new Decimal(MOIC).pow(loanLifeIRRMul).sub(1).toDP(4).toNumber() : 0;
243
- //
244
- // const newDataRow: ReportRow = {
245
- // borrowerName: borrower.name,
246
- // productName: product.name,
247
- // maturityDate: product.maturityDate ? dayjs(product.maturityDate).format(dateFormat) : '',
248
- // payOffDate: product.payoffDate ? dayjs(product.payoffDate).format(dateFormat) : '',
249
- // commitmentAmount: product.commitment,
250
- // endingBalance,
251
- // endingParticipationBalance,
252
- // netExposure,
253
- // lastSignedBBCDate,
254
- // compliance: complianceBorrower ? complianceBorrower.fundingStatus : '',
255
- // averageBalanceSinceInception,
256
- // totalIncomeReceived,
257
- // loanLifeIRR,
258
- // MOIC,
259
- // };
260
- // productsDataMap.set(product._id.toString(), newDataRow);
261
- // allBorrowersSummary.push(newDataRow);
262
- // }
263
- // }
264
- // const sortedData = _.orderBy(allBorrowersSummary, ['borrowerName', 'productName'], ['asc', 'asc']);
265
- // const headerAsArray = reorderObject(header, headerKeys);
266
- // const dataAsArrays = sortedData.map((row) => reorderObject(row, headerKeys));
267
- // const headerWithStyles = Object.values(headerAsArray).reduce((acc, header) => [...acc, {
268
- // v: header,
269
- // t: 's',
270
- // s: styles.whiteOnGray,
271
- // }], []);
272
- // const dataWithStyles = Object.values(dataAsArrays).map((dataRow) => {
273
- // return Object.entries(dataRow).map(([key, v]) => ({
274
- // v,
275
- // t: numericFields_ReportRow.includes(key) ? 'n' : 's',
276
- // ...(numericFields_ReportRow.includes(key) ? { z: percentageFields_ReportRow.includes(key) ? numberFormat.percent : numberFormat.thousands } : {}),
277
- // }));
278
- // });
279
- // const reportPeriod = [
280
- // [{ v: 'Report period', t: 's' }],
281
- // [{ v: 'From', t: 's' }, { v: dayjs(start).utcOffset(0).format(dateFormat), t: 's' }],
282
- // [{ v: 'To', t: 's' }, { v: dayjs(end).utcOffset(0).format(dateFormat), t: 's' }],
283
- // ];
284
- // return { report: [...reportPeriod, emptyRow, headerWithStyles, ...dataWithStyles] };
285
- // };
286
- //
287
- // private async getAllBorrowersSummary(borrowerIds: string[], start: Date, end: Date): Promise<DataSheet> {
288
- // const borrowerSummary: { [borrowerName: string]: IExcelJsCell[][] } = {};
289
- // for (const borrowerId of borrowerIds) {
290
- // const borrower = borrowersMap.get(borrowerId);
291
- // const borrowerNameOrId = borrower ? borrower.name : borrowerId;
292
- // const borrowerData = await this.getBorrowerSummary(borrowerId, start, end);
293
- // if (borrowerData) {
294
- // borrowerSummary[borrowerNameOrId] = borrowerData;
295
- // }
296
- // }
297
- // return borrowerSummary;
298
- // };
299
- //
300
- // private async getBorrowerSummary(borrowerId: string, start: Date, end: Date): Promise<IExcelJsCell[][]> {
301
- // const header: BorrowerSummaryRow = {
302
- // title: '',
303
- // grossValue: 'Gross Value',
304
- // ineligible: 'Ineligible',
305
- // netValue: 'Net Value',
306
- // advanceRate: 'Advance Rate',
307
- // availability: 'Availability',
308
- // };
309
- //
310
- // const borrower = borrowersMap.get(borrowerId);
311
- // const borrowerTitle: IExcelJsCell[] = [
312
- // { v: borrower?.name ?? borrowerId, s: styles.whiteOnBlack },
313
- // ...Array(borrowerTitleLength).fill({ v: '', s: styles.whiteOnBlack }),
314
- // ];
315
- //
316
- // const getBorrowerSubTitle = async (): Promise<IExcelJsCell[]> => {
317
- // const borrowerOption = await _borrowerService.getBorrowerOption(borrowerId, 'NAISC Code');
318
- // if (!borrowerOption) {
319
- // return [{ v: '' }];
320
- // }
321
- // const industry = await CRMProspectIndustry.findOne({ code: borrowerOption.dataValue.toString() });
322
- // if (!industry) {
323
- // return [{ v: `NASIC CODE: ${borrowerOption.dataValue}` }];
324
- // }
325
- // return [{ v: `NASIC CODE: ${borrowerOption.dataValue} INDUSTRY: ${industry.name.toLocaleUpperCase()}` }];
326
- // };
327
- // const borrowerSubTitle = await getBorrowerSubTitle();
328
- //
329
- // const borrowerSummary = await BorrowerSummary.findOne({ borrowerId });
330
- //
331
- // const getSegment = (title: string) => {
332
- // const segment: IExcelJsCell[] = [
333
- // { v: title, t: 's', s: styles.whiteOnGray },
334
- // ...Array(borrowerTitleLength).fill({ v: '', t: 's', s: styles.whiteOnGray }),
335
- // ];
336
- // return segment;
337
- // };
338
- //
339
- // const headerKeys = Object.keys(header);
340
- // const headerAsArray = reorderObject(header, headerKeys);
341
- // const headerWithStyles = Object.values(headerAsArray).reduce((acc, v) => [...acc, {
342
- // v,
343
- // t: 's',
344
- // s: rightAlign,
345
- // }], []);
346
- //
347
- // const lastBBC = await getLatestSignedBBCDateDoc(borrowerId);
348
- // if (!lastBBC) {
349
- // return null;
350
- // }
351
- // const availability = await _availabilityService.getAllSummaries(lastBBC._id, null);
352
- //
353
- // // INVENTORY
354
- // const inventoryRows = availability.inventory.reduce((acc, inventory) => {
355
- // if (inventory.inventoryName === 'TOTAL') {
356
- // return acc;
357
- // }
358
- // const newRow: BorrowerSummaryRow = {
359
- // title: inventory.inventoryName,
360
- // grossValue: inventory.totalValue,
361
- // ineligible: inventory.advanceRate === 0 ? inventory.totalValue : 0,
362
- // netValue: inventory.advanceRate === 0 ? 0 : inventory.totalValue,
363
- // advanceRate: inventory.advanceRate,
364
- // availability: inventory.availability,
365
- // };
366
- // return [...acc, newRow];
367
- // }, <BorrowerSummaryRow[]>[]);
368
- // inventoryRows.push({
369
- // title: 'Inventory Reserves',
370
- // grossValue: null,
371
- // ineligible: null,
372
- // netValue: null,
373
- // advanceRate: null,
374
- // availability: availability.reserve[EReserveTypes.INVENTORY].availability ?? 0,
375
- // });
376
- // const inventorySumKeys = ['grossValue', 'ineligible', 'netValue', 'availability'];
377
- // const emptyTotalRow: BorrowerSummaryRow = {
378
- // title: 'Total Collateral',
379
- // grossValue: 0,
380
- // ineligible: 0,
381
- // netValue: 0,
382
- // advanceRate: 0,
383
- // availability: 0,
384
- // };
385
- //
386
- // const inventoryTotalRow = inventoryRows.reduce((acc, row) => {
387
- // inventorySumKeys.forEach((key) => {
388
- // acc[key] = new Decimal(acc[key]).add(row[key] ?? 0).toNumber();
389
- // });
390
- // acc.advanceRate = acc.netValue ? new Decimal(acc.availability).div(acc.netValue).toDP(4).toNumber() : 0;
391
- // return acc;
392
- // }, { ...emptyTotalRow, title: 'Total Inventory Collateral' });
393
- //
394
- // const inventoryDataAsArrays = inventoryRows.map((row) => reorderObject(row, headerKeys));
395
- // const inventoryDataWithStyles = Object.values([...inventoryDataAsArrays, inventoryTotalRow]).map((dataRow) => {
396
- // return Object.entries(dataRow).map(([key, v]) => ({
397
- // v,
398
- // t: borrowerSummaryRowFields[key].t,
399
- // z: borrowerSummaryRowFields[key].z,
400
- // }));
401
- // });
402
- //
403
- // // RECEIVABLE
404
- // const receivableRows: BorrowerSummaryRow[] = [];
405
- // const totalComponents = new Decimal(availability.receivable.uninsuredComponent).add(availability.receivable.insuredComponent);
406
- // const ARUninsuredRow: BorrowerSummaryRow = {
407
- // title: 'AR Uninsured',
408
- // grossValue: availability.receivable.uninsuredComponent,
409
- // ineligible: 0,
410
- // netValue: 0,
411
- // advanceRate: totalComponents ? new Decimal(availability.receivable.invoiceAmount).div(totalComponents).mul(availability.receivable.uninsuredComponent).toNumber() : 0,
412
- // availability: availability.receivable.uninsuredAvailability,
413
- // };
414
- // const ARInsuredRow: BorrowerSummaryRow = {
415
- // title: 'AR Insured',
416
- // grossValue: availability.receivable.insuredComponent,
417
- // ineligible: 0,
418
- // netValue: 0,
419
- // advanceRate: totalComponents ? new Decimal(availability.receivable.invoiceAmount).div(totalComponents).mul(availability.receivable.insuredComponent).toNumber() : 0,
420
- // availability: availability.receivable.uninsuredAvailability,
421
- // };
422
- // const ARReservesRow: BorrowerSummaryRow = {
423
- // title: 'AR Reserves',
424
- // grossValue: null,
425
- // ineligible: null,
426
- // netValue: null,
427
- // advanceRate: null,
428
- // availability: 0,
429
- // };
430
- // receivableRows.push(ARUninsuredRow, ARInsuredRow, ARReservesRow);
431
- // const receivableTotalRow = receivableRows.reduce((acc, row) => {
432
- // acc.availability = row.availability ? new Decimal(acc.availability).add(row.availability).toNumber() : 0;
433
- // return acc;
434
- // }, { ...emptyTotalRow, title: 'Total Receivable Collateral' });
435
- // const receivableDataAsArrays = receivableRows.map((row) => reorderObject(row, headerKeys));
436
- // const receivableDataWithStyles = Object.values([...receivableDataAsArrays, receivableTotalRow]).map((dataRow) => {
437
- // return Object.entries(dataRow).map(([key, v]) => ({
438
- // v,
439
- // t: borrowerSummaryRowFields[key].t,
440
- // z: borrowerSummaryRowFields[key].z,
441
- // }));
442
- // });
443
- //
444
- // // RESERVES
445
- // const reserveRows: BorrowerSummaryRow[] = [];
446
- // const otherReservesAvailability = Object
447
- // .entries(availability.reserve)
448
- // .reduce((acc, [key, el]) => {
449
- // if (key === EReserveTypes.EQUIPMENT || key === EReserveTypes.INVENTORY || key === EReserveTypes.RECEIVABLES) {
450
- // return acc;
451
- // }
452
- // return new Decimal(acc).sub(el.amount).toNumber();
453
- // }, 0);
454
- // const otherReservesRow: BorrowerSummaryRow = {
455
- // title: 'Other non-equipment reserves',
456
- // grossValue: null,
457
- // ineligible: null,
458
- // netValue: null,
459
- // advanceRate: null,
460
- // availability: otherReservesAvailability,
461
- // };
462
- // const collateralAdjustmentsAvailability = Object.values(availability.collateralAdjustment.summary).reduce((acc, summaryRow) => new Decimal(acc).add(summaryRow.amount).abs().toNumber(), 0);
463
- // const collateralAdjustmentsRow: BorrowerSummaryRow = {
464
- // title: 'Collateral Adjustments',
465
- // grossValue: null,
466
- // ineligible: null,
467
- // netValue: null,
468
- // advanceRate: null,
469
- // availability: collateralAdjustmentsAvailability,
470
- // };
471
- // reserveRows.push(otherReservesRow, collateralAdjustmentsRow);
472
- // const reserveTotalRow = reserveRows.reduce((acc, row) => {
473
- // inventorySumKeys.forEach((key) => {
474
- // acc[key] = new Decimal(acc[key]).add(row[key] ?? 0).toNumber();
475
- // });
476
- // acc.advanceRate = row.netValue ? new Decimal(acc.availability).div(row.netValue).toNumber() : 0;
477
- // return acc;
478
- // }, { ...emptyTotalRow, title: 'Other Reserves & Adjustments' });
479
- // const reserveDataAsArrays = reserveRows.map((row) => reorderObject(row, headerKeys));
480
- // const reserveDataWithStyles = Object.values([...reserveDataAsArrays, reserveTotalRow]).map((dataRow) => {
481
- // return Object.entries(dataRow).map(([key, v]) => ({
482
- // v,
483
- // t: borrowerSummaryRowFields[key].t,
484
- // z: borrowerSummaryRowFields[key].z,
485
- // }));
486
- // });
487
- //
488
- // const loanBalanceTotalRows: BorrowerSummaryRow[] = [];
489
- // const totalRevolvingCollateralRow: BorrowerSummaryRow = {
490
- // title: 'TOTAL REVOLVING COLLATERAL',
491
- // grossValue: '',
492
- // ineligible: '',
493
- // netValue: '',
494
- // advanceRate: '',
495
- // availability: new Decimal(inventoryTotalRow.availability).add(receivableTotalRow.availability).toNumber(),
496
- // };
497
- // const loanBalanceRow: BorrowerSummaryRow = {
498
- // title: 'LOAN BALANCE',
499
- // grossValue: '',
500
- // ineligible: '',
501
- // netValue: '',
502
- // advanceRate: '',
503
- // availability: availability.loanBalances.REVOLVER,
504
- // };
505
- // const netRevolverAvailabilityRow: BorrowerSummaryRow = {
506
- // title: 'NET REVOLVER AVAILABILITY',
507
- // grossValue: '',
508
- // ineligible: '',
509
- // netValue: '',
510
- // advanceRate: '',
511
- // availability: new Decimal(totalRevolvingCollateralRow.availability).sub(loanBalanceRow.availability).toNumber(),
512
- // };
513
- // const accruedStatementRow: BorrowerSummaryRow = {
514
- // title: 'ACCRUED STATEMENT',
515
- // grossValue: '',
516
- // ineligible: '',
517
- // netValue: '',
518
- // advanceRate: '',
519
- // availability: availability.accruedStatement,
520
- // };
521
- // const availableToBorrowRow: BorrowerSummaryRow = {
522
- // title: 'AVAILABLE TO BORROW',
523
- // grossValue: '',
524
- // ineligible: '',
525
- // netValue: '',
526
- // advanceRate: '',
527
- // availability: new Decimal(netRevolverAvailabilityRow.availability).sub(accruedStatementRow.availability).toNumber(),
528
- // };
529
- // const monthEndLoanBalanceRow: BorrowerSummaryRow = {
530
- // title: 'MONTH END LOAN BALANCE',
531
- // grossValue: '',
532
- // ineligible: '',
533
- // netValue: '',
534
- // advanceRate: '',
535
- // availability: 0,
536
- // };
537
- // const monthEndAccruedStatementRow: BorrowerSummaryRow = {
538
- // title: 'MONTH END ACCRUED STATEMENT',
539
- // grossValue: '',
540
- // ineligible: '',
541
- // netValue: '',
542
- // advanceRate: '',
543
- // availability: 0,
544
- // };
545
- // loanBalanceTotalRows.push(
546
- // totalRevolvingCollateralRow,
547
- // loanBalanceRow,
548
- // netRevolverAvailabilityRow,
549
- // accruedStatementRow,
550
- // availableToBorrowRow,
551
- // monthEndLoanBalanceRow,
552
- // monthEndAccruedStatementRow,
553
- // );
554
- // const loanBalanceTotalDataAsArrays = loanBalanceTotalRows.map((row) => reorderObject(row, headerKeys));
555
- // const loanBalanceTotalDataAsArraysWithStyles = Object.values([...loanBalanceTotalDataAsArrays]).map((dataRow) => {
556
- // return Object.values(dataRow).map((v) => ({
557
- // v,
558
- // t: typeof v === 'number' ? 'n' : 's',
559
- // z: numberFormat.thousands,
560
- // }));
561
- // });
562
- //
563
- // // LOAN ECONOMICS
564
- // const getLoanEconomics = async (): Promise<IExcelJsCell[][]> => {
565
- // const loanEconomicsData: IExcelJsCell[][] = [];
566
- // const chargesData: IExcelJsCell[][] = [];
567
- // const products = [...productsMap.values()];
568
- // const yieldProducts = products.filter((product) => product.borrowerId.toString() === borrowerId);
569
- // for (const product of yieldProducts) {
570
- //
571
- // const productData = productsDataMap.get(product._id.toString());
572
- // loanEconomicsData.push([{ v: product.name }]);
573
- // if (productData) {
574
- // loanEconomicsData.push([{ v: 'Average Balance Since Inception' }, {
575
- // v: productData.averageBalanceSinceInception,
576
- // t: 'n',
577
- // z: numberFormat.thousands,
578
- // }]);
579
- // loanEconomicsData.push([{ v: 'Total Income Received' }, {
580
- // v: productData.totalIncomeReceived,
581
- // t: 'n',
582
- // z: numberFormat.thousands,
583
- // }]);
584
- // loanEconomicsData.push([{ v: 'Loan Life IRR' }, {
585
- // v: productData.loanLifeIRR,
586
- // t: 'n',
587
- // z: numberFormat.percent,
588
- // }]);
589
- // loanEconomicsData.push([{ v: 'MOIC' }, {
590
- // v: productData.MOIC,
591
- // t: 'n',
592
- // z: numberFormat.xNumber,
593
- // }]);
594
- // }
595
- //
596
- // const dataDeep = 5;
597
- // const periodEnd = { month: dayjs(start).month() + 1, year: dayjs(start).year() };
598
- // const periodStart = getShiftedMonth(periodEnd, -dataDeep);
599
- // const monthHeader: IExcelJsCell[] = [];
600
- // const months = [];
601
- // for (let i = 0; i <= dataDeep; i++) {
602
- // const currentMonth = getShiftedMonth(periodEnd, -i);
603
- // monthHeader.push({
604
- // v: `${currentMonth.month}/${currentMonth.year}`,
605
- // t: 's',
606
- // s: { alignment: { horizontal: 'right' } },
607
- // });
608
- // months.push(dayjs(`${currentMonth.year}-${currentMonth.month}-01`).format('YYYY-MM'));
609
- // }
610
- // loanEconomicsData.push(emptyRow);
611
- // loanEconomicsData.push([{ v: '' }, ...monthHeader]);
612
- //
613
- // const transactions = await getLoanTransactions(
614
- // {
615
- // borrowerId,
616
- // productId: product._id.toString(),
617
- // periodStart: dayjs(`${periodStart.year}-${periodStart.month}-01`).utcOffset(0).toDate(),
618
- // periodEnd: end,
619
- // },
620
- // null,
621
- // false,
622
- // );
623
- //
624
- // const grouped = _.groupBy(transactions, (tx) => dayjs(tx.date).format('YYYY-MM'));
625
- // const yieldData = await this.yieldService.getCalculatedYieldTotalsForPeriod(product._id.toString(), periodStart, periodEnd);
626
- //
627
- // const totalDisbursementRow: IExcelJsCell[] = [{ v: 'Total Disbursements in Month' }];
628
- // const countDisbursementRow: IExcelJsCell[] = [{ v: 'Number of Disbursements' }];
629
- // const loanTurnRow: IExcelJsCell[] = [{ v: 'Loan Turn (days)' }];
630
- // const totalCollectionRow: IExcelJsCell[] = [{ v: 'Total Collections in Month' }];
631
- // const countCollectionRow: IExcelJsCell[] = [{ v: 'Number of Collections' }];
632
- //
633
- // const charges = [...chargesMap.values()]
634
- // .filter((charge) => charge.productId.toString() === product._id.toString() && charge.includeInYield)
635
- // .sort((a, b) => a.order - b.order);
636
- //
637
- // for (const month of months) {
638
- // const txs = grouped[month] ?? [];
639
- // const disbursements = txs.filter((t) => t.transactionType === ELoanTransactionTypes.DISBURSEMENT);
640
- // const collections = txs.filter((t) => t.transactionType === ELoanTransactionTypes.COLLECTION);
641
- // const totalDisbursements = _.sumBy(disbursements, 'amount');
642
- // const totalCollections = Math.abs(_.sumBy(collections, 'amount'));
643
- // totalDisbursementRow.push({ v: totalDisbursements, t: 'n', z: numberFormat.thousands });
644
- // countDisbursementRow.push({ v: disbursements.length, t: 'n', z: numberFormat.shortNumber });
645
- // totalCollectionRow.push({ v: totalCollections, t: 'n', z: numberFormat.thousands });
646
- // countCollectionRow.push({ v: collections.length, t: 'n', z: numberFormat.shortNumber });
647
- // }
648
- //
649
- // charges.forEach((charge) => {
650
- // const chargeYieldData = yieldData.filter((yieldDataDoc) => {
651
- // return yieldDataDoc.chargeId?.toString() === charge._id.toString();
652
- // });
653
- //
654
- // const chargeData = chargeYieldData.reduce((acc, chargeYieldDataDoc) => {
655
- // const newRow: IExcelJsCell = chargeYieldDataDoc.valuePercent < 0
656
- // ? negativeCell
657
- // : { v: chargeYieldDataDoc.valuePercent, t: 'n', z: numberFormat.percent };
658
- // return [...acc, newRow];
659
- // }, [{ v: charge.name, t: 's' }]);
660
- // chargesData.push(chargeData);
661
- // });
662
- //
663
- // chargesData.push(emptyRow);
664
- //
665
- // Object.entries(YIELD_TOTALS_MAP).forEach(([totalType, total]) => {
666
- // const totalYieldData = yieldData.filter((yieldDataDoc) => {
667
- // return yieldDataDoc.totalType === totalType;
668
- // }).sort((a, b) => {
669
- // if (a.year !== b.year) {
670
- // return b.year - a.year;
671
- // }
672
- // return b.month - a.month;
673
- // });
674
- // if (totalType === ETotalType.AVERAGE_BALANCE) {
675
- // const loanTurn = totalYieldData.reduce((acc, yieldDataDoc) => {
676
- //
677
- // const monthStart = dayjs(`${yieldDataDoc.year}-${yieldDataDoc.month}-01`).utcOffset(0);
678
- // const daysInMonth = monthStart.daysInMonth();
679
- // const txs = grouped[monthStart.format('YYYY-MM')] ?? [];
680
- // const collections = txs.filter((t) => t.transactionType === ELoanTransactionTypes.COLLECTION);
681
- // const totalCollections = _.sumBy(collections, 'amount');
682
- //
683
- // const newRow: IExcelJsCell = {
684
- // v: totalCollections === 0 ? 0 : new Decimal(yieldDataDoc.value).div(Math.abs(totalCollections)).mul(daysInMonth).round().toNumber(),
685
- // t: 'n',
686
- // z: numberFormat.thousands,
687
- // };
688
- // return [...acc, newRow];
689
- // }, []);
690
- // loanTurnRow.push(...loanTurn);
691
- // }
692
- //
693
- // const replacedValues = [ETotalType.APR_GROSS, ETotalType.APR_LIFE_GROSS];
694
- //
695
- // const chargeData = totalYieldData.reduce((acc, yieldDataDoc) => {
696
- // const isPercent = !!yieldDataDoc.valuePercent;
697
- // if (replacedValues.includes(ETotalType[yieldDataDoc.totalType]) && yieldDataDoc.valuePercent < 0) {
698
- // return [...acc, negativeCell];
699
- // }
700
- // const newRow: IExcelJsCell = isPercent
701
- // ? { v: yieldDataDoc.valuePercent, t: 'n', z: numberFormat.percent }
702
- // : { v: yieldDataDoc.value, t: 'n', z: numberFormat.thousands };
703
- // return [...acc, newRow];
704
- // }, [{ v: total.title, t: 's' }]);
705
- // chargesData.push(chargeData);
706
- // });
707
- //
708
- // loanEconomicsData.push(totalDisbursementRow);
709
- // loanEconomicsData.push(totalCollectionRow);
710
- // loanEconomicsData.push(loanTurnRow);
711
- // loanEconomicsData.push(countDisbursementRow);
712
- // loanEconomicsData.push(countCollectionRow);
713
- //
714
- // loanEconomicsData.push(emptyRow);
715
- //
716
- // loanEconomicsData.push(...chargesData);
717
- // }
718
- // return loanEconomicsData;
719
- // };
720
- // const loanEconomicsData = await getLoanEconomics();
721
- //
722
- // // FINANCIAL
723
- // const getFinancialDate = async (endDate: Date): Promise<IExcelJsCell[][]> => {
724
- // const month = dayjs(endDate).month();
725
- // const year = dayjs(endDate).year();
726
- // const monthDeep = 11;
727
- // const { data: dataPL, sheets: sheetsPL } = await getFinancialSpreadingData({
728
- // borrowerId,
729
- // financialSpreadingType: EFinancialSpreadingType.PROFIT_LOSS,
730
- // selectedMonth: { month, year },
731
- // }, monthDeep);
732
- //
733
- // const { data: dataBS, sheets: sheetsBS } = await getFinancialSpreadingData({
734
- // borrowerId,
735
- // financialSpreadingType: EFinancialSpreadingType.BALANCE_SHEET,
736
- // selectedMonth: { month, year },
737
- // }, monthDeep);
738
- //
739
- // const data = [...dataPL, ...dataBS];
740
- // const sheets = [...sheetsPL, ...sheetsBS];
741
- //
742
- // const generateMonthCells = (): IExcelJsCell[] => {
743
- // const result: IExcelJsCell[] = [];
744
- //
745
- // for (let i = 0; i <= monthDeep; i++) {
746
- // const date = dayjs(`${year}-${String(month).padStart(2, '0')}-01`).subtract(i, 'month');
747
- // result.push({
748
- // v: date.endOf('month').format(dateFormat),
749
- // t: 's',
750
- // s: rightAlign,
751
- // });
752
- // }
753
- //
754
- // return [{ v: 'Last 12 months', t: 's' }, ...result];
755
- // };
756
- //
757
- // const percentageIndexes = [EFinanceSpreadingPLTotal.GROSS_MARGIN, EFinanceSpreadingPLTotal.OPERATING_MARGIN] as const;
758
- //
759
- // const convertAmount = (index: string, amount: number): number => {
760
- // if ((percentageIndexes as readonly string[]).includes(index)) {
761
- // return new Decimal(amount).div(100).toDP(4).toNumber();
762
- // }
763
- // return amount;
764
- // };
765
- //
766
- // const createFinancialIndexes = <T extends Record<string, string>>(
767
- // enumObj: T,
768
- // keys: Array<keyof T>,
769
- // overrides: Partial<Record<keyof T, Partial<FinancialIndexOptions>>> = {},
770
- // ): Record<string, FinancialIndexOptions> =>
771
- // keys.reduce((acc, key) => {
772
- // const enumValue = enumObj[key] as keyof typeof financialSpreadingTotalDictionary;
773
- // acc[enumValue] = {
774
- // format: overrides[key]?.format ?? numberFormat.thousands,
775
- // title: overrides[key]?.title ?? financialSpreadingTotalDictionary[enumValue],
776
- // addEmptyRow: overrides[key]?.addEmptyRow ?? false,
777
- // style: overrides[key]?.style ?? {},
778
- // };
779
- // return acc;
780
- // }, {} as Record<string, FinancialIndexOptions>);
781
- //
782
- // const financialPLIndexes = createFinancialIndexes(EFinanceSpreadingPLTotal, [
783
- // EFinanceSpreadingPLTotal.SALES,
784
- // EFinanceSpreadingPLTotal.COST_OF_SALES,
785
- // EFinanceSpreadingPLTotal.GROSS_PROFIT,
786
- // EFinanceSpreadingPLTotal.GROSS_MARGIN,
787
- // EFinanceSpreadingPLTotal.OPERATING_EXPENSES,
788
- // EFinanceSpreadingPLTotal.OPERATING_MARGIN,
789
- // EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS,
790
- // EFinanceSpreadingPLTotal.NON_OPERATING_EXPENSES,
791
- // EFinanceSpreadingPLTotal.NON_OPERATING_INCOME,
792
- // EFinanceSpreadingPLTotal.FINANCING_COSTS,
793
- // EFinanceSpreadingPLTotal.DEPRECIATION_AMORTIZATION,
794
- // EFinanceSpreadingPLTotal.TAX,
795
- // EFinanceSpreadingPLTotal.NET_INCOME,
796
- // ], {
797
- // [EFinanceSpreadingPLTotal.GROSS_MARGIN]: {
798
- // format: numberFormat.percent,
799
- // addEmptyRow: true,
800
- // style: { font: { italic: true, name: 'Calibri' } },
801
- // },
802
- // [EFinanceSpreadingPLTotal.OPERATING_MARGIN]: {
803
- // format: numberFormat.percent,
804
- // addEmptyRow: true,
805
- // style: { font: { italic: true, name: 'Calibri' } },
806
- // },
807
- // [EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS]: {
808
- // title: 'EBITDA',
809
- // format: numberFormat.thousands,
810
- // style: { font: { bold: true, name: 'Calibri' } },
811
- // },
812
- // [EFinanceSpreadingPLTotal.NON_OPERATING_EXPENSES]: {
813
- // style: { font: { bold: true, name: 'Calibri' } },
814
- // },
815
- // [EFinanceSpreadingPLTotal.NON_OPERATING_INCOME]: {
816
- // style: { font: { bold: true, name: 'Calibri' } },
817
- // },
818
- // [EFinanceSpreadingPLTotal.NET_INCOME]: {
819
- // style: { font: { bold: true, name: 'Calibri' } },
820
- // },
821
- // });
822
- //
823
- // const financialBSIndexes = createFinancialIndexes(EFinanceSpreadingBSTotal, [
824
- // EFinanceSpreadingBSTotal.CASH_EQUIVALENTS,
825
- // EFinanceSpreadingBSTotal.TRADE_RECEIVABLES,
826
- // EFinanceSpreadingBSTotal.INVENTORY,
827
- // EFinanceSpreadingBSTotal.OTHER_CURRENT_ASSETS,
828
- // EFinanceSpreadingBSTotal.TOTAL_CURRENT_ASSET,
829
- // EFinanceSpreadingBSTotal.TOTAL_ASSETS,
830
- // EFinanceSpreadingBSTotal.ACCOUNTS_PAYABLE,
831
- // EFinanceSpreadingBSTotal.OTHER_CURRENT_LIABILITIES,
832
- // EFinanceSpreadingBSTotal.TOTAL_CURRENT_LIABILITIES,
833
- // EFinanceSpreadingBSTotal.COMPANY_DEBT,
834
- // EFinanceSpreadingBSTotal.OTHER_DEBT,
835
- // EFinanceSpreadingBSTotal.OTHER_LONG_TERM_LIABILITIES,
836
- // EFinanceSpreadingBSTotal.TOTAL_LIABILITIES,
837
- // ], {
838
- // [EFinanceSpreadingBSTotal.TOTAL_CURRENT_ASSET]: { style: { font: { bold: true, name: 'Calibri' } } },
839
- // [EFinanceSpreadingBSTotal.TOTAL_ASSETS]: {
840
- // addEmptyRow: true,
841
- // style: { font: { bold: true, name: 'Calibri' } },
842
- // },
843
- // [EFinanceSpreadingBSTotal.TOTAL_CURRENT_LIABILITIES]: {
844
- // addEmptyRow: true,
845
- // style: { font: { bold: true, name: 'Calibri' } },
846
- // },
847
- // [EFinanceSpreadingBSTotal.COMPANY_DEBT]: { title: 'Senior Debt' },
848
- // [EFinanceSpreadingBSTotal.TOTAL_LIABILITIES]: { style: { font: { bold: true, name: 'Calibri' } } },
849
- // });
850
- //
851
- // const generateFinancialRows = <T extends Record<string, string>>(financialIndexes: Record<string, FinancialIndexOptions>, enumObj: T): IExcelJsCell[][] => {
852
- // return Object.entries(financialIndexes).reduce((acc, [financialIndex, options]) => {
853
- // const sheet = sheets.find((s) => financialIndex === enumObj[s.rowType] && s.isTotal);
854
- // if (!sheet) {
855
- // return acc;
856
- // }
857
- //
858
- // const dataEntry = data.find((d) => d.sheetId === sheet._id);
859
- // if (!dataEntry) {
860
- // return acc;
861
- // }
862
- //
863
- // const baseValue = convertAmount(enumObj[financialIndex as keyof T], dataEntry.amount);
864
- // const monthlyValues = Array.from({ length: monthDeep }, (_, i) => ({
865
- // v: convertAmount(enumObj[financialIndex as keyof T], dataEntry[`minus_${i + 1}`]),
866
- // t: 'n',
867
- // z: options.format,
868
- // s: options.style,
869
- // }));
870
- //
871
- // const row: IExcelJsCell[] = [
872
- // { v: options.title, t: 's', s: options.style },
873
- // { v: baseValue, t: 'n', z: options.format },
874
- // ...monthlyValues,
875
- // ];
876
- //
877
- // return options.addEmptyRow ? [...acc, row, emptyRow] : [...acc, row];
878
- // }, [] as IExcelJsCell[][]);
879
- // };
880
- //
881
- // type FinancialRatioFormula = {
882
- // label: string;
883
- // dependencies: string[];
884
- // format?: string;
885
- // formula: (data: Record<string, FinancialSpreadingDTO>, monthIndex: number) => number;
886
- // };
887
- //
888
- // const get = (base: Record<string, FinancialSpreadingDTO>, i: number, key: string) => new Decimal(i === 0 ? base[key]?.amount : base[key]?.[`minus_${i}`] ?? 0);
889
- //
890
- // const financialRatioFormulas: FinancialRatioFormula[] = [
891
- // {
892
- // label: 'Fixed Charge Coverage Ratio',
893
- // dependencies: [EFinanceSpreadingPLTotal.FINANCING_COSTS, EFinanceSpreadingPLTotal.DEPRECIATION_AMORTIZATION, EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS],
894
- // format: numberFormat.xNumber1DP,
895
- // formula: (base, i) => {
896
- // const EBITDA = get(base, i, EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS);
897
- // const DepreciationAmortization = get(base, i, EFinanceSpreadingPLTotal.DEPRECIATION_AMORTIZATION);
898
- // const FinancingCost = get(base, i, EFinanceSpreadingPLTotal.FINANCING_COSTS);
899
- // if (FinancingCost.lessThanOrEqualTo(0)) {
900
- // return null;
901
- // }
902
- // const result = EBITDA.sub(DepreciationAmortization).add(FinancingCost).div(FinancingCost).toDP(4);
903
- // if (result.lessThanOrEqualTo(0)) {
904
- // return null;
905
- // }
906
- // return result.toNumber();
907
- // },
908
- // },
909
- // {
910
- // label: 'Interest Coverage Ratio',
911
- // dependencies: [EFinanceSpreadingPLTotal.FINANCING_COSTS, EFinanceSpreadingPLTotal.DEPRECIATION_AMORTIZATION, EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS],
912
- // format: numberFormat.xNumber1DP,
913
- // formula: (base, i) => {
914
- // const EBITDA = get(base, i, EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS);
915
- // const DepreciationAmortization = get(base, i, EFinanceSpreadingPLTotal.DEPRECIATION_AMORTIZATION);
916
- // const FinancingCost = get(base, i, EFinanceSpreadingPLTotal.FINANCING_COSTS);
917
- // if (FinancingCost.lessThanOrEqualTo(0)) {
918
- // return null;
919
- // }
920
- // const result = EBITDA.add(DepreciationAmortization).div(FinancingCost).toDP(4);
921
- // if (result.lessThanOrEqualTo(0)) {
922
- // return null;
923
- // }
924
- // return result.toNumber();
925
- // },
926
- // },
927
- // {
928
- // label: 'Total Debt / EBITDA',
929
- // dependencies: [EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS, EFinanceSpreadingBSTotal.COMPANY_DEBT, EFinanceSpreadingBSTotal.OTHER_DEBT],
930
- // format: numberFormat.xNumber1DP,
931
- // formula: (base, i) => {
932
- // const EBITDA = get(base, i, EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS);
933
- // const CompanyDebt = get(base, i, EFinanceSpreadingBSTotal.COMPANY_DEBT);
934
- // const OtherDebt = get(base, i, EFinanceSpreadingBSTotal.OTHER_DEBT);
935
- // if (EBITDA.lessThanOrEqualTo(0)) {
936
- // return null;
937
- // }
938
- // const result = CompanyDebt.add(OtherDebt).abs().div(EBITDA).toDP(4);
939
- // if (result.lessThanOrEqualTo(0)) {
940
- // return null;
941
- // }
942
- // return result.toNumber();
943
- // },
944
- // },
945
- // {
946
- // label: 'Senior Debt / EBITDA',
947
- // dependencies: [EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS, EFinanceSpreadingBSTotal.COMPANY_DEBT],
948
- // format: numberFormat.xNumber1DP,
949
- // formula: (base, i) => {
950
- // const EBITDA = get(base, i, EFinanceSpreadingPLTotal.INCOME_FROM_OPERATIONS);
951
- // const CompanyDebt = get(base, i, EFinanceSpreadingBSTotal.COMPANY_DEBT);
952
- // if (EBITDA.lessThanOrEqualTo(0)) {
953
- // return null;
954
- // }
955
- // const result = CompanyDebt.div(EBITDA).toDP(4);
956
- // if (result.lessThanOrEqualTo(0)) {
957
- // return null;
958
- // }
959
- // return result.toNumber();
960
- // },
961
- // },
962
- // {
963
- // label: 'AR Turnover Days',
964
- // dependencies: [EFinanceSpreadingPLTotal.SALES, EFinanceSpreadingBSTotal.TRADE_RECEIVABLES],
965
- // format: numberFormat.fullNumber,
966
- // formula: (base, i) => {
967
- // const Sales = get(base, i, EFinanceSpreadingPLTotal.SALES);
968
- // const TradeReceivables = get(base, i, EFinanceSpreadingBSTotal.TRADE_RECEIVABLES);
969
- // if (Sales.lessThanOrEqualTo(0)) {
970
- // return null;
971
- // }
972
- // const result = TradeReceivables.abs().div(Sales).mul(30).toDP(0);
973
- // if (result.lessThanOrEqualTo(0)) {
974
- // return null;
975
- // }
976
- // return result.toNumber();
977
- // },
978
- // },
979
- // {
980
- // label: 'AP Turnover Days',
981
- // dependencies: [EFinanceSpreadingPLTotal.COST_OF_SALES, EFinanceSpreadingBSTotal.ACCOUNTS_PAYABLE],
982
- // format: numberFormat.fullNumber,
983
- // formula: (base, i) => {
984
- // const CostOfSales = get(base, i, EFinanceSpreadingPLTotal.COST_OF_SALES);
985
- // const AccountsPayable = get(base, i, EFinanceSpreadingBSTotal.ACCOUNTS_PAYABLE);
986
- // if (CostOfSales.lessThanOrEqualTo(0)) {
987
- // return null;
988
- // }
989
- // const result = AccountsPayable.abs().div(CostOfSales).mul(30).toDP(0);
990
- // if (result.lessThanOrEqualTo(0)) {
991
- // return null;
992
- // }
993
- // return result.toNumber();
994
- // },
995
- // },
996
- // {
997
- // label: 'Inventory Turnover Days',
998
- // dependencies: [EFinanceSpreadingPLTotal.COST_OF_SALES, EFinanceSpreadingBSTotal.INVENTORY],
999
- // format: numberFormat.fullNumber,
1000
- // formula: (base, i) => {
1001
- // const CostOfSales = get(base, i, EFinanceSpreadingPLTotal.COST_OF_SALES);
1002
- // const Inventory = get(base, i, EFinanceSpreadingBSTotal.INVENTORY);
1003
- // if (CostOfSales.lessThanOrEqualTo(0)) {
1004
- // return null;
1005
- // }
1006
- // const result = Inventory.abs().div(CostOfSales).mul(30).toDP(0);
1007
- // if (result.lessThanOrEqualTo(0)) {
1008
- // return null;
1009
- // }
1010
- // return result.toNumber();
1011
- // },
1012
- // },
1013
- // ];
1014
- //
1015
- // const generateFinancialRatios = (): IExcelJsCell[][] => {
1016
- // const ratiosData: IExcelJsCell[][] = [[{ v: 'FINANCIAL RATIOS:' }]];
1017
- //
1018
- // const financialRatiosBaseKeys = _.uniq(financialRatioFormulas.flatMap((f) => f.dependencies));
1019
- //
1020
- // const financialRatiosBase = financialRatiosBaseKeys.reduce((acc, key) => {
1021
- // const sheet = sheets.find((s) => s.rowType === key && s.isTotal);
1022
- // const dataEntry = data.find((d) => d.sheetId.toString() === sheet._id.toString());
1023
- // return { ...acc, [key]: dataEntry };
1024
- // }, {} as Record<string, FinancialSpreadingDTO>);
1025
- //
1026
- // const ratioRows = financialRatioFormulas.map(({ label, formula, format }) => {
1027
- // const row: IExcelJsCell[] = [{ v: label, t: 's' }];
1028
- // for (let i = 0; i <= monthDeep; i++) {
1029
- // const result = formula(financialRatiosBase, i);
1030
- // if (result) {
1031
- // row.push({ v: result, t: 'n', z: format ?? numberFormat.thousands });
1032
- // } else {
1033
- // row.push(negativeCell);
1034
- // }
1035
- // }
1036
- // return row;
1037
- // });
1038
- //
1039
- // return [...ratiosData, ...ratioRows];
1040
- // };
1041
- //
1042
- // const financialPLDataFull = generateFinancialRows(financialPLIndexes, EFinanceSpreadingPLTotal);
1043
- // const financialBSDataFull = generateFinancialRows(financialBSIndexes, EFinanceSpreadingBSTotal);
1044
- // const financialRatios = generateFinancialRatios();
1045
- //
1046
- // return [
1047
- // generateMonthCells(),
1048
- // emptyRow,
1049
- // [{ v: 'PROFIT & LOSS HIGHLIGHTS', t: 's' }],
1050
- // ...financialPLDataFull,
1051
- // emptyRow,
1052
- // [{ v: 'BALANCE SHEET HIGHLIGHTS', t: 's' }],
1053
- // ...financialBSDataFull,
1054
- // emptyRow,
1055
- // ...financialRatios,
1056
- // ];
1057
- // };
1058
- // const financialDate = await getFinancialDate(end);
1059
- //
1060
- // // HEADROOM
1061
- // const headSourceData = borrowerSummary.chartData.LAST_6_MONTH.data.reverse();
1062
- // const headRoomHeader: IExcelJsCell[] = headSourceData.reduce((acc, row) => {
1063
- // return [...acc, { v: row.bbc, t: 's', s: rightAlign }];
1064
- // }, <IExcelJsCell[]>[{ v: 'Last 6 months' }]);
1065
- // const totalRevolver: number[] = headSourceData.map((dataRow) => dataRow.revolverCollateralTotal);
1066
- // const totalRevolverBalance: number[] = headSourceData.map((dataRow) => dataRow.revolverLoanBalance);
1067
- // const headRoom: number[] = headSourceData.map((dataRow) => {
1068
- // if (dataRow.revolverCollateralTotal === 0) {
1069
- // return 0;
1070
- // }
1071
- // return new Decimal(dataRow.revolverCollateralTotal).sub(dataRow.revolverLoanBalance).div(dataRow.revolverCollateralTotal).toDP(2).toNumber();
1072
- // });
1073
- // const totalRevolverWithStyle: IExcelJsCell[] = totalRevolver.map((v) => ({ v, t: 'n', z: numberFormat.thousands }));
1074
- // const totalRevolverBalanceWithStyle: IExcelJsCell[] = totalRevolverBalance.map((v) => ({
1075
- // v,
1076
- // t: 'n',
1077
- // z: numberFormat.thousands,
1078
- // }));
1079
- // const headRoomWithStyle: IExcelJsCell[] = headRoom.map((v) => ({ v, t: 'n', z: '0.00%' }));
1080
- // const headRoomRows = [
1081
- // [{ v: 'Total Revolver Availability', t: 's' }, ...totalRevolverWithStyle],
1082
- // [{ v: 'Revolver Loan Balance', t: 's' }, ...totalRevolverBalanceWithStyle],
1083
- // [{ v: 'Headroom %', t: 's' }, ...headRoomWithStyle],
1084
- // ];
1085
- //
1086
- // // TOP 5
1087
- // const getTop5Data = async () => {
1088
- // const top5SKUs_new = await _borrowerSummaryService.getTop5SKUsData(borrowerId);
1089
- // const top5CustomerConcentration_new = await _borrowerSummaryService.getTop5CustomersConcentrationData(borrowerId);
1090
- // const gap = 1;
1091
- //
1092
- // const titleStyleLeft = {
1093
- // font: { bold: true, name: 'Calibri' },
1094
- // alignment: { horizontal: 'left' },
1095
- // };
1096
- //
1097
- // const titleStyleRight = {
1098
- // font: { bold: true, name: 'Calibri' },
1099
- // alignment: { horizontal: 'right' },
1100
- // };
1101
- //
1102
- // const getTop5SummaryTable = (
1103
- // title: string,
1104
- // headers: string[],
1105
- // rows: { _id: string, totalAmount: number }[],
1106
- // startColIndex: number,
1107
- // ): IExcelJsCell[][] => {
1108
- // const result: IExcelJsCell[][] = [];
1109
- //
1110
- // const total = rows.reduce((acc, row) => new Decimal(acc).add(row.totalAmount).toNumber(), 0);
1111
- //
1112
- // // Title
1113
- // result.push(
1114
- // Array(startColIndex).fill({ v: '' }).concat([
1115
- // { v: title, t: 's', s: titleStyleLeft },
1116
- // { v: '' },
1117
- // { v: '' },
1118
- // ]),
1119
- // );
1120
- //
1121
- // // Headers
1122
- // result.push(
1123
- // Array(startColIndex).fill({ v: '' }).concat(
1124
- // headers.map((header, index) => ({ v: header, t: 's', s: index > 0 ? titleStyleRight : titleStyleLeft })),
1125
- // ),
1126
- // );
1127
- //
1128
- // // Data rows
1129
- // for (const row of rows.slice(0, 5)) {
1130
- // result.push(
1131
- // Array(startColIndex).fill({ v: '' }).concat([
1132
- // { v: row._id, t: 's' },
1133
- // {
1134
- // v: row.totalAmount,
1135
- // t: 'n',
1136
- // z: numberFormat.thousands,
1137
- // },
1138
- // {
1139
- // v: new Decimal(row.totalAmount).div(total).toDP(4).toNumber(),
1140
- // t: 'n',
1141
- // z: numberFormat.percent,
1142
- // },
1143
- // ]),
1144
- // );
1145
- // }
1146
- // return result;
1147
- // };
1148
- //
1149
- // const leftTable = getTop5SummaryTable(
1150
- // 'Top 5 SKU',
1151
- // ['SKU', 'Value', '%'],
1152
- // top5SKUs_new,
1153
- // 1,
1154
- // );
1155
- //
1156
- // const rightTable = getTop5SummaryTable(
1157
- // 'Top 5 customers',
1158
- // ['Customer', 'Value', '%'],
1159
- // top5CustomerConcentration_new,
1160
- // gap,
1161
- // );
1162
- //
1163
- // const maxRows = Math.max(leftTable.length, rightTable.length);
1164
- // const combined: IExcelJsCell[][] = [];
1165
- //
1166
- // for (let i = 0; i < maxRows; i++) {
1167
- // combined.push([
1168
- // ...(leftTable[i] || []),
1169
- // ...(rightTable[i] || []),
1170
- // ]);
1171
- // }
1172
- // return combined;
1173
- // };
1174
- // const top5Data = await getTop5Data();
1175
- //
1176
- // // COMPLIANCE REPORT
1177
- // const complianceItemsHeader: ComplianceItemRow = {
1178
- // name: 'Item',
1179
- // dueDate: 'Due date',
1180
- // progress: 'Progress',
1181
- // submittedDate: 'Submitted date',
1182
- // status: 'Status',
1183
- // };
1184
- // const complianceItemsHeaderKeys = Object.keys(complianceItemsHeader);
1185
- // const complianceItemsHeaderAsArray = reorderObject(complianceItemsHeader, complianceItemsHeaderKeys);
1186
- // const complianceItemsHeaderAsArrayWithStyles = Object.values(complianceItemsHeaderAsArray).reduce((acc, v) => [...acc, {
1187
- // v,
1188
- // t: 's',
1189
- // }], []);
1190
- //
1191
- // const complianceBorrower = complianceBorrowersMap.get(borrowerId);
1192
- // const fullComplianceBorrower = await _complianceBorrowersService.getFullComplianceBorrowerById(complianceBorrower._id.toString());
1193
- // const dueItems = [];
1194
- // const acceptedItems = [];
1195
- // const previousMonthStart = dayjs(start).utcOffset(0).subtract(1, 'months').startOf('month');
1196
- //
1197
- // if (fullComplianceBorrower) {
1198
- // fullComplianceBorrower.items.forEach((item) => {
1199
- // item.instances.forEach((instance) => {
1200
- // const row: ComplianceItemRow = {
1201
- // name: `${item.item?.name} (${dayjs(instance.nextDate).format('MMM.D, YYYY')})`,
1202
- // dueDate: instance.nextDate ? dayjs(instance.nextDate).format('YYYY/MM/DD') : '-',
1203
- // progress: instance.progress?.text ?? '-',
1204
- // submittedDate: instance.submittedDate ? dayjs(instance.submittedDate).format('YYYY/MM/DD') : '-',
1205
- // status: instance.status,
1206
- // };
1207
- // if (instance.status === EComplianceItemStatus.ACCEPTED) {
1208
- // if (previousMonthStart.isBefore(dayjs(instance.submittedDate))) {
1209
- // acceptedItems.push(row);
1210
- // }
1211
- // } else {
1212
- // dueItems.push(row);
1213
- // }
1214
- // });
1215
- // });
1216
- // }
1217
- // const dueItemsDataAsArrays = dueItems.map((row) => reorderObject(row, complianceItemsHeaderKeys));
1218
- // const dueItemsDataAsArraysWithStyles = Object.values([...dueItemsDataAsArrays]).map((dataRow) => {
1219
- // return Object.values(dataRow).map((v) => ({ v, t: 's' }));
1220
- // });
1221
- // const acceptedItemsDataAsArrays = acceptedItems.map((row) => reorderObject(row, complianceItemsHeaderKeys));
1222
- // const acceptedItemsDataAsArraysWithStyles = Object.values([...acceptedItemsDataAsArrays]).map((dataRow) => {
1223
- // return Object.values(dataRow).map((v) => ({ v, t: 's' }));
1224
- // });
1225
- //
1226
- // return [
1227
- // borrowerTitle,
1228
- // borrowerSubTitle,
1229
- // [{ v: 'ALL VALUES $\'000' }],
1230
- // emptyRow,
1231
- //
1232
- // getSegment(`LOAN BALANCE & COLLATERAL - AS AT ${dayjs(lastBBC.bbcDate).utcOffset(0).format(dateFormat)} (LAST SIGNED BBC)`),
1233
- // headerWithStyles,
1234
- // ...inventoryDataWithStyles,
1235
- // emptyRow,
1236
- // ...receivableDataWithStyles,
1237
- // emptyRow,
1238
- // ...reserveDataWithStyles,
1239
- // emptyRow,
1240
- // ...loanBalanceTotalDataAsArraysWithStyles,
1241
- // emptyRow,
1242
- //
1243
- // getSegment('LOAN ECONOMICS'),
1244
- // ...loanEconomicsData,
1245
- // emptyRow,
1246
- //
1247
- // getSegment('FINANCIALS'),
1248
- // emptyRow,
1249
- // ...financialDate,
1250
- // emptyRow,
1251
- //
1252
- // getSegment('HEADROOM'),
1253
- // emptyRow,
1254
- // headRoomHeader,
1255
- // emptyRow,
1256
- // ...headRoomRows,
1257
- // emptyRow,
1258
- //
1259
- // getSegment('TOP 5'),
1260
- // emptyRow,
1261
- // ...top5Data,
1262
- // emptyRow,
1263
- //
1264
- // getSegment('COMPLIANCE REPORT'),
1265
- // complianceItemsHeaderAsArrayWithStyles,
1266
- // emptyRow,
1267
- // ...dueItemsDataAsArraysWithStyles,
1268
- // emptyRow,
1269
- // ...acceptedItemsDataAsArraysWithStyles,
1270
- // ];
1271
- // };
1272
- //
1273
- // private async getPaidAmount(productId: string) {
1274
- // const loanCharges = await getLoanChargeForProduct(productId);
1275
- // const yieldLoanCharges = loanCharges.filter((charge) => charge.includeInYield);
1276
- //
1277
- // const totals = await LoanStatementTransactionModel.aggregate<{ totalAmountPaid: number }>([
1278
- // {
1279
- // '$match': {
1280
- // 'chargeId': {
1281
- // '$in': yieldLoanCharges.map((charge) => charge._id),
1282
- // },
1283
- // },
1284
- // }, {
1285
- // '$group': {
1286
- // '_id': null,
1287
- // 'totalAmountPaid': {
1288
- // '$sum': '$amountPaid',
1289
- // },
1290
- // },
1291
- // }, {
1292
- // '$project': {
1293
- // '_id': 0,
1294
- // 'totalAmountPaid': {
1295
- // '$round': [
1296
- // '$totalAmountPaid', 2,
1297
- // ],
1298
- // },
1299
- // },
1300
- // },
1301
- // ]);
1302
- // if (totals.length > 0) {
1303
- // return totals[0].totalAmountPaid;
1304
- // }
1305
- // return 0;
1306
- // };
1307
- //
1308
- // async getNewSummaryData(borrowerIds: string[], start: Date, end: Date): Promise<{
1309
- // [p: string]: IExcelJsCell[][]
1310
- // }> {
1311
- // const allBorrowers = await BorrowerModel.find().lean();
1312
- // allBorrowers.forEach((borrower) => borrowersMap.set(borrower._id.toString(), borrower));
1313
- //
1314
- // const allProducts = await LoanProduct.find().lean();
1315
- // allProducts.forEach((product) => productsMap.set(product._id.toString(), product));
1316
- //
1317
- // const allCharges = await LoanCharge.find().lean();
1318
- // allCharges.forEach((charge) => chargesMap.set(charge._id.toString(), charge));
1319
- //
1320
- // const allComplianceBorrowers = await BorrowerCompliance.find().lean();
1321
- // allComplianceBorrowers.forEach((borrower) => complianceBorrowersMap.set(borrower.borrower.toString(), borrower));
1322
- //
1323
- // const mainData = await this.getMainData(borrowerIds, start, end);
1324
- // const borrowersSummary = await this.getAllBorrowersSummary(borrowerIds, start, end);
1325
- // return { ...mainData, ...borrowersSummary };
1326
- // };
1327
- // }