@hed-hog/finance 0.0.279 → 0.0.285

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 (35) hide show
  1. package/dist/dto/finance-report-query.dto.d.ts +16 -0
  2. package/dist/dto/finance-report-query.dto.d.ts.map +1 -0
  3. package/dist/dto/finance-report-query.dto.js +59 -0
  4. package/dist/dto/finance-report-query.dto.js.map +1 -0
  5. package/dist/finance-reports.controller.d.ts +71 -0
  6. package/dist/finance-reports.controller.d.ts.map +1 -0
  7. package/dist/finance-reports.controller.js +61 -0
  8. package/dist/finance-reports.controller.js.map +1 -0
  9. package/dist/finance.module.d.ts.map +1 -1
  10. package/dist/finance.module.js +2 -0
  11. package/dist/finance.module.js.map +1 -1
  12. package/dist/finance.service.d.ts +93 -0
  13. package/dist/finance.service.d.ts.map +1 -1
  14. package/dist/finance.service.js +456 -0
  15. package/dist/finance.service.js.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/hedhog/data/route.yaml +27 -0
  21. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +158 -125
  22. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +102 -88
  23. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +113 -89
  24. package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
  25. package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +96 -78
  26. package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +247 -130
  27. package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +250 -135
  28. package/hedhog/frontend/messages/en.json +33 -2
  29. package/hedhog/frontend/messages/pt.json +33 -2
  30. package/package.json +6 -6
  31. package/src/dto/finance-report-query.dto.ts +49 -0
  32. package/src/finance-reports.controller.ts +28 -0
  33. package/src/finance.module.ts +2 -0
  34. package/src/finance.service.ts +645 -10
  35. package/src/index.ts +1 -0
@@ -1,19 +1,19 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
2
  import {
3
- PageOrderDirection,
4
- PaginationDTO,
5
- PaginationService,
3
+ PageOrderDirection,
4
+ PaginationDTO,
5
+ PaginationService,
6
6
  } from '@hed-hog/api-pagination';
7
7
  import { PrismaService } from '@hed-hog/api-prisma';
8
8
  import { AiService, FileService, SettingService } from '@hed-hog/core';
9
9
  import {
10
- BadRequestException,
11
- ConflictException,
12
- forwardRef,
13
- Inject,
14
- Injectable,
15
- Logger,
16
- NotFoundException,
10
+ BadRequestException,
11
+ ConflictException,
12
+ forwardRef,
13
+ Inject,
14
+ Injectable,
15
+ Logger,
16
+ NotFoundException,
17
17
  } from '@nestjs/common';
18
18
  import { createHash } from 'node:crypto';
19
19
  import { readFile } from 'node:fs/promises';
@@ -52,6 +52,7 @@ type TitleStatus =
52
52
  | 'overdue';
53
53
 
54
54
  type ForecastScenario = 'base' | 'pessimista' | 'otimista';
55
+ type FinanceReportGroupBy = 'day' | 'week' | 'month' | 'year';
55
56
 
56
57
  type FinancialScenarioConfig = {
57
58
  id: ForecastScenario;
@@ -932,6 +933,465 @@ export class FinanceService {
932
933
  };
933
934
  }
934
935
 
936
+ async getOverviewResultsReport(filters?: {
937
+ from?: string;
938
+ to?: string;
939
+ groupBy?: FinanceReportGroupBy;
940
+ }) {
941
+ const { fromDate, toDate } = this.resolveReportDateRange(
942
+ filters?.from,
943
+ filters?.to,
944
+ );
945
+ const groupBy = this.resolveReportGroupBy(filters?.groupBy);
946
+ const installments = await this.prisma.financial_installment.findMany({
947
+ where: {
948
+ competence_date: {
949
+ gte: fromDate,
950
+ lte: toDate,
951
+ },
952
+ status: {
953
+ not: 'canceled',
954
+ },
955
+ financial_title: {
956
+ status: {
957
+ notIn: ['draft', 'canceled'],
958
+ },
959
+ },
960
+ },
961
+ select: {
962
+ competence_date: true,
963
+ amount_cents: true,
964
+ financial_title: {
965
+ select: {
966
+ title_type: true,
967
+ finance_category: {
968
+ select: {
969
+ code: true,
970
+ name: true,
971
+ kind: true,
972
+ },
973
+ },
974
+ },
975
+ },
976
+ },
977
+ orderBy: {
978
+ competence_date: 'asc',
979
+ },
980
+ });
981
+
982
+ const grouped = new Map<
983
+ string,
984
+ {
985
+ period: string;
986
+ faturamento: number;
987
+ despesasEmprestimos: number;
988
+ diferenca: number;
989
+ aporteInvestidor: number;
990
+ emprestimoBanco: number;
991
+ despesas: number;
992
+ }
993
+ >();
994
+
995
+ for (const installment of installments) {
996
+ const category = installment.financial_title.finance_category;
997
+
998
+ if (this.isTransferReportCategory(category)) {
999
+ continue;
1000
+ }
1001
+
1002
+ const period = this.getReportBucketKey(
1003
+ installment.competence_date,
1004
+ groupBy,
1005
+ );
1006
+ const current = grouped.get(period) || {
1007
+ period,
1008
+ faturamento: 0,
1009
+ despesasEmprestimos: 0,
1010
+ diferenca: 0,
1011
+ aporteInvestidor: 0,
1012
+ emprestimoBanco: 0,
1013
+ despesas: 0,
1014
+ };
1015
+ const amount = this.fromCents(installment.amount_cents);
1016
+
1017
+ if (installment.financial_title.title_type === 'receivable') {
1018
+ if (this.isInvestorContributionReportCategory(category)) {
1019
+ current.aporteInvestidor += amount;
1020
+ } else if (this.isLoanReportCategory(category)) {
1021
+ current.emprestimoBanco += amount;
1022
+ } else {
1023
+ current.faturamento += amount;
1024
+ }
1025
+ } else if (this.isLoanReportCategory(category)) {
1026
+ current.emprestimoBanco += amount;
1027
+ } else {
1028
+ current.despesas += amount;
1029
+ }
1030
+
1031
+ current.despesasEmprestimos =
1032
+ current.despesas + current.emprestimoBanco;
1033
+ current.diferenca = current.faturamento - current.despesasEmprestimos;
1034
+
1035
+ grouped.set(period, current);
1036
+ }
1037
+
1038
+ const rows = Array.from(grouped.values())
1039
+ .sort((a, b) => this.sortReportBuckets(a.period, b.period, groupBy))
1040
+ .map((row) => ({
1041
+ period: row.period,
1042
+ faturamento: this.roundCurrency(row.faturamento),
1043
+ despesasEmprestimos: this.roundCurrency(row.despesasEmprestimos),
1044
+ diferenca: this.roundCurrency(row.diferenca),
1045
+ aporteInvestidor: this.roundCurrency(row.aporteInvestidor),
1046
+ emprestimoBanco: this.roundCurrency(row.emprestimoBanco),
1047
+ despesas: this.roundCurrency(row.despesas),
1048
+ }));
1049
+
1050
+ const totals = rows.reduce(
1051
+ (acc, row) => {
1052
+ acc.faturamento += row.faturamento;
1053
+ acc.despesasEmprestimos += row.despesasEmprestimos;
1054
+ acc.diferenca += row.diferenca;
1055
+ acc.aporteInvestidor += row.aporteInvestidor;
1056
+ acc.emprestimoBanco += row.emprestimoBanco;
1057
+ acc.despesas += row.despesas;
1058
+ return acc;
1059
+ },
1060
+ {
1061
+ faturamento: 0,
1062
+ despesasEmprestimos: 0,
1063
+ diferenca: 0,
1064
+ aporteInvestidor: 0,
1065
+ emprestimoBanco: 0,
1066
+ despesas: 0,
1067
+ },
1068
+ );
1069
+
1070
+ return {
1071
+ rows,
1072
+ totals: {
1073
+ faturamento: this.roundCurrency(totals.faturamento),
1074
+ despesasEmprestimos: this.roundCurrency(totals.despesasEmprestimos),
1075
+ diferenca: this.roundCurrency(totals.diferenca),
1076
+ aporteInvestidor: this.roundCurrency(totals.aporteInvestidor),
1077
+ emprestimoBanco: this.roundCurrency(totals.emprestimoBanco),
1078
+ despesas: this.roundCurrency(totals.despesas),
1079
+ margem:
1080
+ totals.faturamento > 0
1081
+ ? this.roundCurrency(
1082
+ (totals.diferenca / Math.abs(totals.faturamento)) * 100,
1083
+ )
1084
+ : 0,
1085
+ },
1086
+ };
1087
+ }
1088
+
1089
+ async getTopCustomersReport(filters?: {
1090
+ from?: string;
1091
+ to?: string;
1092
+ groupBy?: FinanceReportGroupBy;
1093
+ topN?: number;
1094
+ search?: string;
1095
+ }) {
1096
+ const { fromDate, toDate } = this.resolveReportDateRange(
1097
+ filters?.from,
1098
+ filters?.to,
1099
+ );
1100
+ const groupBy = this.resolveReportGroupBy(filters?.groupBy);
1101
+ const topN = this.resolveTopN(filters?.topN);
1102
+ const normalizedSearch = this.normalizeReportText(filters?.search || '');
1103
+ const installments = await this.prisma.financial_installment.findMany({
1104
+ where: {
1105
+ competence_date: {
1106
+ gte: fromDate,
1107
+ lte: toDate,
1108
+ },
1109
+ status: {
1110
+ not: 'canceled',
1111
+ },
1112
+ financial_title: {
1113
+ title_type: 'receivable',
1114
+ status: {
1115
+ notIn: ['draft', 'canceled'],
1116
+ },
1117
+ },
1118
+ },
1119
+ select: {
1120
+ competence_date: true,
1121
+ amount_cents: true,
1122
+ financial_title: {
1123
+ select: {
1124
+ person_id: true,
1125
+ person: {
1126
+ select: {
1127
+ name: true,
1128
+ },
1129
+ },
1130
+ finance_category: {
1131
+ select: {
1132
+ code: true,
1133
+ name: true,
1134
+ kind: true,
1135
+ },
1136
+ },
1137
+ },
1138
+ },
1139
+ },
1140
+ orderBy: {
1141
+ competence_date: 'asc',
1142
+ },
1143
+ });
1144
+
1145
+ const byCustomer = new Map<string, { customer: string; value: number }>();
1146
+ const byBucket = new Map<string, number>();
1147
+
1148
+ for (const installment of installments) {
1149
+ const category = installment.financial_title.finance_category;
1150
+
1151
+ if (
1152
+ this.isTransferReportCategory(category) ||
1153
+ this.isLoanReportCategory(category) ||
1154
+ this.isInvestorContributionReportCategory(category)
1155
+ ) {
1156
+ continue;
1157
+ }
1158
+
1159
+ const customer =
1160
+ installment.financial_title.person?.name ||
1161
+ `Customer ${installment.financial_title.person_id}`;
1162
+ const amount = this.fromCents(installment.amount_cents);
1163
+ const current = byCustomer.get(customer) || { customer, value: 0 };
1164
+
1165
+ current.value += amount;
1166
+ byCustomer.set(customer, current);
1167
+
1168
+ const period = this.getReportBucketKey(
1169
+ installment.competence_date,
1170
+ groupBy,
1171
+ );
1172
+
1173
+ byBucket.set(period, (byBucket.get(period) || 0) + amount);
1174
+ }
1175
+
1176
+ const filteredCustomers = Array.from(byCustomer.values())
1177
+ .filter((item) =>
1178
+ normalizedSearch.length === 0
1179
+ ? true
1180
+ : this.normalizeReportText(item.customer).includes(normalizedSearch),
1181
+ )
1182
+ .sort((a, b) => b.value - a.value);
1183
+
1184
+ const topCustomers = filteredCustomers.slice(0, topN).map((item) => ({
1185
+ customer: item.customer,
1186
+ value: this.roundCurrency(item.value),
1187
+ }));
1188
+ const total = this.roundCurrency(
1189
+ topCustomers.reduce((acc, item) => acc + item.value, 0),
1190
+ );
1191
+ const top5 = this.roundCurrency(
1192
+ topCustomers.slice(0, 5).reduce((acc, item) => acc + item.value, 0),
1193
+ );
1194
+ const pieData = topCustomers.slice(0, 9).map((item) => ({
1195
+ customer: item.customer,
1196
+ value: item.value,
1197
+ }));
1198
+ const othersValue = this.roundCurrency(
1199
+ topCustomers.slice(9).reduce((acc, item) => acc + item.value, 0),
1200
+ );
1201
+
1202
+ if (othersValue > 0) {
1203
+ pieData.push({
1204
+ customer: 'Outros',
1205
+ value: othersValue,
1206
+ });
1207
+ }
1208
+
1209
+ const groupedPeriods = Array.from(byBucket.entries())
1210
+ .map(([period, value]) => ({
1211
+ period,
1212
+ value: this.roundCurrency(value),
1213
+ }))
1214
+ .sort((a, b) => this.sortReportBuckets(a.period, b.period, groupBy));
1215
+
1216
+ return {
1217
+ total,
1218
+ top5Percent: total > 0 ? this.roundCurrency((top5 / total) * 100) : 0,
1219
+ topCustomers,
1220
+ pieData,
1221
+ groupedPeriods,
1222
+ leader: topCustomers[0] || null,
1223
+ };
1224
+ }
1225
+
1226
+ async getTopOperationalExpensesReport(filters?: {
1227
+ from?: string;
1228
+ to?: string;
1229
+ groupBy?: FinanceReportGroupBy;
1230
+ topN?: number;
1231
+ search?: string;
1232
+ }) {
1233
+ const { fromDate, toDate } = this.resolveReportDateRange(
1234
+ filters?.from,
1235
+ filters?.to,
1236
+ );
1237
+ const groupBy = this.resolveReportGroupBy(filters?.groupBy);
1238
+ const topN = this.resolveTopN(filters?.topN);
1239
+ const normalizedSearch = this.normalizeReportText(filters?.search || '');
1240
+ const installments = await this.prisma.financial_installment.findMany({
1241
+ where: {
1242
+ competence_date: {
1243
+ gte: fromDate,
1244
+ lte: toDate,
1245
+ },
1246
+ status: {
1247
+ not: 'canceled',
1248
+ },
1249
+ financial_title: {
1250
+ title_type: 'payable',
1251
+ status: {
1252
+ notIn: ['draft', 'canceled'],
1253
+ },
1254
+ },
1255
+ },
1256
+ select: {
1257
+ competence_date: true,
1258
+ amount_cents: true,
1259
+ installment_allocation: {
1260
+ select: {
1261
+ allocated_amount_cents: true,
1262
+ cost_center: {
1263
+ select: {
1264
+ name: true,
1265
+ },
1266
+ },
1267
+ },
1268
+ },
1269
+ financial_title: {
1270
+ select: {
1271
+ finance_category: {
1272
+ select: {
1273
+ code: true,
1274
+ name: true,
1275
+ kind: true,
1276
+ },
1277
+ },
1278
+ },
1279
+ },
1280
+ },
1281
+ orderBy: {
1282
+ competence_date: 'asc',
1283
+ },
1284
+ });
1285
+
1286
+ const byExpense = new Map<
1287
+ string,
1288
+ { category: string; costCenter: string; label: string; value: number }
1289
+ >();
1290
+ const byCostCenter = new Map<string, number>();
1291
+ const byBucket = new Map<string, number>();
1292
+
1293
+ for (const installment of installments) {
1294
+ const category = installment.financial_title.finance_category;
1295
+
1296
+ if (
1297
+ this.isTransferReportCategory(category) ||
1298
+ this.isLoanReportCategory(category)
1299
+ ) {
1300
+ continue;
1301
+ }
1302
+
1303
+ const allocations =
1304
+ installment.installment_allocation.length > 0
1305
+ ? installment.installment_allocation.map((allocation) => ({
1306
+ costCenter: allocation.cost_center?.name || 'N/A',
1307
+ amount: this.fromCents(allocation.allocated_amount_cents),
1308
+ }))
1309
+ : [
1310
+ {
1311
+ costCenter: 'N/A',
1312
+ amount: this.fromCents(installment.amount_cents),
1313
+ },
1314
+ ];
1315
+
1316
+ const period = this.getReportBucketKey(
1317
+ installment.competence_date,
1318
+ groupBy,
1319
+ );
1320
+
1321
+ for (const allocation of allocations) {
1322
+ const categoryName = category?.name || 'Sem categoria';
1323
+ const key = `${categoryName}::${allocation.costCenter}`;
1324
+ const current = byExpense.get(key) || {
1325
+ category: categoryName,
1326
+ costCenter: allocation.costCenter,
1327
+ label: `${categoryName} - ${allocation.costCenter}`,
1328
+ value: 0,
1329
+ };
1330
+
1331
+ current.value += allocation.amount;
1332
+ byExpense.set(key, current);
1333
+ byCostCenter.set(
1334
+ allocation.costCenter,
1335
+ (byCostCenter.get(allocation.costCenter) || 0) + allocation.amount,
1336
+ );
1337
+ byBucket.set(period, (byBucket.get(period) || 0) + allocation.amount);
1338
+ }
1339
+ }
1340
+
1341
+ const filteredExpenses = Array.from(byExpense.values())
1342
+ .filter((item) =>
1343
+ normalizedSearch.length === 0
1344
+ ? true
1345
+ : this.normalizeReportText(
1346
+ `${item.category} ${item.costCenter}`,
1347
+ ).includes(normalizedSearch),
1348
+ )
1349
+ .sort((a, b) => b.value - a.value);
1350
+
1351
+ const topExpenses = filteredExpenses.slice(0, topN).map((item) => ({
1352
+ category: item.category,
1353
+ costCenter: item.costCenter,
1354
+ label: item.label,
1355
+ value: this.roundCurrency(item.value),
1356
+ }));
1357
+ const total = this.roundCurrency(
1358
+ topExpenses.reduce((acc, item) => acc + item.value, 0),
1359
+ );
1360
+ const pieByCostCenter = new Map<string, number>();
1361
+
1362
+ for (const item of topExpenses) {
1363
+ pieByCostCenter.set(
1364
+ item.costCenter,
1365
+ (pieByCostCenter.get(item.costCenter) || 0) + item.value,
1366
+ );
1367
+ }
1368
+
1369
+ const pieData = Array.from(pieByCostCenter.entries())
1370
+ .map(([name, value]) => ({
1371
+ name,
1372
+ value: this.roundCurrency(value),
1373
+ }))
1374
+ .sort((a, b) => b.value - a.value);
1375
+ const groupedPeriods = Array.from(byBucket.entries())
1376
+ .map(([period, value]) => ({
1377
+ period,
1378
+ value: this.roundCurrency(value),
1379
+ }))
1380
+ .sort((a, b) => this.sortReportBuckets(a.period, b.period, groupBy));
1381
+
1382
+ return {
1383
+ total,
1384
+ average:
1385
+ topExpenses.length > 0
1386
+ ? this.roundCurrency(total / topExpenses.length)
1387
+ : 0,
1388
+ topExpenses,
1389
+ pieData,
1390
+ groupedPeriods,
1391
+ highest: topExpenses[0] || null,
1392
+ };
1393
+ }
1394
+
935
1395
  async updateScenarioSettings(
936
1396
  scenario: string,
937
1397
  data: {
@@ -6153,6 +6613,181 @@ export class FinanceService {
6153
6613
  }
6154
6614
  }
6155
6615
 
6616
+ private resolveReportDateRange(from?: string, to?: string) {
6617
+ const fromDate = from
6618
+ ? new Date(`${from}T00:00:00.000`)
6619
+ : new Date('2021-01-01T00:00:00.000');
6620
+ const toDate = to
6621
+ ? new Date(`${to}T23:59:59.999`)
6622
+ : new Date('2026-12-31T23:59:59.999');
6623
+
6624
+ if (
6625
+ Number.isNaN(fromDate.getTime()) ||
6626
+ Number.isNaN(toDate.getTime()) ||
6627
+ fromDate > toDate
6628
+ ) {
6629
+ throw new BadRequestException('Invalid report date range');
6630
+ }
6631
+
6632
+ return { fromDate, toDate };
6633
+ }
6634
+
6635
+ private resolveReportGroupBy(groupBy?: string): FinanceReportGroupBy {
6636
+ if (groupBy === 'day' || groupBy === 'week' || groupBy === 'month') {
6637
+ return groupBy;
6638
+ }
6639
+
6640
+ return 'year';
6641
+ }
6642
+
6643
+ private resolveTopN(topN?: number) {
6644
+ if (!topN || !Number.isFinite(topN)) {
6645
+ return 20;
6646
+ }
6647
+
6648
+ return Math.max(1, Math.min(100, Math.trunc(topN)));
6649
+ }
6650
+
6651
+ private getReportBucketKey(date: Date, groupBy: FinanceReportGroupBy) {
6652
+ const baseDate = this.startOfDay(date);
6653
+
6654
+ if (groupBy === 'day') {
6655
+ return baseDate.toISOString().slice(0, 10);
6656
+ }
6657
+
6658
+ if (groupBy === 'week') {
6659
+ const weekStart = this.startOfWeek(baseDate);
6660
+ const isoWeek = this.getIsoWeek(weekStart);
6661
+
6662
+ return `${weekStart.getFullYear()}-W${String(isoWeek).padStart(2, '0')}`;
6663
+ }
6664
+
6665
+ if (groupBy === 'month') {
6666
+ return `${baseDate.getFullYear()}-${String(baseDate.getMonth() + 1).padStart(2, '0')}`;
6667
+ }
6668
+
6669
+ return String(baseDate.getFullYear());
6670
+ }
6671
+
6672
+ private sortReportBuckets(
6673
+ a: string,
6674
+ b: string,
6675
+ groupBy: FinanceReportGroupBy,
6676
+ ) {
6677
+ if (groupBy === 'year' || groupBy === 'month' || groupBy === 'day') {
6678
+ return a.localeCompare(b);
6679
+ }
6680
+
6681
+ const [aYear, aWeek] = a.split('-W');
6682
+ const [bYear, bWeek] = b.split('-W');
6683
+ const yearDiff = Number(aYear) - Number(bYear);
6684
+
6685
+ if (yearDiff !== 0) {
6686
+ return yearDiff;
6687
+ }
6688
+
6689
+ return Number(aWeek) - Number(bWeek);
6690
+ }
6691
+
6692
+ private startOfWeek(date: Date) {
6693
+ const current = new Date(date);
6694
+ const day = current.getDay();
6695
+ const diff = day === 0 ? -6 : 1 - day;
6696
+
6697
+ current.setDate(current.getDate() + diff);
6698
+ current.setHours(0, 0, 0, 0);
6699
+
6700
+ return current;
6701
+ }
6702
+
6703
+ private getIsoWeek(date: Date) {
6704
+ const current = new Date(
6705
+ Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
6706
+ );
6707
+ const dayNum = current.getUTCDay() || 7;
6708
+
6709
+ current.setUTCDate(current.getUTCDate() + 4 - dayNum);
6710
+
6711
+ const yearStart = new Date(Date.UTC(current.getUTCFullYear(), 0, 1));
6712
+
6713
+ return Math.ceil(
6714
+ ((current.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7,
6715
+ );
6716
+ }
6717
+
6718
+ private normalizeReportText(value: string) {
6719
+ return value
6720
+ .normalize('NFD')
6721
+ .replace(/[\u0300-\u036f]/g, '')
6722
+ .toLowerCase()
6723
+ .trim();
6724
+ }
6725
+
6726
+ private isTransferReportCategory(category?: {
6727
+ code?: string | null;
6728
+ name?: string | null;
6729
+ kind?: string | null;
6730
+ } | null) {
6731
+ if (!category) {
6732
+ return false;
6733
+ }
6734
+
6735
+ if (category.kind === 'transfer') {
6736
+ return true;
6737
+ }
6738
+
6739
+ const haystack = this.normalizeReportText(
6740
+ `${category.code || ''} ${category.name || ''}`,
6741
+ );
6742
+
6743
+ return haystack.includes('transfer');
6744
+ }
6745
+
6746
+ private isLoanReportCategory(category?: {
6747
+ code?: string | null;
6748
+ name?: string | null;
6749
+ kind?: string | null;
6750
+ } | null) {
6751
+ if (!category) {
6752
+ return false;
6753
+ }
6754
+
6755
+ const haystack = this.normalizeReportText(
6756
+ `${category.code || ''} ${category.name || ''}`,
6757
+ );
6758
+
6759
+ return (
6760
+ haystack.includes('emprestimo') ||
6761
+ haystack.includes('financiamento') ||
6762
+ haystack.includes('loan')
6763
+ );
6764
+ }
6765
+
6766
+ private isInvestorContributionReportCategory(category?: {
6767
+ code?: string | null;
6768
+ name?: string | null;
6769
+ kind?: string | null;
6770
+ } | null) {
6771
+ if (!category) {
6772
+ return false;
6773
+ }
6774
+
6775
+ const haystack = this.normalizeReportText(
6776
+ `${category.code || ''} ${category.name || ''}`,
6777
+ );
6778
+
6779
+ return (
6780
+ haystack.includes('aporte') ||
6781
+ haystack.includes('investidor') ||
6782
+ haystack.includes('capital') ||
6783
+ haystack.includes('socio')
6784
+ );
6785
+ }
6786
+
6787
+ private roundCurrency(value: number) {
6788
+ return Number(value.toFixed(2));
6789
+ }
6790
+
6156
6791
  private toCents(value: number) {
6157
6792
  return Math.round(value * 100);
6158
6793
  }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './finance-cost-centers.controller';
7
7
  export * from './finance-data.controller';
8
8
  export * from './finance-installments.controller';
9
9
  export * from './finance-period-close.controller';
10
+ export * from './finance-reports.controller';
10
11
  export * from './finance-statements.controller';
11
12
  export * from './finance-transfers.controller';
12
13
  export * from './finance.module';