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 CHANGED
@@ -80,7 +80,7 @@ const styles = {
80
80
  },
81
81
  },
82
82
  };
83
- const borrowerTitleLength = 12;
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({ ...emptyTotalRow, title: '', netValue: null, availability: null, grossValue: null, ineligible: null, advanceRate: null });
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 = [...dataPL, ...dataBS];
771
- const sheets = [...sheetsPL, ...sheetsBS];
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 result = [];
774
- for (let i = 0; i <= monthDeep; i++) {
775
- const date = (0, dayjs_1.default)(`${year}-${String(month).padStart(2, '0')}-01`).subtract(i, 'month');
776
- result.push({
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 [{ v: 'Last 12 months', t: 's' }, ...result];
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) => d.sheetId.toString() === sheet._id.toString());
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 = 12;
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({ ...emptyTotalRow, title: '', netValue: null, availability: null, grossValue: null, ineligible: null, advanceRate: null });
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 data = [...dataPL, ...dataBS];
901
- const sheets = [...sheetsPL, ...sheetsBS];
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 result: IExcelJsCell[] = [];
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
- for (let i = 0; i <= monthDeep; i++) {
907
- const date = dayjs(`${year}-${String(month).padStart(2, '0')}-01`).subtract(i, 'month');
908
- result.push({
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 [{ v: 'Last 12 months', t: 's' }, ...result];
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) => d.sheetId.toString() === sheet._id.toString());
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,
@@ -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(400).allow(''),
306
- comments: joi_1.default.string().max(400).allow(''),
307
- collateralDescription: joi_1.default.string().max(400).allow(''),
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(''),
@@ -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(400).allow(''),
452
- comments: Joi.string().max(400).allow(''),
453
- collateralDescription: Joi.string().max(400).allow(''),
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(''),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemcap-be-common",
3
- "version": "1.3.63",
3
+ "version": "1.3.65",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {