gemcap-be-common 1.5.70 → 1.5.72

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