@hed-hog/finance 0.0.318 → 0.0.321
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/dist/dto/create-bank-account.dto.d.ts +1 -0
- package/dist/dto/create-bank-account.dto.d.ts.map +1 -1
- package/dist/dto/create-bank-account.dto.js +7 -0
- package/dist/dto/create-bank-account.dto.js.map +1 -1
- package/dist/dto/create-bank-statement-entry.dto.d.ts +8 -0
- package/dist/dto/create-bank-statement-entry.dto.d.ts.map +1 -0
- package/dist/dto/create-bank-statement-entry.dto.js +54 -0
- package/dist/dto/create-bank-statement-entry.dto.js.map +1 -0
- package/dist/dto/create-currency.dto.d.ts +6 -0
- package/dist/dto/create-currency.dto.d.ts.map +1 -0
- package/dist/dto/create-currency.dto.js +37 -0
- package/dist/dto/create-currency.dto.js.map +1 -0
- package/dist/dto/update-bank-account.dto.d.ts +1 -0
- package/dist/dto/update-bank-account.dto.d.ts.map +1 -1
- package/dist/dto/update-bank-account.dto.js +7 -0
- package/dist/dto/update-bank-account.dto.js.map +1 -1
- package/dist/dto/update-bank-statement-entry.dto.d.ts +6 -0
- package/dist/dto/update-bank-statement-entry.dto.d.ts.map +1 -0
- package/dist/dto/update-bank-statement-entry.dto.js +42 -0
- package/dist/dto/update-bank-statement-entry.dto.js.map +1 -0
- package/dist/dto/update-currency.dto.d.ts +7 -0
- package/dist/dto/update-currency.dto.d.ts.map +1 -0
- package/dist/dto/update-currency.dto.js +47 -0
- package/dist/dto/update-currency.dto.js.map +1 -0
- package/dist/finance-bank-accounts.controller.d.ts +25 -13
- package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
- package/dist/finance-bank-accounts.controller.js +5 -3
- package/dist/finance-bank-accounts.controller.js.map +1 -1
- package/dist/finance-currencies.controller.d.ts +36 -0
- package/dist/finance-currencies.controller.d.ts.map +1 -0
- package/dist/finance-currencies.controller.js +74 -0
- package/dist/finance-currencies.controller.js.map +1 -0
- package/dist/finance-data.controller.d.ts +4 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.d.ts +3 -2
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.js +10 -6
- package/dist/finance-installments.controller.js.map +1 -1
- package/dist/finance-statements.controller.d.ts +61 -12
- package/dist/finance-statements.controller.d.ts.map +1 -1
- package/dist/finance-statements.controller.js +50 -8
- package/dist/finance-statements.controller.js.map +1 -1
- package/dist/finance-transfers.controller.d.ts +13 -8
- package/dist/finance-transfers.controller.d.ts.map +1 -1
- package/dist/finance-transfers.controller.js +11 -5
- package/dist/finance-transfers.controller.js.map +1 -1
- 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 +153 -35
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +468 -55
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/currency.yaml +14 -0
- package/hedhog/data/menu.yaml +16 -0
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/data/route.yaml +78 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +87 -72
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +53 -25
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +8 -0
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +60 -24
- package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +490 -0
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +243 -65
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +25 -3
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +732 -61
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +101 -15
- package/hedhog/frontend/messages/en.json +58 -0
- package/hedhog/frontend/messages/pt.json +58 -0
- package/hedhog/table/bank_account.yaml +8 -0
- package/hedhog/table/bank_statement_line.yaml +1 -1
- package/hedhog/table/cashflow_projection.yaml +1 -1
- package/hedhog/table/currency.yaml +21 -0
- package/hedhog/table/financial_installment.yaml +2 -2
- package/hedhog/table/financial_title.yaml +1 -1
- package/hedhog/table/installment_allocation.yaml +1 -1
- package/hedhog/table/receivable_schedule.yaml +1 -1
- package/hedhog/table/settlement.yaml +1 -1
- package/hedhog/table/settlement_allocation.yaml +5 -5
- package/package.json +6 -6
- package/src/dto/create-bank-account.dto.ts +18 -1
- package/src/dto/create-bank-statement-entry.dto.ts +50 -0
- package/src/dto/create-currency.dto.ts +21 -0
- package/src/dto/update-bank-account.dto.ts +11 -1
- package/src/dto/update-bank-statement-entry.dto.ts +31 -0
- package/src/dto/update-currency.dto.ts +31 -0
- package/src/finance-bank-accounts.controller.ts +3 -2
- package/src/finance-currencies.controller.ts +44 -0
- package/src/finance-installments.controller.ts +9 -3
- package/src/finance-statements.controller.ts +40 -0
- package/src/finance-transfers.controller.ts +7 -1
- package/src/finance.module.ts +2 -0
- package/src/finance.service.ts +633 -55
package/src/finance.service.ts
CHANGED
|
@@ -20,7 +20,9 @@ import { readFile } from 'node:fs/promises';
|
|
|
20
20
|
import { CreateBankAccountDto } from './dto/create-bank-account.dto';
|
|
21
21
|
import { CreateBankReconciliationDto } from './dto/create-bank-reconciliation.dto';
|
|
22
22
|
import { CreateBankStatementAdjustmentDto } from './dto/create-bank-statement-adjustment.dto';
|
|
23
|
+
import { CreateBankStatementEntryDto } from './dto/create-bank-statement-entry.dto';
|
|
23
24
|
import { CreateCostCenterDto } from './dto/create-cost-center.dto';
|
|
25
|
+
import { CreateCurrencyDto } from './dto/create-currency.dto';
|
|
24
26
|
import { CreateFinanceCategoryDto } from './dto/create-finance-category.dto';
|
|
25
27
|
import { CreateFinanceTagDto } from './dto/create-finance-tag.dto';
|
|
26
28
|
import { CreateFinancialTitleDto } from './dto/create-financial-title.dto';
|
|
@@ -33,7 +35,9 @@ import { ReverseSettlementDto } from './dto/reverse-settlement.dto';
|
|
|
33
35
|
import { SendCollectionDto } from './dto/send-collection.dto';
|
|
34
36
|
import { SettleInstallmentDto } from './dto/settle-installment.dto';
|
|
35
37
|
import { UpdateBankAccountDto } from './dto/update-bank-account.dto';
|
|
38
|
+
import { UpdateBankStatementEntryDto } from './dto/update-bank-statement-entry.dto';
|
|
36
39
|
import { UpdateCostCenterDto } from './dto/update-cost-center.dto';
|
|
40
|
+
import { UpdateCurrencyDto } from './dto/update-currency.dto';
|
|
37
41
|
import { UpdateFinanceCategoryDto } from './dto/update-finance-category.dto';
|
|
38
42
|
|
|
39
43
|
type TitleType = 'payable' | 'receivable';
|
|
@@ -654,6 +658,28 @@ export class FinanceService {
|
|
|
654
658
|
return dt.toISOString().slice(0, 10);
|
|
655
659
|
}
|
|
656
660
|
|
|
661
|
+
private parseFilterDate(value?: string, endOfDay = false): Date | undefined {
|
|
662
|
+
if (!value) {
|
|
663
|
+
return undefined;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const raw = String(value).trim();
|
|
667
|
+
if (!raw) {
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const normalized = /^\d{4}-\d{2}-\d{2}$/.test(raw)
|
|
672
|
+
? `${raw}T${endOfDay ? '23:59:59.999' : '00:00:00.000'}`
|
|
673
|
+
: raw;
|
|
674
|
+
const parsed = new Date(normalized);
|
|
675
|
+
|
|
676
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return parsed;
|
|
681
|
+
}
|
|
682
|
+
|
|
657
683
|
private normalizeMonth(value: any): string {
|
|
658
684
|
if (!value) return '';
|
|
659
685
|
const raw = String(value).trim();
|
|
@@ -695,6 +721,14 @@ export class FinanceService {
|
|
|
695
721
|
card: 'cartao',
|
|
696
722
|
credito: 'cartao',
|
|
697
723
|
debito: 'cartao',
|
|
724
|
+
'debito automatico': 'debito_automatico',
|
|
725
|
+
debito_automatico: 'debito_automatico',
|
|
726
|
+
'debito-automatico': 'debito_automatico',
|
|
727
|
+
'débito automático': 'debito_automatico',
|
|
728
|
+
'debito em conta': 'debito_em_conta',
|
|
729
|
+
debito_em_conta: 'debito_em_conta',
|
|
730
|
+
'debito-em-conta': 'debito_em_conta',
|
|
731
|
+
'débito em conta': 'debito_em_conta',
|
|
698
732
|
dinheiro: 'dinheiro',
|
|
699
733
|
cash: 'dinheiro',
|
|
700
734
|
cheque: 'cheque',
|
|
@@ -774,7 +808,14 @@ export class FinanceService {
|
|
|
774
808
|
this.loadCategories(),
|
|
775
809
|
this.loadCostCenters(),
|
|
776
810
|
this.loadBankAccounts(),
|
|
777
|
-
this.listBankStatements(
|
|
811
|
+
this.listBankStatements({
|
|
812
|
+
page: 1,
|
|
813
|
+
pageSize: 1000,
|
|
814
|
+
search: undefined,
|
|
815
|
+
sortField: undefined,
|
|
816
|
+
sortOrder: undefined,
|
|
817
|
+
fields: undefined,
|
|
818
|
+
}),
|
|
778
819
|
this.getAccountsReceivableCollectionsDefault(),
|
|
779
820
|
this.loadTags(),
|
|
780
821
|
this.loadAuditLogs(),
|
|
@@ -795,7 +836,7 @@ export class FinanceService {
|
|
|
795
836
|
bankAccountsResult.status === 'fulfilled' ? bankAccountsResult.value : [];
|
|
796
837
|
const bankStatements =
|
|
797
838
|
bankStatementsResult.status === 'fulfilled'
|
|
798
|
-
? bankStatementsResult.value
|
|
839
|
+
? bankStatementsResult.value.data
|
|
799
840
|
: [];
|
|
800
841
|
const collectionsDefault =
|
|
801
842
|
collectionsDefaultResult.status === 'fulfilled'
|
|
@@ -2114,7 +2155,7 @@ export class FinanceService {
|
|
|
2114
2155
|
throw new NotFoundException('Person not found');
|
|
2115
2156
|
}
|
|
2116
2157
|
|
|
2117
|
-
const firstDueDate =
|
|
2158
|
+
const firstDueDate = this.parseLocalDate(data.first_due_date);
|
|
2118
2159
|
|
|
2119
2160
|
if (Number.isNaN(firstDueDate.getTime())) {
|
|
2120
2161
|
throw new BadRequestException('Invalid first due date');
|
|
@@ -2333,15 +2374,23 @@ export class FinanceService {
|
|
|
2333
2374
|
async listAccountsPayableInstallments(
|
|
2334
2375
|
paginationParams: PaginationDTO,
|
|
2335
2376
|
status?: string,
|
|
2377
|
+
filters?: {
|
|
2378
|
+
from?: string;
|
|
2379
|
+
to?: string;
|
|
2380
|
+
},
|
|
2336
2381
|
) {
|
|
2337
|
-
return this.listTitles('payable', paginationParams, status);
|
|
2382
|
+
return this.listTitles('payable', paginationParams, status, filters);
|
|
2338
2383
|
}
|
|
2339
2384
|
|
|
2340
2385
|
async listAccountsReceivableInstallments(
|
|
2341
2386
|
paginationParams: PaginationDTO,
|
|
2342
2387
|
status?: string,
|
|
2388
|
+
filters?: {
|
|
2389
|
+
from?: string;
|
|
2390
|
+
to?: string;
|
|
2391
|
+
},
|
|
2343
2392
|
) {
|
|
2344
|
-
return this.listTitles('receivable', paginationParams, status);
|
|
2393
|
+
return this.listTitles('receivable', paginationParams, status, filters);
|
|
2345
2394
|
}
|
|
2346
2395
|
|
|
2347
2396
|
async getAccountsPayableInstallment(id: number, locale: string) {
|
|
@@ -2727,27 +2776,49 @@ export class FinanceService {
|
|
|
2727
2776
|
return this.updateTitleTags(id, 'receivable', tagIds, locale);
|
|
2728
2777
|
}
|
|
2729
2778
|
|
|
2730
|
-
async listBankAccounts() {
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2779
|
+
async listBankAccounts(paginationParams?: PaginationDTO) {
|
|
2780
|
+
const paginated = await this.paginationService.paginate(
|
|
2781
|
+
this.prisma.bank_account,
|
|
2782
|
+
{
|
|
2783
|
+
...paginationParams,
|
|
2784
|
+
sortField: paginationParams?.sortField || 'code',
|
|
2785
|
+
sortOrder: paginationParams?.sortOrder || PageOrderDirection.Asc,
|
|
2786
|
+
},
|
|
2787
|
+
{
|
|
2788
|
+
include: {
|
|
2789
|
+
bank_statement_line: {
|
|
2790
|
+
select: {
|
|
2791
|
+
amount_cents: true,
|
|
2792
|
+
status: true,
|
|
2793
|
+
posted_date: true,
|
|
2794
|
+
description: true,
|
|
2795
|
+
},
|
|
2737
2796
|
},
|
|
2738
2797
|
},
|
|
2798
|
+
orderBy: [{ code: 'asc' }, { name: 'asc' }],
|
|
2739
2799
|
},
|
|
2740
|
-
|
|
2741
|
-
});
|
|
2800
|
+
);
|
|
2742
2801
|
|
|
2743
|
-
return
|
|
2802
|
+
return {
|
|
2803
|
+
...paginated,
|
|
2804
|
+
data: (paginated.data || []).map((bankAccount) =>
|
|
2805
|
+
this.mapBankAccountToFront(bankAccount),
|
|
2806
|
+
),
|
|
2807
|
+
};
|
|
2744
2808
|
}
|
|
2745
2809
|
|
|
2746
|
-
async listTransfers(
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2810
|
+
async listTransfers(
|
|
2811
|
+
paginationParams: PaginationDTO,
|
|
2812
|
+
filters?: {
|
|
2813
|
+
search?: string;
|
|
2814
|
+
bank_account_id?: string;
|
|
2815
|
+
from?: string;
|
|
2816
|
+
to?: string;
|
|
2817
|
+
},
|
|
2818
|
+
) {
|
|
2750
2819
|
const search = filters?.search?.trim();
|
|
2820
|
+
const fromDate = this.parseFilterDate(filters?.from);
|
|
2821
|
+
const toDate = this.parseFilterDate(filters?.to, true);
|
|
2751
2822
|
const parsedBankAccountId = filters?.bank_account_id
|
|
2752
2823
|
? Number.parseInt(filters.bank_account_id, 10)
|
|
2753
2824
|
: undefined;
|
|
@@ -2773,6 +2844,14 @@ export class FinanceService {
|
|
|
2773
2844
|
}
|
|
2774
2845
|
: {}),
|
|
2775
2846
|
...(bankAccountId ? { bank_account_id: bankAccountId } : {}),
|
|
2847
|
+
...((fromDate || toDate)
|
|
2848
|
+
? {
|
|
2849
|
+
posted_date: {
|
|
2850
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
2851
|
+
...(toDate ? { lte: toDate } : {}),
|
|
2852
|
+
},
|
|
2853
|
+
}
|
|
2854
|
+
: {}),
|
|
2776
2855
|
},
|
|
2777
2856
|
select: {
|
|
2778
2857
|
external_id: true,
|
|
@@ -2788,7 +2867,12 @@ export class FinanceService {
|
|
|
2788
2867
|
);
|
|
2789
2868
|
|
|
2790
2869
|
if (transferKeys.length === 0) {
|
|
2791
|
-
return
|
|
2870
|
+
return {
|
|
2871
|
+
...this.paginateCollection([], paginationParams),
|
|
2872
|
+
summary: {
|
|
2873
|
+
totalTransferido: 0,
|
|
2874
|
+
},
|
|
2875
|
+
};
|
|
2792
2876
|
}
|
|
2793
2877
|
}
|
|
2794
2878
|
|
|
@@ -2805,6 +2889,14 @@ export class FinanceService {
|
|
|
2805
2889
|
startsWith: 'transfer:',
|
|
2806
2890
|
},
|
|
2807
2891
|
}),
|
|
2892
|
+
...((fromDate || toDate)
|
|
2893
|
+
? {
|
|
2894
|
+
posted_date: {
|
|
2895
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
2896
|
+
...(toDate ? { lte: toDate } : {}),
|
|
2897
|
+
},
|
|
2898
|
+
}
|
|
2899
|
+
: {}),
|
|
2808
2900
|
},
|
|
2809
2901
|
select: {
|
|
2810
2902
|
id: true,
|
|
@@ -2844,20 +2936,28 @@ export class FinanceService {
|
|
|
2844
2936
|
contaOrigemId: String(sourceLine.bank_account_id),
|
|
2845
2937
|
contaDestinoId: String(destinationLine.bank_account_id),
|
|
2846
2938
|
data: sourceLine.posted_date.toISOString(),
|
|
2847
|
-
valor: this.fromCents(
|
|
2939
|
+
valor: Math.abs(this.fromCents(sourceLine.amount_cents)),
|
|
2848
2940
|
descricao: sourceLine.description || destinationLine.description || '',
|
|
2849
2941
|
};
|
|
2850
2942
|
})
|
|
2851
2943
|
.filter(Boolean);
|
|
2852
2944
|
|
|
2853
|
-
return
|
|
2945
|
+
return {
|
|
2946
|
+
...this.paginateCollection(transfers, paginationParams),
|
|
2947
|
+
summary: {
|
|
2948
|
+
totalTransferido: transfers.reduce(
|
|
2949
|
+
(acc, transfer) => acc + Number(transfer?.valor || 0),
|
|
2950
|
+
0,
|
|
2951
|
+
),
|
|
2952
|
+
},
|
|
2953
|
+
};
|
|
2854
2954
|
}
|
|
2855
2955
|
|
|
2856
2956
|
async createTransfer(data: CreateTransferDto, userId?: number) {
|
|
2857
2957
|
const sourceAccountId = Number(data.source_account_id);
|
|
2858
2958
|
const destinationAccountId = Number(data.destination_account_id);
|
|
2859
2959
|
const amount = Number(data.amount);
|
|
2860
|
-
const postedDate =
|
|
2960
|
+
const postedDate = this.parseLocalDate(data.date);
|
|
2861
2961
|
|
|
2862
2962
|
if (
|
|
2863
2963
|
Number.isNaN(sourceAccountId) ||
|
|
@@ -3182,8 +3282,18 @@ export class FinanceService {
|
|
|
3182
3282
|
};
|
|
3183
3283
|
}
|
|
3184
3284
|
|
|
3185
|
-
async listBankStatements(
|
|
3285
|
+
async listBankStatements(
|
|
3286
|
+
paginationParams: PaginationDTO,
|
|
3287
|
+
bankAccountId?: number,
|
|
3288
|
+
search?: string,
|
|
3289
|
+
filters?: {
|
|
3290
|
+
from?: string;
|
|
3291
|
+
to?: string;
|
|
3292
|
+
},
|
|
3293
|
+
) {
|
|
3186
3294
|
const trimmedSearch = search?.trim();
|
|
3295
|
+
const fromDate = this.parseFilterDate(filters?.from);
|
|
3296
|
+
const toDate = this.parseFilterDate(filters?.to, true);
|
|
3187
3297
|
|
|
3188
3298
|
const statements = await this.prisma.bank_statement_line.findMany({
|
|
3189
3299
|
where: {
|
|
@@ -3196,6 +3306,14 @@ export class FinanceService {
|
|
|
3196
3306
|
},
|
|
3197
3307
|
}
|
|
3198
3308
|
: {}),
|
|
3309
|
+
...((fromDate || toDate)
|
|
3310
|
+
? {
|
|
3311
|
+
posted_date: {
|
|
3312
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
3313
|
+
...(toDate ? { lte: toDate } : {}),
|
|
3314
|
+
},
|
|
3315
|
+
}
|
|
3316
|
+
: {}),
|
|
3199
3317
|
},
|
|
3200
3318
|
include: {
|
|
3201
3319
|
bank_account: {
|
|
@@ -3222,7 +3340,7 @@ export class FinanceService {
|
|
|
3222
3340
|
orderBy: [{ posted_date: 'desc' }, { id: 'desc' }],
|
|
3223
3341
|
});
|
|
3224
3342
|
|
|
3225
|
-
|
|
3343
|
+
const mappedStatements = statements.map((statement) => ({
|
|
3226
3344
|
id: String(statement.id),
|
|
3227
3345
|
contaBancariaId: String(statement.bank_account_id),
|
|
3228
3346
|
data: statement.posted_date.toISOString(),
|
|
@@ -3230,6 +3348,13 @@ export class FinanceService {
|
|
|
3230
3348
|
valor: this.fromCents(statement.amount_cents),
|
|
3231
3349
|
tipo: statement.amount_cents >= 0 ? 'entrada' : 'saida',
|
|
3232
3350
|
statusConciliacao: this.mapStatementStatusToPt(statement.status),
|
|
3351
|
+
isTransfer: statement.external_id?.startsWith('transfer:') || false,
|
|
3352
|
+
canEdit:
|
|
3353
|
+
!statement.external_id?.startsWith('transfer:') &&
|
|
3354
|
+
statement.bank_reconciliation.length === 0,
|
|
3355
|
+
canDelete:
|
|
3356
|
+
!statement.external_id?.startsWith('transfer:') &&
|
|
3357
|
+
statement.bank_reconciliation.length === 0,
|
|
3233
3358
|
reconciliationId: statement.bank_reconciliation[0]?.id
|
|
3234
3359
|
? String(statement.bank_reconciliation[0].id)
|
|
3235
3360
|
: null,
|
|
@@ -3237,6 +3362,18 @@ export class FinanceService {
|
|
|
3237
3362
|
? String(statement.bank_reconciliation[0].settlement_id)
|
|
3238
3363
|
: null,
|
|
3239
3364
|
}));
|
|
3365
|
+
|
|
3366
|
+
return {
|
|
3367
|
+
...this.paginateCollection(mappedStatements, paginationParams),
|
|
3368
|
+
summary: {
|
|
3369
|
+
totalEntradas: mappedStatements
|
|
3370
|
+
.filter((statement) => statement.tipo === 'entrada')
|
|
3371
|
+
.reduce((acc, statement) => acc + Number(statement.valor || 0), 0),
|
|
3372
|
+
totalSaidas: mappedStatements
|
|
3373
|
+
.filter((statement) => statement.tipo === 'saida')
|
|
3374
|
+
.reduce((acc, statement) => acc + Number(statement.valor || 0), 0),
|
|
3375
|
+
},
|
|
3376
|
+
};
|
|
3240
3377
|
}
|
|
3241
3378
|
|
|
3242
3379
|
async createBankReconciliation(
|
|
@@ -3322,7 +3459,7 @@ export class FinanceService {
|
|
|
3322
3459
|
title.id,
|
|
3323
3460
|
{
|
|
3324
3461
|
installment_id: installment.id,
|
|
3325
|
-
amount: this.fromCents(
|
|
3462
|
+
amount: Math.abs(this.fromCents(statementLine.amount_cents)),
|
|
3326
3463
|
settled_at: statementLine.posted_date.toISOString(),
|
|
3327
3464
|
bank_account_id: statementLine.bank_account_id,
|
|
3328
3465
|
bank_statement_line_id: statementLine.id,
|
|
@@ -3379,7 +3516,7 @@ export class FinanceService {
|
|
|
3379
3516
|
const receivableAmounts = new Set<number>();
|
|
3380
3517
|
|
|
3381
3518
|
for (const installment of openInstallments) {
|
|
3382
|
-
const amount =
|
|
3519
|
+
const amount = Number(installment.open_amount_cents);
|
|
3383
3520
|
if (installment.financial_title.title_type === 'payable') {
|
|
3384
3521
|
payableAmounts.add(amount);
|
|
3385
3522
|
} else if (installment.financial_title.title_type === 'receivable') {
|
|
@@ -3391,8 +3528,8 @@ export class FinanceService {
|
|
|
3391
3528
|
let differenceCents = 0;
|
|
3392
3529
|
|
|
3393
3530
|
for (const statement of pendingStatements) {
|
|
3394
|
-
const normalizedAmount = Math.abs(statement.amount_cents);
|
|
3395
|
-
differenceCents += statement.amount_cents;
|
|
3531
|
+
const normalizedAmount = Math.abs(Number(statement.amount_cents));
|
|
3532
|
+
differenceCents += Number(statement.amount_cents);
|
|
3396
3533
|
|
|
3397
3534
|
const hasPossibleMatch =
|
|
3398
3535
|
statement.amount_cents < 0
|
|
@@ -3410,8 +3547,28 @@ export class FinanceService {
|
|
|
3410
3547
|
};
|
|
3411
3548
|
}
|
|
3412
3549
|
|
|
3413
|
-
async exportBankStatementsCsv(
|
|
3414
|
-
|
|
3550
|
+
async exportBankStatementsCsv(
|
|
3551
|
+
bankAccountId: number,
|
|
3552
|
+
search?: string,
|
|
3553
|
+
filters?: {
|
|
3554
|
+
from?: string;
|
|
3555
|
+
to?: string;
|
|
3556
|
+
},
|
|
3557
|
+
) {
|
|
3558
|
+
const statementsResult = await this.listBankStatements(
|
|
3559
|
+
{
|
|
3560
|
+
page: 1,
|
|
3561
|
+
pageSize: 100000,
|
|
3562
|
+
search: undefined,
|
|
3563
|
+
sortField: undefined,
|
|
3564
|
+
sortOrder: undefined,
|
|
3565
|
+
fields: undefined,
|
|
3566
|
+
},
|
|
3567
|
+
bankAccountId,
|
|
3568
|
+
search,
|
|
3569
|
+
filters,
|
|
3570
|
+
);
|
|
3571
|
+
const statements = statementsResult.data;
|
|
3415
3572
|
|
|
3416
3573
|
const headers = [
|
|
3417
3574
|
'id',
|
|
@@ -3454,7 +3611,7 @@ export class FinanceService {
|
|
|
3454
3611
|
) {
|
|
3455
3612
|
const bankAccountId = Number(data.bank_account_id);
|
|
3456
3613
|
const amount = Number(data.amount);
|
|
3457
|
-
const postedAt = data.date ?
|
|
3614
|
+
const postedAt = data.date ? this.parseLocalDate(data.date) : new Date();
|
|
3458
3615
|
|
|
3459
3616
|
if (Number.isNaN(bankAccountId) || bankAccountId <= 0) {
|
|
3460
3617
|
throw new BadRequestException('bank_account_id is required');
|
|
@@ -3527,6 +3684,264 @@ export class FinanceService {
|
|
|
3527
3684
|
};
|
|
3528
3685
|
}
|
|
3529
3686
|
|
|
3687
|
+
async createBankStatementEntry(
|
|
3688
|
+
data: CreateBankStatementEntryDto,
|
|
3689
|
+
userId?: number,
|
|
3690
|
+
) {
|
|
3691
|
+
const bankAccountId = Number(data.bank_account_id);
|
|
3692
|
+
const amount = Number(data.amount);
|
|
3693
|
+
const postedDate = new Date(`${data.date}T00:00:00`);
|
|
3694
|
+
|
|
3695
|
+
if (Number.isNaN(bankAccountId) || bankAccountId <= 0) {
|
|
3696
|
+
throw new BadRequestException('bank_account_id is required');
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
if (Number.isNaN(amount) || amount <= 0) {
|
|
3700
|
+
throw new BadRequestException('amount must be greater than zero');
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
if (Number.isNaN(postedDate.getTime())) {
|
|
3704
|
+
throw new BadRequestException('Invalid statement date');
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
const description = data.description?.trim();
|
|
3708
|
+
if (!description) {
|
|
3709
|
+
throw new BadRequestException('description is required');
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
const bankAccount = await this.prisma.bank_account.findUnique({
|
|
3713
|
+
where: { id: bankAccountId },
|
|
3714
|
+
select: { id: true },
|
|
3715
|
+
});
|
|
3716
|
+
|
|
3717
|
+
if (!bankAccount) {
|
|
3718
|
+
throw new NotFoundException('Bank account not found');
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
const signedAmountCents =
|
|
3722
|
+
data.type === 'saida'
|
|
3723
|
+
? -Math.abs(this.toCents(amount))
|
|
3724
|
+
: Math.abs(this.toCents(amount));
|
|
3725
|
+
|
|
3726
|
+
const created = await this.prisma.$transaction(async (tx) => {
|
|
3727
|
+
await this.assertDateNotInClosedPeriod(
|
|
3728
|
+
tx,
|
|
3729
|
+
postedDate,
|
|
3730
|
+
'create bank statement entry',
|
|
3731
|
+
);
|
|
3732
|
+
|
|
3733
|
+
const reference = `manual-entry:${bankAccountId}:${Date.now()}`;
|
|
3734
|
+
const statement = await tx.bank_statement.create({
|
|
3735
|
+
data: {
|
|
3736
|
+
bank_account_id: bankAccountId,
|
|
3737
|
+
source_type: 'manual',
|
|
3738
|
+
imported_at: postedDate,
|
|
3739
|
+
imported_by_user_id: userId,
|
|
3740
|
+
idempotency_key: reference,
|
|
3741
|
+
period_start: postedDate,
|
|
3742
|
+
period_end: postedDate,
|
|
3743
|
+
},
|
|
3744
|
+
});
|
|
3745
|
+
|
|
3746
|
+
return tx.bank_statement_line.create({
|
|
3747
|
+
data: {
|
|
3748
|
+
bank_statement_id: statement.id,
|
|
3749
|
+
bank_account_id: bankAccountId,
|
|
3750
|
+
posted_date: postedDate,
|
|
3751
|
+
amount_cents: signedAmountCents,
|
|
3752
|
+
description,
|
|
3753
|
+
status: 'pending',
|
|
3754
|
+
dedupe_key: `${reference}:${Math.round(Math.random() * 1_000_000)}`,
|
|
3755
|
+
},
|
|
3756
|
+
});
|
|
3757
|
+
});
|
|
3758
|
+
|
|
3759
|
+
return {
|
|
3760
|
+
id: String(created.id),
|
|
3761
|
+
contaBancariaId: String(created.bank_account_id),
|
|
3762
|
+
data: created.posted_date.toISOString(),
|
|
3763
|
+
descricao: created.description,
|
|
3764
|
+
valor: this.fromCents(created.amount_cents),
|
|
3765
|
+
tipo: created.amount_cents >= 0 ? 'entrada' : 'saida',
|
|
3766
|
+
statusConciliacao: this.mapStatementStatusToPt(created.status),
|
|
3767
|
+
isTransfer: false,
|
|
3768
|
+
canEdit: true,
|
|
3769
|
+
canDelete: true,
|
|
3770
|
+
reconciliationId: null,
|
|
3771
|
+
settlementId: null,
|
|
3772
|
+
};
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
async updateBankStatementEntry(
|
|
3776
|
+
id: number,
|
|
3777
|
+
data: UpdateBankStatementEntryDto,
|
|
3778
|
+
userId?: number,
|
|
3779
|
+
) {
|
|
3780
|
+
if (data.amount !== undefined) {
|
|
3781
|
+
const amount = Number(data.amount);
|
|
3782
|
+
if (Number.isNaN(amount) || amount <= 0) {
|
|
3783
|
+
throw new BadRequestException('amount must be greater than zero');
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
if (data.amount === undefined && data.description === undefined && data.date === undefined) {
|
|
3788
|
+
throw new BadRequestException('At least one field must be provided for update');
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
const updated = await this.prisma.$transaction(async (tx) => {
|
|
3792
|
+
const statementLine = await tx.bank_statement_line.findUnique({
|
|
3793
|
+
where: { id },
|
|
3794
|
+
include: {
|
|
3795
|
+
bank_reconciliation: {
|
|
3796
|
+
where: {
|
|
3797
|
+
status: {
|
|
3798
|
+
in: ['pending', 'reconciled', 'adjusted'],
|
|
3799
|
+
},
|
|
3800
|
+
},
|
|
3801
|
+
select: {
|
|
3802
|
+
id: true,
|
|
3803
|
+
},
|
|
3804
|
+
take: 1,
|
|
3805
|
+
},
|
|
3806
|
+
},
|
|
3807
|
+
});
|
|
3808
|
+
|
|
3809
|
+
if (!statementLine) {
|
|
3810
|
+
throw new NotFoundException('Bank statement line not found');
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
if (statementLine.external_id?.startsWith('transfer:')) {
|
|
3814
|
+
throw new ConflictException('Transfer movements cannot be edited');
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
if (statementLine.bank_reconciliation.length > 0) {
|
|
3818
|
+
throw new ConflictException('Reconciled movements cannot be edited');
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
const targetDate = data.date ? this.parseLocalDate(data.date) : statementLine.posted_date;
|
|
3822
|
+
await this.assertDateNotInClosedPeriod(
|
|
3823
|
+
tx,
|
|
3824
|
+
targetDate,
|
|
3825
|
+
'update bank statement entry',
|
|
3826
|
+
);
|
|
3827
|
+
|
|
3828
|
+
const updateData: Record<string, unknown> = {};
|
|
3829
|
+
|
|
3830
|
+
if (data.amount !== undefined) {
|
|
3831
|
+
const amount = Number(data.amount);
|
|
3832
|
+
updateData.amount_cents =
|
|
3833
|
+
statementLine.amount_cents < 0
|
|
3834
|
+
? -Math.abs(this.toCents(amount))
|
|
3835
|
+
: Math.abs(this.toCents(amount));
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
if (data.description !== undefined) {
|
|
3839
|
+
updateData.description = data.description.trim();
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3842
|
+
if (data.date !== undefined) {
|
|
3843
|
+
updateData.posted_date = this.parseLocalDate(data.date);
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
const result = await tx.bank_statement_line.update({
|
|
3847
|
+
where: { id },
|
|
3848
|
+
data: updateData,
|
|
3849
|
+
});
|
|
3850
|
+
|
|
3851
|
+
await this.createAuditLog(tx, {
|
|
3852
|
+
action: 'UPDATE_BANK_STATEMENT_LINE',
|
|
3853
|
+
entityTable: 'bank_statement_line',
|
|
3854
|
+
entityId: String(result.id),
|
|
3855
|
+
actorUserId: userId,
|
|
3856
|
+
summary: `Updated bank statement line ${result.id}`,
|
|
3857
|
+
});
|
|
3858
|
+
|
|
3859
|
+
return result;
|
|
3860
|
+
});
|
|
3861
|
+
|
|
3862
|
+
return {
|
|
3863
|
+
id: String(updated.id),
|
|
3864
|
+
contaBancariaId: String(updated.bank_account_id),
|
|
3865
|
+
data: updated.posted_date.toISOString(),
|
|
3866
|
+
descricao: updated.description,
|
|
3867
|
+
valor: this.fromCents(updated.amount_cents),
|
|
3868
|
+
tipo: updated.amount_cents >= 0 ? 'entrada' : 'saida',
|
|
3869
|
+
statusConciliacao: this.mapStatementStatusToPt(updated.status),
|
|
3870
|
+
isTransfer: false,
|
|
3871
|
+
canEdit: true,
|
|
3872
|
+
canDelete: true,
|
|
3873
|
+
reconciliationId: null,
|
|
3874
|
+
settlementId: null,
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
async deleteBankStatementEntry(id: number, userId?: number) {
|
|
3879
|
+
return this.prisma.$transaction(async (tx) => {
|
|
3880
|
+
const statementLine = await tx.bank_statement_line.findUnique({
|
|
3881
|
+
where: { id },
|
|
3882
|
+
include: {
|
|
3883
|
+
bank_reconciliation: {
|
|
3884
|
+
where: {
|
|
3885
|
+
status: {
|
|
3886
|
+
in: ['pending', 'reconciled', 'adjusted'],
|
|
3887
|
+
},
|
|
3888
|
+
},
|
|
3889
|
+
select: {
|
|
3890
|
+
id: true,
|
|
3891
|
+
},
|
|
3892
|
+
take: 1,
|
|
3893
|
+
},
|
|
3894
|
+
},
|
|
3895
|
+
});
|
|
3896
|
+
|
|
3897
|
+
if (!statementLine) {
|
|
3898
|
+
throw new NotFoundException('Bank statement line not found');
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
if (statementLine.external_id?.startsWith('transfer:')) {
|
|
3902
|
+
throw new ConflictException('Transfer movements cannot be deleted');
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
if (statementLine.bank_reconciliation.length > 0) {
|
|
3906
|
+
throw new ConflictException('Reconciled movements cannot be deleted');
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
await this.assertDateNotInClosedPeriod(
|
|
3910
|
+
tx,
|
|
3911
|
+
statementLine.posted_date,
|
|
3912
|
+
'delete bank statement entry',
|
|
3913
|
+
);
|
|
3914
|
+
|
|
3915
|
+
await tx.bank_statement_line.delete({
|
|
3916
|
+
where: { id },
|
|
3917
|
+
});
|
|
3918
|
+
|
|
3919
|
+
const remainingLines = await tx.bank_statement_line.count({
|
|
3920
|
+
where: {
|
|
3921
|
+
bank_statement_id: statementLine.bank_statement_id,
|
|
3922
|
+
},
|
|
3923
|
+
});
|
|
3924
|
+
|
|
3925
|
+
if (remainingLines === 0) {
|
|
3926
|
+
await tx.bank_statement.delete({
|
|
3927
|
+
where: {
|
|
3928
|
+
id: statementLine.bank_statement_id,
|
|
3929
|
+
},
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
await this.createAuditLog(tx, {
|
|
3934
|
+
action: 'DELETE_BANK_STATEMENT_LINE',
|
|
3935
|
+
entityTable: 'bank_statement_line',
|
|
3936
|
+
entityId: String(statementLine.id),
|
|
3937
|
+
actorUserId: userId,
|
|
3938
|
+
summary: `Deleted bank statement line ${statementLine.id}`,
|
|
3939
|
+
});
|
|
3940
|
+
|
|
3941
|
+
return { success: true };
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3530
3945
|
async importBankStatements(
|
|
3531
3946
|
bankAccountId: number,
|
|
3532
3947
|
file: MulterFile,
|
|
@@ -4003,6 +4418,7 @@ export class FinanceService {
|
|
|
4003
4418
|
|
|
4004
4419
|
const code = this.generateBankAccountCode(data.bank, data.account);
|
|
4005
4420
|
const name = data.description?.trim() || data.bank;
|
|
4421
|
+
const startDate = this.parseFilterDate(data.start_date) || new Date();
|
|
4006
4422
|
|
|
4007
4423
|
const createdAccount = await this.prisma.$transaction(async (tx) => {
|
|
4008
4424
|
const account = await tx.bank_account.create({
|
|
@@ -4019,7 +4435,7 @@ export class FinanceService {
|
|
|
4019
4435
|
});
|
|
4020
4436
|
|
|
4021
4437
|
if (data.initial_balance && data.initial_balance > 0) {
|
|
4022
|
-
const postedDate =
|
|
4438
|
+
const postedDate = startDate;
|
|
4023
4439
|
await this.assertDateNotInClosedPeriod(
|
|
4024
4440
|
tx,
|
|
4025
4441
|
postedDate,
|
|
@@ -4058,6 +4474,8 @@ export class FinanceService {
|
|
|
4058
4474
|
select: {
|
|
4059
4475
|
amount_cents: true,
|
|
4060
4476
|
status: true,
|
|
4477
|
+
posted_date: true,
|
|
4478
|
+
description: true,
|
|
4061
4479
|
},
|
|
4062
4480
|
},
|
|
4063
4481
|
},
|
|
@@ -4099,8 +4517,8 @@ export class FinanceService {
|
|
|
4099
4517
|
}
|
|
4100
4518
|
|
|
4101
4519
|
async createPeriodClose(data: CreatePeriodCloseDto, userId?: number) {
|
|
4102
|
-
const periodStart =
|
|
4103
|
-
const periodEnd =
|
|
4520
|
+
const periodStart = this.parseLocalDate(data.period_start);
|
|
4521
|
+
const periodEnd = this.parseLocalDate(data.period_end);
|
|
4104
4522
|
|
|
4105
4523
|
if (
|
|
4106
4524
|
Number.isNaN(periodStart.getTime()) ||
|
|
@@ -4176,6 +4594,8 @@ export class FinanceService {
|
|
|
4176
4594
|
select: {
|
|
4177
4595
|
amount_cents: true,
|
|
4178
4596
|
status: true,
|
|
4597
|
+
posted_date: true,
|
|
4598
|
+
description: true,
|
|
4179
4599
|
},
|
|
4180
4600
|
},
|
|
4181
4601
|
},
|
|
@@ -4322,6 +4742,100 @@ export class FinanceService {
|
|
|
4322
4742
|
return { success: true };
|
|
4323
4743
|
}
|
|
4324
4744
|
|
|
4745
|
+
async listCurrencies(paginationParams: PaginationDTO) {
|
|
4746
|
+
const currencies = await this.prisma.currency.findMany({
|
|
4747
|
+
orderBy: [{ code: 'asc' }],
|
|
4748
|
+
});
|
|
4749
|
+
|
|
4750
|
+
return currencies.map((c) => ({
|
|
4751
|
+
id: String(c.id),
|
|
4752
|
+
code: c.code,
|
|
4753
|
+
name: c.name,
|
|
4754
|
+
symbol: c.symbol,
|
|
4755
|
+
status: c.status,
|
|
4756
|
+
ativo: c.status === 'active',
|
|
4757
|
+
}));
|
|
4758
|
+
}
|
|
4759
|
+
|
|
4760
|
+
async createCurrency(data: CreateCurrencyDto) {
|
|
4761
|
+
const existing = await this.prisma.currency.findUnique({
|
|
4762
|
+
where: { code: data.code.toUpperCase() },
|
|
4763
|
+
select: { id: true },
|
|
4764
|
+
});
|
|
4765
|
+
|
|
4766
|
+
if (existing) {
|
|
4767
|
+
throw new ConflictException(
|
|
4768
|
+
`Currency with code ${data.code.toUpperCase()} already exists`,
|
|
4769
|
+
);
|
|
4770
|
+
}
|
|
4771
|
+
|
|
4772
|
+
const created = await this.prisma.currency.create({
|
|
4773
|
+
data: {
|
|
4774
|
+
code: data.code.toUpperCase(),
|
|
4775
|
+
name: data.name.trim(),
|
|
4776
|
+
symbol: data.symbol.trim(),
|
|
4777
|
+
status: 'active',
|
|
4778
|
+
},
|
|
4779
|
+
});
|
|
4780
|
+
|
|
4781
|
+
return {
|
|
4782
|
+
id: String(created.id),
|
|
4783
|
+
code: created.code,
|
|
4784
|
+
name: created.name,
|
|
4785
|
+
symbol: created.symbol,
|
|
4786
|
+
status: created.status,
|
|
4787
|
+
ativo: created.status === 'active',
|
|
4788
|
+
};
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4791
|
+
async updateCurrency(id: number, data: UpdateCurrencyDto) {
|
|
4792
|
+
const current = await this.prisma.currency.findUnique({
|
|
4793
|
+
where: { id },
|
|
4794
|
+
select: { id: true },
|
|
4795
|
+
});
|
|
4796
|
+
|
|
4797
|
+
if (!current) {
|
|
4798
|
+
throw new NotFoundException('Currency not found');
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
const updated = await this.prisma.currency.update({
|
|
4802
|
+
where: { id },
|
|
4803
|
+
data: {
|
|
4804
|
+
code: data.code?.toUpperCase(),
|
|
4805
|
+
name: data.name?.trim(),
|
|
4806
|
+
symbol: data.symbol?.trim(),
|
|
4807
|
+
status: data.status,
|
|
4808
|
+
},
|
|
4809
|
+
});
|
|
4810
|
+
|
|
4811
|
+
return {
|
|
4812
|
+
id: String(updated.id),
|
|
4813
|
+
code: updated.code,
|
|
4814
|
+
name: updated.name,
|
|
4815
|
+
symbol: updated.symbol,
|
|
4816
|
+
status: updated.status,
|
|
4817
|
+
ativo: updated.status === 'active',
|
|
4818
|
+
};
|
|
4819
|
+
}
|
|
4820
|
+
|
|
4821
|
+
async deleteCurrency(id: number) {
|
|
4822
|
+
const current = await this.prisma.currency.findUnique({
|
|
4823
|
+
where: { id },
|
|
4824
|
+
select: { id: true },
|
|
4825
|
+
});
|
|
4826
|
+
|
|
4827
|
+
if (!current) {
|
|
4828
|
+
throw new NotFoundException('Currency not found');
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4831
|
+
await this.prisma.currency.update({
|
|
4832
|
+
where: { id },
|
|
4833
|
+
data: { status: 'inactive' },
|
|
4834
|
+
});
|
|
4835
|
+
|
|
4836
|
+
return { success: true };
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4325
4839
|
async deleteFinanceCategory(id: number) {
|
|
4326
4840
|
const current = await this.prisma.finance_category.findUnique({
|
|
4327
4841
|
where: { id },
|
|
@@ -4346,9 +4860,15 @@ export class FinanceService {
|
|
|
4346
4860
|
titleType: TitleType,
|
|
4347
4861
|
paginationParams: PaginationDTO,
|
|
4348
4862
|
status?: string,
|
|
4863
|
+
filters?: {
|
|
4864
|
+
from?: string;
|
|
4865
|
+
to?: string;
|
|
4866
|
+
},
|
|
4349
4867
|
) {
|
|
4350
4868
|
const prismaStatus = this.mapStatusFromPt(status);
|
|
4351
4869
|
const search = paginationParams?.search?.trim();
|
|
4870
|
+
const fromDate = this.parseFilterDate(filters?.from);
|
|
4871
|
+
const toDate = this.parseFilterDate(filters?.to, true);
|
|
4352
4872
|
const where: any = {
|
|
4353
4873
|
title_type: titleType,
|
|
4354
4874
|
};
|
|
@@ -4376,6 +4896,17 @@ export class FinanceService {
|
|
|
4376
4896
|
];
|
|
4377
4897
|
}
|
|
4378
4898
|
|
|
4899
|
+
if (fromDate || toDate) {
|
|
4900
|
+
where.financial_installment = {
|
|
4901
|
+
some: {
|
|
4902
|
+
due_date: {
|
|
4903
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
4904
|
+
...(toDate ? { lte: toDate } : {}),
|
|
4905
|
+
},
|
|
4906
|
+
},
|
|
4907
|
+
};
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4379
4910
|
const normalizedPaginationParams: PaginationDTO = {
|
|
4380
4911
|
...paginationParams,
|
|
4381
4912
|
sortField: paginationParams?.sortField || 'created_at',
|
|
@@ -4495,8 +5026,8 @@ export class FinanceService {
|
|
|
4495
5026
|
await this.assertDateNotInClosedPeriod(
|
|
4496
5027
|
tx,
|
|
4497
5028
|
data.competence_date
|
|
4498
|
-
?
|
|
4499
|
-
:
|
|
5029
|
+
? this.parseLocalDate(data.competence_date)
|
|
5030
|
+
: this.parseLocalDate(installments[0].due_date),
|
|
4500
5031
|
'create title',
|
|
4501
5032
|
);
|
|
4502
5033
|
|
|
@@ -4508,9 +5039,9 @@ export class FinanceService {
|
|
|
4508
5039
|
document_number: data.document_number,
|
|
4509
5040
|
description: data.description,
|
|
4510
5041
|
competence_date: data.competence_date
|
|
4511
|
-
?
|
|
5042
|
+
? this.parseLocalDate(data.competence_date)
|
|
4512
5043
|
: null,
|
|
4513
|
-
issue_date: data.issue_date ?
|
|
5044
|
+
issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
|
|
4514
5045
|
total_amount_cents: this.toCents(data.total_amount),
|
|
4515
5046
|
finance_category_id: data.finance_category_id,
|
|
4516
5047
|
created_by_user_id: userId,
|
|
@@ -4536,15 +5067,15 @@ export class FinanceService {
|
|
|
4536
5067
|
title_id: title.id,
|
|
4537
5068
|
installment_number: installment.installment_number,
|
|
4538
5069
|
competence_date: data.competence_date
|
|
4539
|
-
?
|
|
4540
|
-
:
|
|
4541
|
-
due_date:
|
|
5070
|
+
? this.parseLocalDate(data.competence_date)
|
|
5071
|
+
: this.parseLocalDate(installment.due_date),
|
|
5072
|
+
due_date: this.parseLocalDate(installment.due_date),
|
|
4542
5073
|
amount_cents: amountCents,
|
|
4543
5074
|
open_amount_cents: amountCents,
|
|
4544
5075
|
status: this.resolveInstallmentStatus(
|
|
4545
5076
|
amountCents,
|
|
4546
5077
|
amountCents,
|
|
4547
|
-
|
|
5078
|
+
this.parseLocalDate(installment.due_date),
|
|
4548
5079
|
),
|
|
4549
5080
|
notes: data.description,
|
|
4550
5081
|
},
|
|
@@ -4684,7 +5215,7 @@ export class FinanceService {
|
|
|
4684
5215
|
await this.assertDateNotInClosedPeriod(
|
|
4685
5216
|
tx,
|
|
4686
5217
|
data.competence_date
|
|
4687
|
-
?
|
|
5218
|
+
? this.parseLocalDate(data.competence_date)
|
|
4688
5219
|
: new Date(installments[0].due_date),
|
|
4689
5220
|
'update title',
|
|
4690
5221
|
);
|
|
@@ -4717,9 +5248,9 @@ export class FinanceService {
|
|
|
4717
5248
|
document_number: data.document_number,
|
|
4718
5249
|
description: data.description,
|
|
4719
5250
|
competence_date: data.competence_date
|
|
4720
|
-
?
|
|
5251
|
+
? this.parseLocalDate(data.competence_date)
|
|
4721
5252
|
: null,
|
|
4722
|
-
issue_date: data.issue_date ?
|
|
5253
|
+
issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
|
|
4723
5254
|
total_amount_cents: this.toCents(data.total_amount),
|
|
4724
5255
|
finance_category_id: data.finance_category_id,
|
|
4725
5256
|
},
|
|
@@ -4757,15 +5288,15 @@ export class FinanceService {
|
|
|
4757
5288
|
title_id: title.id,
|
|
4758
5289
|
installment_number: installment.installment_number,
|
|
4759
5290
|
competence_date: data.competence_date
|
|
4760
|
-
?
|
|
4761
|
-
:
|
|
4762
|
-
due_date:
|
|
5291
|
+
? this.parseLocalDate(data.competence_date)
|
|
5292
|
+
: this.parseLocalDate(installment.due_date),
|
|
5293
|
+
due_date: this.parseLocalDate(installment.due_date),
|
|
4763
5294
|
amount_cents: amountCents,
|
|
4764
5295
|
open_amount_cents: amountCents,
|
|
4765
5296
|
status: this.resolveInstallmentStatus(
|
|
4766
5297
|
amountCents,
|
|
4767
5298
|
amountCents,
|
|
4768
|
-
|
|
5299
|
+
this.parseLocalDate(installment.due_date),
|
|
4769
5300
|
),
|
|
4770
5301
|
notes: data.description,
|
|
4771
5302
|
},
|
|
@@ -5292,7 +5823,7 @@ export class FinanceService {
|
|
|
5292
5823
|
);
|
|
5293
5824
|
}
|
|
5294
5825
|
|
|
5295
|
-
if (Math.abs(statementLine.amount_cents) !== amountCents) {
|
|
5826
|
+
if (Math.abs(Number(statementLine.amount_cents)) !== amountCents) {
|
|
5296
5827
|
throw new ConflictException(
|
|
5297
5828
|
'Bank statement amount and settlement amount must match',
|
|
5298
5829
|
);
|
|
@@ -5399,8 +5930,8 @@ export class FinanceService {
|
|
|
5399
5930
|
}
|
|
5400
5931
|
|
|
5401
5932
|
const nextInstallmentStatus = this.resolveInstallmentStatus(
|
|
5402
|
-
updatedInstallment.amount_cents,
|
|
5403
|
-
updatedInstallment.open_amount_cents,
|
|
5933
|
+
Number(updatedInstallment.amount_cents),
|
|
5934
|
+
Number(updatedInstallment.open_amount_cents),
|
|
5404
5935
|
updatedInstallment.due_date,
|
|
5405
5936
|
);
|
|
5406
5937
|
|
|
@@ -5930,6 +6461,8 @@ export class FinanceService {
|
|
|
5930
6461
|
select: {
|
|
5931
6462
|
amount_cents: true,
|
|
5932
6463
|
status: true,
|
|
6464
|
+
posted_date: true,
|
|
6465
|
+
description: true,
|
|
5933
6466
|
},
|
|
5934
6467
|
},
|
|
5935
6468
|
},
|
|
@@ -6334,6 +6867,31 @@ export class FinanceService {
|
|
|
6334
6867
|
return statusMap[status] || 'aberto';
|
|
6335
6868
|
}
|
|
6336
6869
|
|
|
6870
|
+
private paginateCollection<T>(items: T[], paginationParams?: PaginationDTO) {
|
|
6871
|
+
const requestedPage = Number(paginationParams?.page || 1);
|
|
6872
|
+
const requestedPageSize = Number(paginationParams?.pageSize || 10);
|
|
6873
|
+
const page = Number.isNaN(requestedPage) || requestedPage < 1
|
|
6874
|
+
? 1
|
|
6875
|
+
: requestedPage;
|
|
6876
|
+
const pageSize = Number.isNaN(requestedPageSize) || requestedPageSize < 1
|
|
6877
|
+
? 10
|
|
6878
|
+
: requestedPageSize;
|
|
6879
|
+
const total = items.length;
|
|
6880
|
+
const lastPage = Math.max(1, Math.ceil(total / pageSize));
|
|
6881
|
+
const currentPage = Math.min(page, lastPage);
|
|
6882
|
+
const start = (currentPage - 1) * pageSize;
|
|
6883
|
+
|
|
6884
|
+
return {
|
|
6885
|
+
data: items.slice(start, start + pageSize),
|
|
6886
|
+
total,
|
|
6887
|
+
page: currentPage,
|
|
6888
|
+
pageSize,
|
|
6889
|
+
prev: currentPage > 1 ? currentPage - 1 : null,
|
|
6890
|
+
next: currentPage < lastPage ? currentPage + 1 : null,
|
|
6891
|
+
lastPage,
|
|
6892
|
+
};
|
|
6893
|
+
}
|
|
6894
|
+
|
|
6337
6895
|
private normalizeTagSlug(value?: string | null) {
|
|
6338
6896
|
if (!value) {
|
|
6339
6897
|
return '';
|
|
@@ -6430,18 +6988,25 @@ export class FinanceService {
|
|
|
6430
6988
|
|
|
6431
6989
|
private mapBankAccountToFront(bankAccount: any) {
|
|
6432
6990
|
const currentCents = (bankAccount.bank_statement_line || []).reduce(
|
|
6433
|
-
(acc, line) => acc + line.amount_cents,
|
|
6991
|
+
(acc, line) => acc + Number(line.amount_cents),
|
|
6434
6992
|
0,
|
|
6435
6993
|
);
|
|
6436
6994
|
|
|
6437
6995
|
const reconciledCents = (bankAccount.bank_statement_line || []).reduce(
|
|
6438
6996
|
(acc, line) =>
|
|
6439
6997
|
line.status === 'reconciled' || line.status === 'adjusted'
|
|
6440
|
-
? acc + line.amount_cents
|
|
6998
|
+
? acc + Number(line.amount_cents)
|
|
6441
6999
|
: acc,
|
|
6442
7000
|
0,
|
|
6443
7001
|
);
|
|
6444
7002
|
|
|
7003
|
+
const initialLine = (bankAccount.bank_statement_line || []).find(
|
|
7004
|
+
(line: any) => line.description === 'Saldo inicial',
|
|
7005
|
+
);
|
|
7006
|
+
const dataInicial = initialLine?.posted_date
|
|
7007
|
+
? new Date(initialLine.posted_date).toISOString().slice(0, 10)
|
|
7008
|
+
: null;
|
|
7009
|
+
|
|
6445
7010
|
return {
|
|
6446
7011
|
id: String(bankAccount.id),
|
|
6447
7012
|
codigo: bankAccount.code,
|
|
@@ -6454,6 +7019,7 @@ export class FinanceService {
|
|
|
6454
7019
|
saldoAtual: this.fromCents(currentCents),
|
|
6455
7020
|
saldoConciliado: this.fromCents(reconciledCents),
|
|
6456
7021
|
ativo: bankAccount.status === 'active',
|
|
7022
|
+
dataInicial,
|
|
6457
7023
|
};
|
|
6458
7024
|
}
|
|
6459
7025
|
|
|
@@ -6796,6 +7362,18 @@ export class FinanceService {
|
|
|
6796
7362
|
return Math.round(value * 100);
|
|
6797
7363
|
}
|
|
6798
7364
|
|
|
7365
|
+
/**
|
|
7366
|
+
* Parses a date-only string (YYYY-MM-DD) as local noon to avoid
|
|
7367
|
+
* timezone shifts that would cause the date to appear as the previous day
|
|
7368
|
+
* in UTC-negative timezones.
|
|
7369
|
+
*/
|
|
7370
|
+
private parseLocalDate(dateString: string): Date {
|
|
7371
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
|
|
7372
|
+
return new Date(dateString + 'T12:00:00.000Z');
|
|
7373
|
+
}
|
|
7374
|
+
return new Date(dateString);
|
|
7375
|
+
}
|
|
7376
|
+
|
|
6799
7377
|
private fromCents(value: number | bigint | string | null | undefined) {
|
|
6800
7378
|
if (value === null || value === undefined) {
|
|
6801
7379
|
return 0;
|