@hed-hog/finance 0.0.278 → 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.
- package/README.md +65 -29
- package/dist/dto/finance-report-query.dto.d.ts +16 -0
- package/dist/dto/finance-report-query.dto.d.ts.map +1 -0
- package/dist/dto/finance-report-query.dto.js +59 -0
- package/dist/dto/finance-report-query.dto.js.map +1 -0
- package/dist/finance-reports.controller.d.ts +71 -0
- package/dist/finance-reports.controller.d.ts.map +1 -0
- package/dist/finance-reports.controller.js +61 -0
- package/dist/finance-reports.controller.js.map +1 -0
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +2 -0
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +93 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +456 -0
- package/dist/finance.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/hedhog/data/menu.yaml +46 -0
- package/hedhog/data/route.yaml +27 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +158 -125
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +102 -88
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +113 -89
- package/hedhog/frontend/app/reports/_lib/report-aggregations.ts.ejs +275 -0
- package/hedhog/frontend/app/reports/_lib/report-mocks.ts.ejs +186 -0
- package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
- package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +355 -0
- package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +427 -0
- package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +433 -0
- package/hedhog/frontend/messages/en.json +179 -0
- package/hedhog/frontend/messages/pt.json +179 -0
- package/package.json +7 -7
- package/src/dto/finance-report-query.dto.ts +49 -0
- package/src/finance-reports.controller.ts +28 -0
- package/src/finance.module.ts +2 -0
- package/src/finance.service.ts +645 -10
- package/src/index.ts +1 -0
package/dist/finance.service.js
CHANGED
|
@@ -627,6 +627,355 @@ let FinanceService = FinanceService_1 = class FinanceService {
|
|
|
627
627
|
periodoAberto: openPeriod,
|
|
628
628
|
};
|
|
629
629
|
}
|
|
630
|
+
async getOverviewResultsReport(filters) {
|
|
631
|
+
const { fromDate, toDate } = this.resolveReportDateRange(filters === null || filters === void 0 ? void 0 : filters.from, filters === null || filters === void 0 ? void 0 : filters.to);
|
|
632
|
+
const groupBy = this.resolveReportGroupBy(filters === null || filters === void 0 ? void 0 : filters.groupBy);
|
|
633
|
+
const installments = await this.prisma.financial_installment.findMany({
|
|
634
|
+
where: {
|
|
635
|
+
competence_date: {
|
|
636
|
+
gte: fromDate,
|
|
637
|
+
lte: toDate,
|
|
638
|
+
},
|
|
639
|
+
status: {
|
|
640
|
+
not: 'canceled',
|
|
641
|
+
},
|
|
642
|
+
financial_title: {
|
|
643
|
+
status: {
|
|
644
|
+
notIn: ['draft', 'canceled'],
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
select: {
|
|
649
|
+
competence_date: true,
|
|
650
|
+
amount_cents: true,
|
|
651
|
+
financial_title: {
|
|
652
|
+
select: {
|
|
653
|
+
title_type: true,
|
|
654
|
+
finance_category: {
|
|
655
|
+
select: {
|
|
656
|
+
code: true,
|
|
657
|
+
name: true,
|
|
658
|
+
kind: true,
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
orderBy: {
|
|
665
|
+
competence_date: 'asc',
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
const grouped = new Map();
|
|
669
|
+
for (const installment of installments) {
|
|
670
|
+
const category = installment.financial_title.finance_category;
|
|
671
|
+
if (this.isTransferReportCategory(category)) {
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
const period = this.getReportBucketKey(installment.competence_date, groupBy);
|
|
675
|
+
const current = grouped.get(period) || {
|
|
676
|
+
period,
|
|
677
|
+
faturamento: 0,
|
|
678
|
+
despesasEmprestimos: 0,
|
|
679
|
+
diferenca: 0,
|
|
680
|
+
aporteInvestidor: 0,
|
|
681
|
+
emprestimoBanco: 0,
|
|
682
|
+
despesas: 0,
|
|
683
|
+
};
|
|
684
|
+
const amount = this.fromCents(installment.amount_cents);
|
|
685
|
+
if (installment.financial_title.title_type === 'receivable') {
|
|
686
|
+
if (this.isInvestorContributionReportCategory(category)) {
|
|
687
|
+
current.aporteInvestidor += amount;
|
|
688
|
+
}
|
|
689
|
+
else if (this.isLoanReportCategory(category)) {
|
|
690
|
+
current.emprestimoBanco += amount;
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
current.faturamento += amount;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
else if (this.isLoanReportCategory(category)) {
|
|
697
|
+
current.emprestimoBanco += amount;
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
current.despesas += amount;
|
|
701
|
+
}
|
|
702
|
+
current.despesasEmprestimos =
|
|
703
|
+
current.despesas + current.emprestimoBanco;
|
|
704
|
+
current.diferenca = current.faturamento - current.despesasEmprestimos;
|
|
705
|
+
grouped.set(period, current);
|
|
706
|
+
}
|
|
707
|
+
const rows = Array.from(grouped.values())
|
|
708
|
+
.sort((a, b) => this.sortReportBuckets(a.period, b.period, groupBy))
|
|
709
|
+
.map((row) => ({
|
|
710
|
+
period: row.period,
|
|
711
|
+
faturamento: this.roundCurrency(row.faturamento),
|
|
712
|
+
despesasEmprestimos: this.roundCurrency(row.despesasEmprestimos),
|
|
713
|
+
diferenca: this.roundCurrency(row.diferenca),
|
|
714
|
+
aporteInvestidor: this.roundCurrency(row.aporteInvestidor),
|
|
715
|
+
emprestimoBanco: this.roundCurrency(row.emprestimoBanco),
|
|
716
|
+
despesas: this.roundCurrency(row.despesas),
|
|
717
|
+
}));
|
|
718
|
+
const totals = rows.reduce((acc, row) => {
|
|
719
|
+
acc.faturamento += row.faturamento;
|
|
720
|
+
acc.despesasEmprestimos += row.despesasEmprestimos;
|
|
721
|
+
acc.diferenca += row.diferenca;
|
|
722
|
+
acc.aporteInvestidor += row.aporteInvestidor;
|
|
723
|
+
acc.emprestimoBanco += row.emprestimoBanco;
|
|
724
|
+
acc.despesas += row.despesas;
|
|
725
|
+
return acc;
|
|
726
|
+
}, {
|
|
727
|
+
faturamento: 0,
|
|
728
|
+
despesasEmprestimos: 0,
|
|
729
|
+
diferenca: 0,
|
|
730
|
+
aporteInvestidor: 0,
|
|
731
|
+
emprestimoBanco: 0,
|
|
732
|
+
despesas: 0,
|
|
733
|
+
});
|
|
734
|
+
return {
|
|
735
|
+
rows,
|
|
736
|
+
totals: {
|
|
737
|
+
faturamento: this.roundCurrency(totals.faturamento),
|
|
738
|
+
despesasEmprestimos: this.roundCurrency(totals.despesasEmprestimos),
|
|
739
|
+
diferenca: this.roundCurrency(totals.diferenca),
|
|
740
|
+
aporteInvestidor: this.roundCurrency(totals.aporteInvestidor),
|
|
741
|
+
emprestimoBanco: this.roundCurrency(totals.emprestimoBanco),
|
|
742
|
+
despesas: this.roundCurrency(totals.despesas),
|
|
743
|
+
margem: totals.faturamento > 0
|
|
744
|
+
? this.roundCurrency((totals.diferenca / Math.abs(totals.faturamento)) * 100)
|
|
745
|
+
: 0,
|
|
746
|
+
},
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
async getTopCustomersReport(filters) {
|
|
750
|
+
var _a;
|
|
751
|
+
const { fromDate, toDate } = this.resolveReportDateRange(filters === null || filters === void 0 ? void 0 : filters.from, filters === null || filters === void 0 ? void 0 : filters.to);
|
|
752
|
+
const groupBy = this.resolveReportGroupBy(filters === null || filters === void 0 ? void 0 : filters.groupBy);
|
|
753
|
+
const topN = this.resolveTopN(filters === null || filters === void 0 ? void 0 : filters.topN);
|
|
754
|
+
const normalizedSearch = this.normalizeReportText((filters === null || filters === void 0 ? void 0 : filters.search) || '');
|
|
755
|
+
const installments = await this.prisma.financial_installment.findMany({
|
|
756
|
+
where: {
|
|
757
|
+
competence_date: {
|
|
758
|
+
gte: fromDate,
|
|
759
|
+
lte: toDate,
|
|
760
|
+
},
|
|
761
|
+
status: {
|
|
762
|
+
not: 'canceled',
|
|
763
|
+
},
|
|
764
|
+
financial_title: {
|
|
765
|
+
title_type: 'receivable',
|
|
766
|
+
status: {
|
|
767
|
+
notIn: ['draft', 'canceled'],
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
select: {
|
|
772
|
+
competence_date: true,
|
|
773
|
+
amount_cents: true,
|
|
774
|
+
financial_title: {
|
|
775
|
+
select: {
|
|
776
|
+
person_id: true,
|
|
777
|
+
person: {
|
|
778
|
+
select: {
|
|
779
|
+
name: true,
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
finance_category: {
|
|
783
|
+
select: {
|
|
784
|
+
code: true,
|
|
785
|
+
name: true,
|
|
786
|
+
kind: true,
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
orderBy: {
|
|
793
|
+
competence_date: 'asc',
|
|
794
|
+
},
|
|
795
|
+
});
|
|
796
|
+
const byCustomer = new Map();
|
|
797
|
+
const byBucket = new Map();
|
|
798
|
+
for (const installment of installments) {
|
|
799
|
+
const category = installment.financial_title.finance_category;
|
|
800
|
+
if (this.isTransferReportCategory(category) ||
|
|
801
|
+
this.isLoanReportCategory(category) ||
|
|
802
|
+
this.isInvestorContributionReportCategory(category)) {
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
const customer = ((_a = installment.financial_title.person) === null || _a === void 0 ? void 0 : _a.name) ||
|
|
806
|
+
`Customer ${installment.financial_title.person_id}`;
|
|
807
|
+
const amount = this.fromCents(installment.amount_cents);
|
|
808
|
+
const current = byCustomer.get(customer) || { customer, value: 0 };
|
|
809
|
+
current.value += amount;
|
|
810
|
+
byCustomer.set(customer, current);
|
|
811
|
+
const period = this.getReportBucketKey(installment.competence_date, groupBy);
|
|
812
|
+
byBucket.set(period, (byBucket.get(period) || 0) + amount);
|
|
813
|
+
}
|
|
814
|
+
const filteredCustomers = Array.from(byCustomer.values())
|
|
815
|
+
.filter((item) => normalizedSearch.length === 0
|
|
816
|
+
? true
|
|
817
|
+
: this.normalizeReportText(item.customer).includes(normalizedSearch))
|
|
818
|
+
.sort((a, b) => b.value - a.value);
|
|
819
|
+
const topCustomers = filteredCustomers.slice(0, topN).map((item) => ({
|
|
820
|
+
customer: item.customer,
|
|
821
|
+
value: this.roundCurrency(item.value),
|
|
822
|
+
}));
|
|
823
|
+
const total = this.roundCurrency(topCustomers.reduce((acc, item) => acc + item.value, 0));
|
|
824
|
+
const top5 = this.roundCurrency(topCustomers.slice(0, 5).reduce((acc, item) => acc + item.value, 0));
|
|
825
|
+
const pieData = topCustomers.slice(0, 9).map((item) => ({
|
|
826
|
+
customer: item.customer,
|
|
827
|
+
value: item.value,
|
|
828
|
+
}));
|
|
829
|
+
const othersValue = this.roundCurrency(topCustomers.slice(9).reduce((acc, item) => acc + item.value, 0));
|
|
830
|
+
if (othersValue > 0) {
|
|
831
|
+
pieData.push({
|
|
832
|
+
customer: 'Outros',
|
|
833
|
+
value: othersValue,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
const groupedPeriods = Array.from(byBucket.entries())
|
|
837
|
+
.map(([period, value]) => ({
|
|
838
|
+
period,
|
|
839
|
+
value: this.roundCurrency(value),
|
|
840
|
+
}))
|
|
841
|
+
.sort((a, b) => this.sortReportBuckets(a.period, b.period, groupBy));
|
|
842
|
+
return {
|
|
843
|
+
total,
|
|
844
|
+
top5Percent: total > 0 ? this.roundCurrency((top5 / total) * 100) : 0,
|
|
845
|
+
topCustomers,
|
|
846
|
+
pieData,
|
|
847
|
+
groupedPeriods,
|
|
848
|
+
leader: topCustomers[0] || null,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
async getTopOperationalExpensesReport(filters) {
|
|
852
|
+
const { fromDate, toDate } = this.resolveReportDateRange(filters === null || filters === void 0 ? void 0 : filters.from, filters === null || filters === void 0 ? void 0 : filters.to);
|
|
853
|
+
const groupBy = this.resolveReportGroupBy(filters === null || filters === void 0 ? void 0 : filters.groupBy);
|
|
854
|
+
const topN = this.resolveTopN(filters === null || filters === void 0 ? void 0 : filters.topN);
|
|
855
|
+
const normalizedSearch = this.normalizeReportText((filters === null || filters === void 0 ? void 0 : filters.search) || '');
|
|
856
|
+
const installments = await this.prisma.financial_installment.findMany({
|
|
857
|
+
where: {
|
|
858
|
+
competence_date: {
|
|
859
|
+
gte: fromDate,
|
|
860
|
+
lte: toDate,
|
|
861
|
+
},
|
|
862
|
+
status: {
|
|
863
|
+
not: 'canceled',
|
|
864
|
+
},
|
|
865
|
+
financial_title: {
|
|
866
|
+
title_type: 'payable',
|
|
867
|
+
status: {
|
|
868
|
+
notIn: ['draft', 'canceled'],
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
select: {
|
|
873
|
+
competence_date: true,
|
|
874
|
+
amount_cents: true,
|
|
875
|
+
installment_allocation: {
|
|
876
|
+
select: {
|
|
877
|
+
allocated_amount_cents: true,
|
|
878
|
+
cost_center: {
|
|
879
|
+
select: {
|
|
880
|
+
name: true,
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
financial_title: {
|
|
886
|
+
select: {
|
|
887
|
+
finance_category: {
|
|
888
|
+
select: {
|
|
889
|
+
code: true,
|
|
890
|
+
name: true,
|
|
891
|
+
kind: true,
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
orderBy: {
|
|
898
|
+
competence_date: 'asc',
|
|
899
|
+
},
|
|
900
|
+
});
|
|
901
|
+
const byExpense = new Map();
|
|
902
|
+
const byCostCenter = new Map();
|
|
903
|
+
const byBucket = new Map();
|
|
904
|
+
for (const installment of installments) {
|
|
905
|
+
const category = installment.financial_title.finance_category;
|
|
906
|
+
if (this.isTransferReportCategory(category) ||
|
|
907
|
+
this.isLoanReportCategory(category)) {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const allocations = installment.installment_allocation.length > 0
|
|
911
|
+
? installment.installment_allocation.map((allocation) => {
|
|
912
|
+
var _a;
|
|
913
|
+
return ({
|
|
914
|
+
costCenter: ((_a = allocation.cost_center) === null || _a === void 0 ? void 0 : _a.name) || 'N/A',
|
|
915
|
+
amount: this.fromCents(allocation.allocated_amount_cents),
|
|
916
|
+
});
|
|
917
|
+
})
|
|
918
|
+
: [
|
|
919
|
+
{
|
|
920
|
+
costCenter: 'N/A',
|
|
921
|
+
amount: this.fromCents(installment.amount_cents),
|
|
922
|
+
},
|
|
923
|
+
];
|
|
924
|
+
const period = this.getReportBucketKey(installment.competence_date, groupBy);
|
|
925
|
+
for (const allocation of allocations) {
|
|
926
|
+
const categoryName = (category === null || category === void 0 ? void 0 : category.name) || 'Sem categoria';
|
|
927
|
+
const key = `${categoryName}::${allocation.costCenter}`;
|
|
928
|
+
const current = byExpense.get(key) || {
|
|
929
|
+
category: categoryName,
|
|
930
|
+
costCenter: allocation.costCenter,
|
|
931
|
+
label: `${categoryName} - ${allocation.costCenter}`,
|
|
932
|
+
value: 0,
|
|
933
|
+
};
|
|
934
|
+
current.value += allocation.amount;
|
|
935
|
+
byExpense.set(key, current);
|
|
936
|
+
byCostCenter.set(allocation.costCenter, (byCostCenter.get(allocation.costCenter) || 0) + allocation.amount);
|
|
937
|
+
byBucket.set(period, (byBucket.get(period) || 0) + allocation.amount);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const filteredExpenses = Array.from(byExpense.values())
|
|
941
|
+
.filter((item) => normalizedSearch.length === 0
|
|
942
|
+
? true
|
|
943
|
+
: this.normalizeReportText(`${item.category} ${item.costCenter}`).includes(normalizedSearch))
|
|
944
|
+
.sort((a, b) => b.value - a.value);
|
|
945
|
+
const topExpenses = filteredExpenses.slice(0, topN).map((item) => ({
|
|
946
|
+
category: item.category,
|
|
947
|
+
costCenter: item.costCenter,
|
|
948
|
+
label: item.label,
|
|
949
|
+
value: this.roundCurrency(item.value),
|
|
950
|
+
}));
|
|
951
|
+
const total = this.roundCurrency(topExpenses.reduce((acc, item) => acc + item.value, 0));
|
|
952
|
+
const pieByCostCenter = new Map();
|
|
953
|
+
for (const item of topExpenses) {
|
|
954
|
+
pieByCostCenter.set(item.costCenter, (pieByCostCenter.get(item.costCenter) || 0) + item.value);
|
|
955
|
+
}
|
|
956
|
+
const pieData = Array.from(pieByCostCenter.entries())
|
|
957
|
+
.map(([name, value]) => ({
|
|
958
|
+
name,
|
|
959
|
+
value: this.roundCurrency(value),
|
|
960
|
+
}))
|
|
961
|
+
.sort((a, b) => b.value - a.value);
|
|
962
|
+
const groupedPeriods = Array.from(byBucket.entries())
|
|
963
|
+
.map(([period, value]) => ({
|
|
964
|
+
period,
|
|
965
|
+
value: this.roundCurrency(value),
|
|
966
|
+
}))
|
|
967
|
+
.sort((a, b) => this.sortReportBuckets(a.period, b.period, groupBy));
|
|
968
|
+
return {
|
|
969
|
+
total,
|
|
970
|
+
average: topExpenses.length > 0
|
|
971
|
+
? this.roundCurrency(total / topExpenses.length)
|
|
972
|
+
: 0,
|
|
973
|
+
topExpenses,
|
|
974
|
+
pieData,
|
|
975
|
+
groupedPeriods,
|
|
976
|
+
highest: topExpenses[0] || null,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
630
979
|
async updateScenarioSettings(scenario, data) {
|
|
631
980
|
const scenarioSlug = this.resolveForecastScenarioStrict(scenario);
|
|
632
981
|
if (data.atrasoMedio < 0 || data.atrasoMedio > 365) {
|
|
@@ -4361,6 +4710,113 @@ let FinanceService = FinanceService_1 = class FinanceService {
|
|
|
4361
4710
|
currentParentId = (parent === null || parent === void 0 ? void 0 : parent.parent_id) || null;
|
|
4362
4711
|
}
|
|
4363
4712
|
}
|
|
4713
|
+
resolveReportDateRange(from, to) {
|
|
4714
|
+
const fromDate = from
|
|
4715
|
+
? new Date(`${from}T00:00:00.000`)
|
|
4716
|
+
: new Date('2021-01-01T00:00:00.000');
|
|
4717
|
+
const toDate = to
|
|
4718
|
+
? new Date(`${to}T23:59:59.999`)
|
|
4719
|
+
: new Date('2026-12-31T23:59:59.999');
|
|
4720
|
+
if (Number.isNaN(fromDate.getTime()) ||
|
|
4721
|
+
Number.isNaN(toDate.getTime()) ||
|
|
4722
|
+
fromDate > toDate) {
|
|
4723
|
+
throw new common_1.BadRequestException('Invalid report date range');
|
|
4724
|
+
}
|
|
4725
|
+
return { fromDate, toDate };
|
|
4726
|
+
}
|
|
4727
|
+
resolveReportGroupBy(groupBy) {
|
|
4728
|
+
if (groupBy === 'day' || groupBy === 'week' || groupBy === 'month') {
|
|
4729
|
+
return groupBy;
|
|
4730
|
+
}
|
|
4731
|
+
return 'year';
|
|
4732
|
+
}
|
|
4733
|
+
resolveTopN(topN) {
|
|
4734
|
+
if (!topN || !Number.isFinite(topN)) {
|
|
4735
|
+
return 20;
|
|
4736
|
+
}
|
|
4737
|
+
return Math.max(1, Math.min(100, Math.trunc(topN)));
|
|
4738
|
+
}
|
|
4739
|
+
getReportBucketKey(date, groupBy) {
|
|
4740
|
+
const baseDate = this.startOfDay(date);
|
|
4741
|
+
if (groupBy === 'day') {
|
|
4742
|
+
return baseDate.toISOString().slice(0, 10);
|
|
4743
|
+
}
|
|
4744
|
+
if (groupBy === 'week') {
|
|
4745
|
+
const weekStart = this.startOfWeek(baseDate);
|
|
4746
|
+
const isoWeek = this.getIsoWeek(weekStart);
|
|
4747
|
+
return `${weekStart.getFullYear()}-W${String(isoWeek).padStart(2, '0')}`;
|
|
4748
|
+
}
|
|
4749
|
+
if (groupBy === 'month') {
|
|
4750
|
+
return `${baseDate.getFullYear()}-${String(baseDate.getMonth() + 1).padStart(2, '0')}`;
|
|
4751
|
+
}
|
|
4752
|
+
return String(baseDate.getFullYear());
|
|
4753
|
+
}
|
|
4754
|
+
sortReportBuckets(a, b, groupBy) {
|
|
4755
|
+
if (groupBy === 'year' || groupBy === 'month' || groupBy === 'day') {
|
|
4756
|
+
return a.localeCompare(b);
|
|
4757
|
+
}
|
|
4758
|
+
const [aYear, aWeek] = a.split('-W');
|
|
4759
|
+
const [bYear, bWeek] = b.split('-W');
|
|
4760
|
+
const yearDiff = Number(aYear) - Number(bYear);
|
|
4761
|
+
if (yearDiff !== 0) {
|
|
4762
|
+
return yearDiff;
|
|
4763
|
+
}
|
|
4764
|
+
return Number(aWeek) - Number(bWeek);
|
|
4765
|
+
}
|
|
4766
|
+
startOfWeek(date) {
|
|
4767
|
+
const current = new Date(date);
|
|
4768
|
+
const day = current.getDay();
|
|
4769
|
+
const diff = day === 0 ? -6 : 1 - day;
|
|
4770
|
+
current.setDate(current.getDate() + diff);
|
|
4771
|
+
current.setHours(0, 0, 0, 0);
|
|
4772
|
+
return current;
|
|
4773
|
+
}
|
|
4774
|
+
getIsoWeek(date) {
|
|
4775
|
+
const current = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
4776
|
+
const dayNum = current.getUTCDay() || 7;
|
|
4777
|
+
current.setUTCDate(current.getUTCDate() + 4 - dayNum);
|
|
4778
|
+
const yearStart = new Date(Date.UTC(current.getUTCFullYear(), 0, 1));
|
|
4779
|
+
return Math.ceil(((current.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
|
4780
|
+
}
|
|
4781
|
+
normalizeReportText(value) {
|
|
4782
|
+
return value
|
|
4783
|
+
.normalize('NFD')
|
|
4784
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
4785
|
+
.toLowerCase()
|
|
4786
|
+
.trim();
|
|
4787
|
+
}
|
|
4788
|
+
isTransferReportCategory(category) {
|
|
4789
|
+
if (!category) {
|
|
4790
|
+
return false;
|
|
4791
|
+
}
|
|
4792
|
+
if (category.kind === 'transfer') {
|
|
4793
|
+
return true;
|
|
4794
|
+
}
|
|
4795
|
+
const haystack = this.normalizeReportText(`${category.code || ''} ${category.name || ''}`);
|
|
4796
|
+
return haystack.includes('transfer');
|
|
4797
|
+
}
|
|
4798
|
+
isLoanReportCategory(category) {
|
|
4799
|
+
if (!category) {
|
|
4800
|
+
return false;
|
|
4801
|
+
}
|
|
4802
|
+
const haystack = this.normalizeReportText(`${category.code || ''} ${category.name || ''}`);
|
|
4803
|
+
return (haystack.includes('emprestimo') ||
|
|
4804
|
+
haystack.includes('financiamento') ||
|
|
4805
|
+
haystack.includes('loan'));
|
|
4806
|
+
}
|
|
4807
|
+
isInvestorContributionReportCategory(category) {
|
|
4808
|
+
if (!category) {
|
|
4809
|
+
return false;
|
|
4810
|
+
}
|
|
4811
|
+
const haystack = this.normalizeReportText(`${category.code || ''} ${category.name || ''}`);
|
|
4812
|
+
return (haystack.includes('aporte') ||
|
|
4813
|
+
haystack.includes('investidor') ||
|
|
4814
|
+
haystack.includes('capital') ||
|
|
4815
|
+
haystack.includes('socio'));
|
|
4816
|
+
}
|
|
4817
|
+
roundCurrency(value) {
|
|
4818
|
+
return Number(value.toFixed(2));
|
|
4819
|
+
}
|
|
4364
4820
|
toCents(value) {
|
|
4365
4821
|
return Math.round(value * 100);
|
|
4366
4822
|
}
|