gemcap-be-common 1.3.63 → 1.3.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/db/new-summary.js +176 -12
- package/db/new-summary.ts +205 -13
- package/models/Prospect.model.js +7 -6
- package/models/Prospect.model.ts +8 -6
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/reports/new-summary.d.ts +0 -0
- package/reports/new-summary.js +0 -1327
- package/reports/new-summary.ts +0 -1327
package/db/new-summary.js
CHANGED
|
@@ -80,7 +80,7 @@ const styles = {
|
|
|
80
80
|
},
|
|
81
81
|
},
|
|
82
82
|
};
|
|
83
|
-
const borrowerTitleLength =
|
|
83
|
+
const borrowerTitleLength = 16;
|
|
84
84
|
const dateFormat = 'MM-DD-YYYY';
|
|
85
85
|
const productsDataMap = new Map();
|
|
86
86
|
const borrowersMap = new Map();
|
|
@@ -538,7 +538,15 @@ class NewSummaryExcel {
|
|
|
538
538
|
}
|
|
539
539
|
});
|
|
540
540
|
if (monthEndData.length) {
|
|
541
|
-
loanBalanceTotalRows.push({
|
|
541
|
+
loanBalanceTotalRows.push({
|
|
542
|
+
...emptyTotalRow,
|
|
543
|
+
title: '',
|
|
544
|
+
netValue: null,
|
|
545
|
+
availability: null,
|
|
546
|
+
grossValue: null,
|
|
547
|
+
ineligible: null,
|
|
548
|
+
advanceRate: null,
|
|
549
|
+
});
|
|
542
550
|
}
|
|
543
551
|
}
|
|
544
552
|
const loanBalanceTotalDataAsArrays = loanBalanceTotalRows.map((row) => (0, common_helper_1.reorderObject)(row, headerKeys));
|
|
@@ -757,6 +765,30 @@ class NewSummaryExcel {
|
|
|
757
765
|
const month = (0, dayjs_1.default)(endDate).month();
|
|
758
766
|
const year = (0, dayjs_1.default)(endDate).year();
|
|
759
767
|
const monthDeep = 11;
|
|
768
|
+
const currentMonth = (0, dayjs_1.default)(endDate).utcOffset(0).month() + 1;
|
|
769
|
+
const currentYear = (0, dayjs_1.default)(endDate).utcOffset(0).year();
|
|
770
|
+
const { data: ytdDataPL } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
771
|
+
borrowerId,
|
|
772
|
+
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.PROFIT_LOSS,
|
|
773
|
+
selectedMonth: { year: currentYear, month: currentMonth },
|
|
774
|
+
}, currentMonth - 1); // jan to current
|
|
775
|
+
const { data: ytdDataBS } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
776
|
+
borrowerId,
|
|
777
|
+
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.BALANCE_SHEET,
|
|
778
|
+
selectedMonth: { year: currentYear, month: currentMonth },
|
|
779
|
+
}, currentMonth - 1); // jan to current
|
|
780
|
+
const { data: lyYtdDataPL } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
781
|
+
borrowerId,
|
|
782
|
+
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.PROFIT_LOSS,
|
|
783
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
784
|
+
}, currentMonth - 1);
|
|
785
|
+
const { data: lyYtdDataBS } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
786
|
+
borrowerId,
|
|
787
|
+
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.BALANCE_SHEET,
|
|
788
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
789
|
+
}, currentMonth - 1);
|
|
790
|
+
const ytdData = [...ytdDataPL, ...ytdDataBS];
|
|
791
|
+
const lyYtdData = [...lyYtdDataPL, ...lyYtdDataBS];
|
|
760
792
|
const { data: dataPL, sheets: sheetsPL } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
761
793
|
borrowerId,
|
|
762
794
|
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.PROFIT_LOSS,
|
|
@@ -767,19 +799,52 @@ class NewSummaryExcel {
|
|
|
767
799
|
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.BALANCE_SHEET,
|
|
768
800
|
selectedMonth: { month, year },
|
|
769
801
|
}, monthDeep);
|
|
770
|
-
const data
|
|
771
|
-
|
|
802
|
+
const { data: sameMonthLastYearPL, sheets: sameMonthLastYearSheetsPL, } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
803
|
+
borrowerId,
|
|
804
|
+
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.PROFIT_LOSS,
|
|
805
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
806
|
+
}, 0);
|
|
807
|
+
const { data: sameMonthLastYearBS, sheets: sameMonthLastYearSheetsBS, } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
808
|
+
borrowerId,
|
|
809
|
+
financialSpreadingType: FinancialSpreadingSheet_model_1.EFinancialSpreadingType.BALANCE_SHEET,
|
|
810
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
811
|
+
}, 0);
|
|
812
|
+
const data = [
|
|
813
|
+
...dataPL,
|
|
814
|
+
...dataBS,
|
|
815
|
+
...sameMonthLastYearPL,
|
|
816
|
+
...sameMonthLastYearBS,
|
|
817
|
+
];
|
|
818
|
+
const sheets = [
|
|
819
|
+
...sheetsPL,
|
|
820
|
+
...sheetsBS,
|
|
821
|
+
...sameMonthLastYearSheetsPL,
|
|
822
|
+
...sameMonthLastYearSheetsBS,
|
|
823
|
+
];
|
|
772
824
|
const generateMonthCells = () => {
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
825
|
+
const current = (0, dayjs_1.default)(`${year}-${String(month).padStart(2, '0')}-01`);
|
|
826
|
+
const sameMonthLastYear = current.subtract(1, 'year');
|
|
827
|
+
const ytdLabel = `YTD ${current.format('MMMM YYYY')}`;
|
|
828
|
+
const lyYtdLabel = `YTD ${sameMonthLastYear.format('MMMM YYYY')}`;
|
|
829
|
+
const historicalMonths = [];
|
|
830
|
+
for (let i = 1; i <= monthDeep; i++) {
|
|
831
|
+
const date = current.subtract(i, 'month');
|
|
832
|
+
historicalMonths.push({
|
|
777
833
|
v: date.endOf('month').format(dateFormat),
|
|
778
834
|
t: 's',
|
|
779
835
|
s: rightAlign,
|
|
780
836
|
});
|
|
781
837
|
}
|
|
782
|
-
return [
|
|
838
|
+
return [
|
|
839
|
+
[
|
|
840
|
+
{ v: '', t: 's' },
|
|
841
|
+
{ v: 'Current Month', t: 's' },
|
|
842
|
+
{ v: 'Same Month Last Year', t: 's' },
|
|
843
|
+
{ v: ytdLabel, t: 's' },
|
|
844
|
+
{ v: lyYtdLabel, t: 's' },
|
|
845
|
+
...historicalMonths,
|
|
846
|
+
],
|
|
847
|
+
];
|
|
783
848
|
};
|
|
784
849
|
const percentageIndexes = [FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.GROSS_MARGIN, FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.OPERATING_MARGIN];
|
|
785
850
|
const convertAmount = (index, amount) => {
|
|
@@ -865,7 +930,19 @@ class NewSummaryExcel {
|
|
|
865
930
|
[FinancialSpreadingSheet_model_1.EFinanceSpreadingBSTotal.COMPANY_DEBT]: { title: 'Senior Debt' },
|
|
866
931
|
[FinancialSpreadingSheet_model_1.EFinanceSpreadingBSTotal.TOTAL_LIABILITIES]: { style: { font: { bold: true, name: 'Calibri' } } },
|
|
867
932
|
});
|
|
933
|
+
const sumRowWithMinusFields = (entry) => {
|
|
934
|
+
const minusFields = Object.keys(entry).filter((field) => field.includes('minus_'));
|
|
935
|
+
const values = minusFields
|
|
936
|
+
.map((key) => entry?.[key] ?? 0)
|
|
937
|
+
.concat(entry.amount ?? 0);
|
|
938
|
+
return values.reduce((acc, val) => new decimal_js_1.default(acc).add(val).toNumber(), 0);
|
|
939
|
+
};
|
|
868
940
|
const generateFinancialRows = (financialIndexes, enumObj) => {
|
|
941
|
+
const totalValues = {
|
|
942
|
+
[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES]: { ytd: 0, lyYtd: 0 },
|
|
943
|
+
[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.GROSS_PROFIT]: { ytd: 0, lyYtd: 0 },
|
|
944
|
+
[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.OPERATING_EXPENSES]: { ytd: 0, lyYtd: 0 },
|
|
945
|
+
};
|
|
869
946
|
return Object.entries(financialIndexes).reduce((acc, [financialIndex, options]) => {
|
|
870
947
|
const sheet = sheets.find((s) => financialIndex === enumObj[s.rowType] && s.isTotal);
|
|
871
948
|
if (!sheet) {
|
|
@@ -876,6 +953,62 @@ class NewSummaryExcel {
|
|
|
876
953
|
return acc;
|
|
877
954
|
}
|
|
878
955
|
const baseValue = convertAmount(enumObj[financialIndex], dataEntry.amount);
|
|
956
|
+
// Same month last year value, always for same calendar month last year (0-based month)
|
|
957
|
+
const sameMonthLastYearEntry = data.find((d) => d.sheetId === sheet._id &&
|
|
958
|
+
d.year === year - 1 &&
|
|
959
|
+
d.month === month);
|
|
960
|
+
const sameMonthLastYearValue = sameMonthLastYearEntry
|
|
961
|
+
? convertAmount(enumObj[financialIndex], sameMonthLastYearEntry.amount)
|
|
962
|
+
: '';
|
|
963
|
+
// YTD and LY YTD sums
|
|
964
|
+
const entryYTD = ytdData.find((d) => d.sheetId === sheet._id);
|
|
965
|
+
let ytdSumRaw = entryYTD ? sumRowWithMinusFields(entryYTD) : 0;
|
|
966
|
+
const entryLY_YTD = lyYtdData.find((d) => d.sheetId === sheet._id);
|
|
967
|
+
let lyYtdSumRaw = entryLY_YTD ? sumRowWithMinusFields(entryLY_YTD) : 0;
|
|
968
|
+
if (Object.keys(totalValues).includes(sheet.rowType)) {
|
|
969
|
+
totalValues[sheet.rowType] = {
|
|
970
|
+
ytd: ytdSumRaw,
|
|
971
|
+
lyYtd: lyYtdSumRaw,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
switch (sheet.rowType) {
|
|
975
|
+
case FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.GROSS_MARGIN: {
|
|
976
|
+
ytdSumRaw = totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].ytd === 0
|
|
977
|
+
? 0
|
|
978
|
+
: new decimal_js_1.default(totalValues.GROSS_PROFIT.ytd)
|
|
979
|
+
.div(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].ytd)
|
|
980
|
+
.mul(100)
|
|
981
|
+
.toDecimalPlaces(2)
|
|
982
|
+
.toNumber();
|
|
983
|
+
lyYtdSumRaw = totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].lyYtd === 0
|
|
984
|
+
? 0
|
|
985
|
+
: new decimal_js_1.default(totalValues.GROSS_PROFIT.lyYtd)
|
|
986
|
+
.div(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].lyYtd)
|
|
987
|
+
.mul(100)
|
|
988
|
+
.toDecimalPlaces(2)
|
|
989
|
+
.toNumber();
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
case FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.OPERATING_MARGIN: {
|
|
993
|
+
ytdSumRaw = totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].ytd === 0
|
|
994
|
+
? 0
|
|
995
|
+
: new decimal_js_1.default(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.GROSS_PROFIT].ytd)
|
|
996
|
+
.sub(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.OPERATING_EXPENSES].ytd)
|
|
997
|
+
.div(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].ytd)
|
|
998
|
+
.toDecimalPlaces(2)
|
|
999
|
+
.toNumber();
|
|
1000
|
+
lyYtdSumRaw = totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].lyYtd === 0
|
|
1001
|
+
? 0
|
|
1002
|
+
: new decimal_js_1.default(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.GROSS_PROFIT].lyYtd)
|
|
1003
|
+
.sub(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.OPERATING_EXPENSES].lyYtd)
|
|
1004
|
+
.div(totalValues[FinancialSpreadingSheet_model_1.EFinanceSpreadingPLTotal.SALES].lyYtd)
|
|
1005
|
+
.toDecimalPlaces(2)
|
|
1006
|
+
.toNumber();
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const ytdSum = convertAmount(enumObj[financialIndex], ytdSumRaw);
|
|
1011
|
+
const lyYtdSum = convertAmount(enumObj[financialIndex], lyYtdSumRaw);
|
|
879
1012
|
const monthlyValues = Array.from({ length: monthDeep }, (_, i) => ({
|
|
880
1013
|
v: convertAmount(enumObj[financialIndex], dataEntry[`minus_${i + 1}`]),
|
|
881
1014
|
t: 'n',
|
|
@@ -885,12 +1018,20 @@ class NewSummaryExcel {
|
|
|
885
1018
|
const row = [
|
|
886
1019
|
{ v: options.title, t: 's', s: options.style },
|
|
887
1020
|
{ v: baseValue, t: 'n', z: options.format },
|
|
1021
|
+
{
|
|
1022
|
+
v: sameMonthLastYearValue,
|
|
1023
|
+
t: 'n',
|
|
1024
|
+
z: options.format,
|
|
1025
|
+
s: options.style,
|
|
1026
|
+
},
|
|
1027
|
+
{ v: ytdSum, t: 'n', z: options.format, s: options.style },
|
|
1028
|
+
{ v: lyYtdSum, t: 'n', z: options.format, s: options.style },
|
|
888
1029
|
...monthlyValues,
|
|
889
1030
|
];
|
|
890
1031
|
return options.addEmptyRow ? [...acc, row, emptyRow] : [...acc, row];
|
|
891
1032
|
}, []);
|
|
892
1033
|
};
|
|
893
|
-
const get = (base, i, key) => new decimal_js_1.default(i === 0 ? base[key]?.amount : base[key]?.[`minus_${i}`] ?? 0);
|
|
1034
|
+
const get = (base, i, key) => new decimal_js_1.default((i === 0 ? base[key]?.amount : base[key]?.[`minus_${i}`]) ?? 0);
|
|
894
1035
|
const financialRatioFormulas = [
|
|
895
1036
|
{
|
|
896
1037
|
label: 'Fixed Charge Coverage Ratio',
|
|
@@ -1020,7 +1161,22 @@ class NewSummaryExcel {
|
|
|
1020
1161
|
const financialRatiosBaseKeys = lodash_1.default.uniq(financialRatioFormulas.flatMap((f) => f.dependencies));
|
|
1021
1162
|
const financialRatiosBase = financialRatiosBaseKeys.reduce((acc, key) => {
|
|
1022
1163
|
const sheet = sheets.find((s) => s.rowType === key && s.isTotal);
|
|
1023
|
-
const dataEntry = data.find((d) =>
|
|
1164
|
+
const dataEntry = data.find((d) => {
|
|
1165
|
+
return d.sheetId.toString() === sheet._id.toString() && d.year === currentYear && d.month === currentMonth;
|
|
1166
|
+
});
|
|
1167
|
+
if (!dataEntry) {
|
|
1168
|
+
return acc;
|
|
1169
|
+
}
|
|
1170
|
+
return { ...acc, [key]: dataEntry };
|
|
1171
|
+
}, {});
|
|
1172
|
+
const financialRatiosBaseLY = financialRatiosBaseKeys.reduce((acc, key) => {
|
|
1173
|
+
const sheet = sheets.find((s) => s.rowType === key && s.isTotal);
|
|
1174
|
+
const dataEntry = data.find((d) => {
|
|
1175
|
+
return d.sheetId.toString() === sheet._id.toString() && d.year === currentYear - 1 && d.month === currentMonth;
|
|
1176
|
+
});
|
|
1177
|
+
if (!dataEntry) {
|
|
1178
|
+
return acc;
|
|
1179
|
+
}
|
|
1024
1180
|
return { ...acc, [key]: dataEntry };
|
|
1025
1181
|
}, {});
|
|
1026
1182
|
const ratioRows = financialRatioFormulas.map(({ label, formula, format }) => {
|
|
@@ -1034,6 +1190,14 @@ class NewSummaryExcel {
|
|
|
1034
1190
|
row.push(negativeCell);
|
|
1035
1191
|
}
|
|
1036
1192
|
}
|
|
1193
|
+
const result = formula(financialRatiosBaseLY, 0);
|
|
1194
|
+
if (result) {
|
|
1195
|
+
row.splice(2, 0, { v: result, t: 'n', z: format ?? excel_helper_1.NUMBER_FORMATS.thousands });
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
row.push(negativeCell);
|
|
1199
|
+
}
|
|
1200
|
+
row.splice(3, 0, ...[{ v: '' }, { v: '' }]);
|
|
1037
1201
|
return row;
|
|
1038
1202
|
});
|
|
1039
1203
|
return [...ratiosData, ...ratioRows];
|
|
@@ -1042,7 +1206,7 @@ class NewSummaryExcel {
|
|
|
1042
1206
|
const financialBSDataFull = generateFinancialRows(financialBSIndexes, FinancialSpreadingSheet_model_1.EFinanceSpreadingBSTotal);
|
|
1043
1207
|
const financialRatios = generateFinancialRatios();
|
|
1044
1208
|
return [
|
|
1045
|
-
generateMonthCells(),
|
|
1209
|
+
...generateMonthCells(),
|
|
1046
1210
|
emptyRow,
|
|
1047
1211
|
[{ v: 'PROFIT & LOSS HIGHLIGHTS', t: 's' }],
|
|
1048
1212
|
...financialPLDataFull,
|
package/db/new-summary.ts
CHANGED
|
@@ -144,7 +144,7 @@ const styles: { [styleName: string]: Partial<ExcelJS.Style> } = {
|
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
const borrowerTitleLength =
|
|
147
|
+
const borrowerTitleLength = 16;
|
|
148
148
|
const dateFormat = 'MM-DD-YYYY';
|
|
149
149
|
|
|
150
150
|
const productsDataMap = new Map<string, ReportRow>();
|
|
@@ -635,7 +635,15 @@ export class NewSummaryExcel {
|
|
|
635
635
|
}
|
|
636
636
|
});
|
|
637
637
|
if (monthEndData.length) {
|
|
638
|
-
loanBalanceTotalRows.push({
|
|
638
|
+
loanBalanceTotalRows.push({
|
|
639
|
+
...emptyTotalRow,
|
|
640
|
+
title: '',
|
|
641
|
+
netValue: null,
|
|
642
|
+
availability: null,
|
|
643
|
+
grossValue: null,
|
|
644
|
+
ineligible: null,
|
|
645
|
+
advanceRate: null,
|
|
646
|
+
});
|
|
639
647
|
}
|
|
640
648
|
}
|
|
641
649
|
|
|
@@ -885,6 +893,37 @@ export class NewSummaryExcel {
|
|
|
885
893
|
const month = dayjs(endDate).month();
|
|
886
894
|
const year = dayjs(endDate).year();
|
|
887
895
|
const monthDeep = 11;
|
|
896
|
+
|
|
897
|
+
const currentMonth = dayjs(endDate).utcOffset(0).month() + 1;
|
|
898
|
+
const currentYear = dayjs(endDate).utcOffset(0).year();
|
|
899
|
+
|
|
900
|
+
const { data: ytdDataPL } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
901
|
+
borrowerId,
|
|
902
|
+
financialSpreadingType: EFinancialSpreadingType.PROFIT_LOSS,
|
|
903
|
+
selectedMonth: { year: currentYear, month: currentMonth },
|
|
904
|
+
}, currentMonth - 1); // jan to current
|
|
905
|
+
|
|
906
|
+
const { data: ytdDataBS } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
907
|
+
borrowerId,
|
|
908
|
+
financialSpreadingType: EFinancialSpreadingType.BALANCE_SHEET,
|
|
909
|
+
selectedMonth: { year: currentYear, month: currentMonth },
|
|
910
|
+
}, currentMonth - 1); // jan to current
|
|
911
|
+
|
|
912
|
+
const { data: lyYtdDataPL } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
913
|
+
borrowerId,
|
|
914
|
+
financialSpreadingType: EFinancialSpreadingType.PROFIT_LOSS,
|
|
915
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
916
|
+
}, currentMonth - 1);
|
|
917
|
+
|
|
918
|
+
const { data: lyYtdDataBS } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
919
|
+
borrowerId,
|
|
920
|
+
financialSpreadingType: EFinancialSpreadingType.BALANCE_SHEET,
|
|
921
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
922
|
+
}, currentMonth - 1);
|
|
923
|
+
|
|
924
|
+
const ytdData = [...ytdDataPL, ...ytdDataBS];
|
|
925
|
+
const lyYtdData = [...lyYtdDataPL, ...lyYtdDataBS];
|
|
926
|
+
|
|
888
927
|
const { data: dataPL, sheets: sheetsPL } = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
889
928
|
borrowerId,
|
|
890
929
|
financialSpreadingType: EFinancialSpreadingType.PROFIT_LOSS,
|
|
@@ -897,22 +936,63 @@ export class NewSummaryExcel {
|
|
|
897
936
|
selectedMonth: { month, year },
|
|
898
937
|
}, monthDeep);
|
|
899
938
|
|
|
900
|
-
const
|
|
901
|
-
|
|
939
|
+
const {
|
|
940
|
+
data: sameMonthLastYearPL,
|
|
941
|
+
sheets: sameMonthLastYearSheetsPL,
|
|
942
|
+
} = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
943
|
+
borrowerId,
|
|
944
|
+
financialSpreadingType: EFinancialSpreadingType.PROFIT_LOSS,
|
|
945
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
946
|
+
}, 0);
|
|
947
|
+
|
|
948
|
+
const {
|
|
949
|
+
data: sameMonthLastYearBS,
|
|
950
|
+
sheets: sameMonthLastYearSheetsBS,
|
|
951
|
+
} = await this.financialSpreadingService.getFinancialSpreadingData({
|
|
952
|
+
borrowerId,
|
|
953
|
+
financialSpreadingType: EFinancialSpreadingType.BALANCE_SHEET,
|
|
954
|
+
selectedMonth: { year: currentYear - 1, month: currentMonth },
|
|
955
|
+
}, 0);
|
|
956
|
+
|
|
957
|
+
const data = [
|
|
958
|
+
...dataPL,
|
|
959
|
+
...dataBS,
|
|
960
|
+
...sameMonthLastYearPL,
|
|
961
|
+
...sameMonthLastYearBS,
|
|
962
|
+
];
|
|
963
|
+
const sheets = [
|
|
964
|
+
...sheetsPL,
|
|
965
|
+
...sheetsBS,
|
|
966
|
+
...sameMonthLastYearSheetsPL,
|
|
967
|
+
...sameMonthLastYearSheetsBS,
|
|
968
|
+
];
|
|
902
969
|
|
|
903
|
-
const generateMonthCells = (): IExcelJsCell[] => {
|
|
904
|
-
const
|
|
970
|
+
const generateMonthCells = (): IExcelJsCell[][] => {
|
|
971
|
+
const current = dayjs(`${year}-${String(month).padStart(2, '0')}-01`);
|
|
972
|
+
const sameMonthLastYear = current.subtract(1, 'year');
|
|
973
|
+
const ytdLabel = `YTD ${current.format('MMMM YYYY')}`;
|
|
974
|
+
const lyYtdLabel = `YTD ${sameMonthLastYear.format('MMMM YYYY')}`;
|
|
905
975
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
976
|
+
const historicalMonths = [];
|
|
977
|
+
for (let i = 1; i <= monthDeep; i++) {
|
|
978
|
+
const date = current.subtract(i, 'month');
|
|
979
|
+
historicalMonths.push({
|
|
909
980
|
v: date.endOf('month').format(dateFormat),
|
|
910
981
|
t: 's',
|
|
911
982
|
s: rightAlign,
|
|
912
983
|
});
|
|
913
984
|
}
|
|
914
985
|
|
|
915
|
-
return [
|
|
986
|
+
return [
|
|
987
|
+
[
|
|
988
|
+
{ v: '', t: 's' },
|
|
989
|
+
{ v: 'Current Month', t: 's' },
|
|
990
|
+
{ v: 'Same Month Last Year', t: 's' },
|
|
991
|
+
{ v: ytdLabel, t: 's' },
|
|
992
|
+
{ v: lyYtdLabel, t: 's' },
|
|
993
|
+
...historicalMonths,
|
|
994
|
+
],
|
|
995
|
+
];
|
|
916
996
|
};
|
|
917
997
|
|
|
918
998
|
const percentageIndexes = [EFinanceSpreadingPLTotal.GROSS_MARGIN, EFinanceSpreadingPLTotal.OPERATING_MARGIN] as const;
|
|
@@ -1009,7 +1089,22 @@ export class NewSummaryExcel {
|
|
|
1009
1089
|
[EFinanceSpreadingBSTotal.TOTAL_LIABILITIES]: { style: { font: { bold: true, name: 'Calibri' } } },
|
|
1010
1090
|
});
|
|
1011
1091
|
|
|
1092
|
+
const sumRowWithMinusFields = (entry: FinancialSpreadingDTO): number => {
|
|
1093
|
+
const minusFields = Object.keys(entry).filter((field) => field.includes('minus_'));
|
|
1094
|
+
const values: number[] = minusFields
|
|
1095
|
+
.map((key) => entry?.[key] ?? 0)
|
|
1096
|
+
.concat(entry.amount ?? 0);
|
|
1097
|
+
|
|
1098
|
+
return values.reduce((acc, val) => new Decimal(acc).add(val).toNumber(), 0);
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1012
1101
|
const generateFinancialRows = <T extends Record<string, string>>(financialIndexes: Record<string, FinancialIndexOptions>, enumObj: T): IExcelJsCell[][] => {
|
|
1102
|
+
const totalValues = {
|
|
1103
|
+
[EFinanceSpreadingPLTotal.SALES]: { ytd: 0, lyYtd: 0 },
|
|
1104
|
+
[EFinanceSpreadingPLTotal.GROSS_PROFIT]: { ytd: 0, lyYtd: 0 },
|
|
1105
|
+
[EFinanceSpreadingPLTotal.OPERATING_EXPENSES]: { ytd: 0, lyYtd: 0 },
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1013
1108
|
return Object.entries(financialIndexes).reduce((acc, [financialIndex, options]) => {
|
|
1014
1109
|
const sheet = sheets.find((s) => financialIndex === enumObj[s.rowType] && s.isTotal);
|
|
1015
1110
|
if (!sheet) {
|
|
@@ -1022,6 +1117,71 @@ export class NewSummaryExcel {
|
|
|
1022
1117
|
}
|
|
1023
1118
|
|
|
1024
1119
|
const baseValue = convertAmount(enumObj[financialIndex as keyof T], dataEntry.amount);
|
|
1120
|
+
|
|
1121
|
+
// Same month last year value, always for same calendar month last year (0-based month)
|
|
1122
|
+
const sameMonthLastYearEntry = data.find((d) =>
|
|
1123
|
+
d.sheetId === sheet._id &&
|
|
1124
|
+
d.year === year - 1 &&
|
|
1125
|
+
d.month === month,
|
|
1126
|
+
);
|
|
1127
|
+
const sameMonthLastYearValue = sameMonthLastYearEntry
|
|
1128
|
+
? convertAmount(enumObj[financialIndex as keyof T], sameMonthLastYearEntry.amount)
|
|
1129
|
+
: '';
|
|
1130
|
+
|
|
1131
|
+
// YTD and LY YTD sums
|
|
1132
|
+
const entryYTD = ytdData.find((d) => d.sheetId === sheet._id);
|
|
1133
|
+
let ytdSumRaw = entryYTD ? sumRowWithMinusFields(entryYTD) : 0;
|
|
1134
|
+
|
|
1135
|
+
const entryLY_YTD = lyYtdData.find((d) => d.sheetId === sheet._id);
|
|
1136
|
+
let lyYtdSumRaw = entryLY_YTD ? sumRowWithMinusFields(entryLY_YTD) : 0;
|
|
1137
|
+
|
|
1138
|
+
if (Object.keys(totalValues).includes(sheet.rowType)) {
|
|
1139
|
+
totalValues[sheet.rowType] = {
|
|
1140
|
+
ytd: ytdSumRaw,
|
|
1141
|
+
lyYtd: lyYtdSumRaw,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
switch (sheet.rowType) {
|
|
1146
|
+
case EFinanceSpreadingPLTotal.GROSS_MARGIN: {
|
|
1147
|
+
ytdSumRaw = totalValues[EFinanceSpreadingPLTotal.SALES].ytd === 0
|
|
1148
|
+
? 0
|
|
1149
|
+
: new Decimal(totalValues.GROSS_PROFIT.ytd)
|
|
1150
|
+
.div(totalValues[EFinanceSpreadingPLTotal.SALES].ytd)
|
|
1151
|
+
.mul(100)
|
|
1152
|
+
.toDecimalPlaces(2)
|
|
1153
|
+
.toNumber();
|
|
1154
|
+
lyYtdSumRaw = totalValues[EFinanceSpreadingPLTotal.SALES].lyYtd === 0
|
|
1155
|
+
? 0
|
|
1156
|
+
: new Decimal(totalValues.GROSS_PROFIT.lyYtd)
|
|
1157
|
+
.div(totalValues[EFinanceSpreadingPLTotal.SALES].lyYtd)
|
|
1158
|
+
.mul(100)
|
|
1159
|
+
.toDecimalPlaces(2)
|
|
1160
|
+
.toNumber();
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
case EFinanceSpreadingPLTotal.OPERATING_MARGIN: {
|
|
1164
|
+
ytdSumRaw = totalValues[EFinanceSpreadingPLTotal.SALES].ytd === 0
|
|
1165
|
+
? 0
|
|
1166
|
+
: new Decimal(totalValues[EFinanceSpreadingPLTotal.GROSS_PROFIT].ytd)
|
|
1167
|
+
.sub(totalValues[EFinanceSpreadingPLTotal.OPERATING_EXPENSES].ytd)
|
|
1168
|
+
.div(totalValues[EFinanceSpreadingPLTotal.SALES].ytd)
|
|
1169
|
+
.toDecimalPlaces(2)
|
|
1170
|
+
.toNumber();
|
|
1171
|
+
lyYtdSumRaw = totalValues[EFinanceSpreadingPLTotal.SALES].lyYtd === 0
|
|
1172
|
+
? 0
|
|
1173
|
+
: new Decimal(totalValues[EFinanceSpreadingPLTotal.GROSS_PROFIT].lyYtd)
|
|
1174
|
+
.sub(totalValues[EFinanceSpreadingPLTotal.OPERATING_EXPENSES].lyYtd)
|
|
1175
|
+
.div(totalValues[EFinanceSpreadingPLTotal.SALES].lyYtd)
|
|
1176
|
+
.toDecimalPlaces(2)
|
|
1177
|
+
.toNumber();
|
|
1178
|
+
break;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const ytdSum = convertAmount(enumObj[financialIndex as keyof T], ytdSumRaw);
|
|
1183
|
+
const lyYtdSum = convertAmount(enumObj[financialIndex as keyof T], lyYtdSumRaw);
|
|
1184
|
+
|
|
1025
1185
|
const monthlyValues = Array.from({ length: monthDeep }, (_, i) => ({
|
|
1026
1186
|
v: convertAmount(enumObj[financialIndex as keyof T], dataEntry[`minus_${i + 1}`]),
|
|
1027
1187
|
t: 'n',
|
|
@@ -1032,6 +1192,14 @@ export class NewSummaryExcel {
|
|
|
1032
1192
|
const row: IExcelJsCell[] = [
|
|
1033
1193
|
{ v: options.title, t: 's', s: options.style },
|
|
1034
1194
|
{ v: baseValue, t: 'n', z: options.format },
|
|
1195
|
+
{
|
|
1196
|
+
v: sameMonthLastYearValue,
|
|
1197
|
+
t: 'n',
|
|
1198
|
+
z: options.format,
|
|
1199
|
+
s: options.style,
|
|
1200
|
+
},
|
|
1201
|
+
{ v: ytdSum, t: 'n', z: options.format, s: options.style },
|
|
1202
|
+
{ v: lyYtdSum, t: 'n', z: options.format, s: options.style },
|
|
1035
1203
|
...monthlyValues,
|
|
1036
1204
|
];
|
|
1037
1205
|
|
|
@@ -1046,7 +1214,7 @@ export class NewSummaryExcel {
|
|
|
1046
1214
|
formula: (data: Record<string, FinancialSpreadingDTO>, monthIndex: number) => number;
|
|
1047
1215
|
};
|
|
1048
1216
|
|
|
1049
|
-
const get = (base: Record<string, FinancialSpreadingDTO>, i: number, key: string) => new Decimal(i === 0 ? base[key]?.amount : base[key]?.[`minus_${i}`] ?? 0);
|
|
1217
|
+
const get = (base: Record<string, FinancialSpreadingDTO>, i: number, key: string) => new Decimal((i === 0 ? base[key]?.amount : base[key]?.[`minus_${i}`]) ?? 0);
|
|
1050
1218
|
|
|
1051
1219
|
const financialRatioFormulas: FinancialRatioFormula[] = [
|
|
1052
1220
|
{
|
|
@@ -1180,7 +1348,23 @@ export class NewSummaryExcel {
|
|
|
1180
1348
|
|
|
1181
1349
|
const financialRatiosBase = financialRatiosBaseKeys.reduce((acc, key) => {
|
|
1182
1350
|
const sheet = sheets.find((s) => s.rowType === key && s.isTotal);
|
|
1183
|
-
const dataEntry = data.find((d) =>
|
|
1351
|
+
const dataEntry = data.find((d) => {
|
|
1352
|
+
return d.sheetId.toString() === sheet._id.toString() && d.year === currentYear && d.month === currentMonth;
|
|
1353
|
+
});
|
|
1354
|
+
if (!dataEntry) {
|
|
1355
|
+
return acc;
|
|
1356
|
+
}
|
|
1357
|
+
return { ...acc, [key]: dataEntry };
|
|
1358
|
+
}, {} as Record<string, FinancialSpreadingDTO>);
|
|
1359
|
+
|
|
1360
|
+
const financialRatiosBaseLY = financialRatiosBaseKeys.reduce((acc, key) => {
|
|
1361
|
+
const sheet = sheets.find((s) => s.rowType === key && s.isTotal);
|
|
1362
|
+
const dataEntry = data.find((d) => {
|
|
1363
|
+
return d.sheetId.toString() === sheet._id.toString() && d.year === currentYear - 1 && d.month === currentMonth;
|
|
1364
|
+
});
|
|
1365
|
+
if (!dataEntry) {
|
|
1366
|
+
return acc;
|
|
1367
|
+
}
|
|
1184
1368
|
return { ...acc, [key]: dataEntry };
|
|
1185
1369
|
}, {} as Record<string, FinancialSpreadingDTO>);
|
|
1186
1370
|
|
|
@@ -1194,6 +1378,14 @@ export class NewSummaryExcel {
|
|
|
1194
1378
|
row.push(negativeCell);
|
|
1195
1379
|
}
|
|
1196
1380
|
}
|
|
1381
|
+
const result = formula(financialRatiosBaseLY, 0);
|
|
1382
|
+
if (result) {
|
|
1383
|
+
row.splice(2, 0, { v: result, t: 'n', z: format ?? NUMBER_FORMATS.thousands });
|
|
1384
|
+
} else {
|
|
1385
|
+
row.push(negativeCell);
|
|
1386
|
+
}
|
|
1387
|
+
row.splice(3, 0, ...[{ v: '' }, { v: '' }]);
|
|
1388
|
+
|
|
1197
1389
|
return row;
|
|
1198
1390
|
});
|
|
1199
1391
|
|
|
@@ -1205,7 +1397,7 @@ export class NewSummaryExcel {
|
|
|
1205
1397
|
const financialRatios = generateFinancialRatios();
|
|
1206
1398
|
|
|
1207
1399
|
return [
|
|
1208
|
-
generateMonthCells(),
|
|
1400
|
+
...generateMonthCells(),
|
|
1209
1401
|
emptyRow,
|
|
1210
1402
|
[{ v: 'PROFIT & LOSS HIGHLIGHTS', t: 's' }],
|
|
1211
1403
|
...financialPLDataFull,
|
package/models/Prospect.model.js
CHANGED
|
@@ -10,6 +10,7 @@ const _models_1 = require("./_models");
|
|
|
10
10
|
const ProspectInfoStageTwo_model_1 = require("./ProspectInfoStageTwo.model");
|
|
11
11
|
const ProspectInfoStageOne_model_1 = require("./ProspectInfoStageOne.model");
|
|
12
12
|
const main_helper_1 = require("../helpers/main.helper");
|
|
13
|
+
const textFieldMaxLength = 600;
|
|
13
14
|
exports.prospectFieldsDictionary = {
|
|
14
15
|
name: 'Name',
|
|
15
16
|
industry: 'Industry',
|
|
@@ -302,13 +303,13 @@ exports.CRMProspectValidationSchema = joi_1.default.object({
|
|
|
302
303
|
dropDate: joi_1.default.date().allow(null),
|
|
303
304
|
}),
|
|
304
305
|
priority: joi_1.default.string().valid(...Object.values(EProspectPriority)).required(),
|
|
305
|
-
businessDescription: joi_1.default.string().max(
|
|
306
|
-
comments: joi_1.default.string().max(
|
|
307
|
-
collateralDescription: joi_1.default.string().max(
|
|
306
|
+
businessDescription: joi_1.default.string().max(textFieldMaxLength).allow(''),
|
|
307
|
+
comments: joi_1.default.string().max(textFieldMaxLength).allow(''),
|
|
308
|
+
collateralDescription: joi_1.default.string().max(textFieldMaxLength).allow(''),
|
|
308
309
|
declineReason: joi_1.default.string().valid(...Object.values(EProspectDeclineReason)),
|
|
309
|
-
sourceId: joi_1.default.string(),
|
|
310
|
-
closingPoints: joi_1.default.number().required(),
|
|
311
|
-
expectedLoanYield: joi_1.default.number().required(),
|
|
310
|
+
sourceId: joi_1.default.string().required(),
|
|
311
|
+
closingPoints: joi_1.default.number().required().allow(null, 0).default(0),
|
|
312
|
+
expectedLoanYield: joi_1.default.number().required().allow(null, 0).default(0),
|
|
312
313
|
state: joi_1.default.string().required(),
|
|
313
314
|
contacts: joi_1.default.array().items(joi_1.default.object({
|
|
314
315
|
name: joi_1.default.string().required().allow(''),
|
package/models/Prospect.model.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { ExternalProspectStageTwoTables, ProspectInfoStageTwo } from './Prospect
|
|
|
7
7
|
import { ExternalProspectStageOneTables, ProspectInfoStageOne } from './ProspectInfoStageOne.model';
|
|
8
8
|
import { getUUID, TDictionary } from '../helpers/main.helper';
|
|
9
9
|
|
|
10
|
+
const textFieldMaxLength = 600;
|
|
11
|
+
|
|
10
12
|
export const prospectFieldsDictionary: TDictionary = {
|
|
11
13
|
name: 'Name',
|
|
12
14
|
industry: 'Industry',
|
|
@@ -448,13 +450,13 @@ export const CRMProspectValidationSchema = Joi.object({
|
|
|
448
450
|
dropDate: Joi.date().allow(null),
|
|
449
451
|
}),
|
|
450
452
|
priority: Joi.string().valid(...Object.values(EProspectPriority)).required(),
|
|
451
|
-
businessDescription: Joi.string().max(
|
|
452
|
-
comments: Joi.string().max(
|
|
453
|
-
collateralDescription: Joi.string().max(
|
|
453
|
+
businessDescription: Joi.string().max(textFieldMaxLength).allow(''),
|
|
454
|
+
comments: Joi.string().max(textFieldMaxLength).allow(''),
|
|
455
|
+
collateralDescription: Joi.string().max(textFieldMaxLength).allow(''),
|
|
454
456
|
declineReason: Joi.string().valid(...Object.values(EProspectDeclineReason)),
|
|
455
|
-
sourceId: Joi.string(),
|
|
456
|
-
closingPoints: Joi.number().required(),
|
|
457
|
-
expectedLoanYield: Joi.number().required(),
|
|
457
|
+
sourceId: Joi.string().required(),
|
|
458
|
+
closingPoints: Joi.number().required().allow(null, 0).default(0),
|
|
459
|
+
expectedLoanYield: Joi.number().required().allow(null, 0).default(0),
|
|
458
460
|
state: Joi.string().required(),
|
|
459
461
|
contacts: Joi.array().items(Joi.object({
|
|
460
462
|
name: Joi.string().required().allow(''),
|