@hed-hog/finance 0.0.317 → 0.0.319
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/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/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-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.service.d.ts +124 -35
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +389 -55
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/data/route.yaml +42 -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/cash-and-banks/bank-accounts/page.tsx.ejs +114 -31
- 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/table/bank_statement_line.yaml +1 -1
- package/hedhog/table/cashflow_projection.yaml +1 -1
- 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 +7 -7
- package/src/dto/create-bank-account.dto.ts +18 -1
- package/src/dto/create-bank-statement-entry.dto.ts +50 -0
- package/src/dto/update-bank-account.dto.ts +11 -1
- package/src/dto/update-bank-statement-entry.dto.ts +31 -0
- package/src/finance-bank-accounts.controller.ts +3 -2
- 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.service.ts +543 -61
package/src/finance.service.ts
CHANGED
|
@@ -20,6 +20,7 @@ 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';
|
|
24
25
|
import { CreateFinanceCategoryDto } from './dto/create-finance-category.dto';
|
|
25
26
|
import { CreateFinanceTagDto } from './dto/create-finance-tag.dto';
|
|
@@ -33,6 +34,7 @@ import { ReverseSettlementDto } from './dto/reverse-settlement.dto';
|
|
|
33
34
|
import { SendCollectionDto } from './dto/send-collection.dto';
|
|
34
35
|
import { SettleInstallmentDto } from './dto/settle-installment.dto';
|
|
35
36
|
import { UpdateBankAccountDto } from './dto/update-bank-account.dto';
|
|
37
|
+
import { UpdateBankStatementEntryDto } from './dto/update-bank-statement-entry.dto';
|
|
36
38
|
import { UpdateCostCenterDto } from './dto/update-cost-center.dto';
|
|
37
39
|
import { UpdateFinanceCategoryDto } from './dto/update-finance-category.dto';
|
|
38
40
|
|
|
@@ -654,6 +656,28 @@ export class FinanceService {
|
|
|
654
656
|
return dt.toISOString().slice(0, 10);
|
|
655
657
|
}
|
|
656
658
|
|
|
659
|
+
private parseFilterDate(value?: string, endOfDay = false): Date | undefined {
|
|
660
|
+
if (!value) {
|
|
661
|
+
return undefined;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const raw = String(value).trim();
|
|
665
|
+
if (!raw) {
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const normalized = /^\d{4}-\d{2}-\d{2}$/.test(raw)
|
|
670
|
+
? `${raw}T${endOfDay ? '23:59:59.999' : '00:00:00.000'}`
|
|
671
|
+
: raw;
|
|
672
|
+
const parsed = new Date(normalized);
|
|
673
|
+
|
|
674
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return parsed;
|
|
679
|
+
}
|
|
680
|
+
|
|
657
681
|
private normalizeMonth(value: any): string {
|
|
658
682
|
if (!value) return '';
|
|
659
683
|
const raw = String(value).trim();
|
|
@@ -695,6 +719,14 @@ export class FinanceService {
|
|
|
695
719
|
card: 'cartao',
|
|
696
720
|
credito: 'cartao',
|
|
697
721
|
debito: 'cartao',
|
|
722
|
+
'debito automatico': 'debito_automatico',
|
|
723
|
+
debito_automatico: 'debito_automatico',
|
|
724
|
+
'debito-automatico': 'debito_automatico',
|
|
725
|
+
'débito automático': 'debito_automatico',
|
|
726
|
+
'debito em conta': 'debito_em_conta',
|
|
727
|
+
debito_em_conta: 'debito_em_conta',
|
|
728
|
+
'debito-em-conta': 'debito_em_conta',
|
|
729
|
+
'débito em conta': 'debito_em_conta',
|
|
698
730
|
dinheiro: 'dinheiro',
|
|
699
731
|
cash: 'dinheiro',
|
|
700
732
|
cheque: 'cheque',
|
|
@@ -774,7 +806,14 @@ export class FinanceService {
|
|
|
774
806
|
this.loadCategories(),
|
|
775
807
|
this.loadCostCenters(),
|
|
776
808
|
this.loadBankAccounts(),
|
|
777
|
-
this.listBankStatements(
|
|
809
|
+
this.listBankStatements({
|
|
810
|
+
page: 1,
|
|
811
|
+
pageSize: 1000,
|
|
812
|
+
search: undefined,
|
|
813
|
+
sortField: undefined,
|
|
814
|
+
sortOrder: undefined,
|
|
815
|
+
fields: undefined,
|
|
816
|
+
}),
|
|
778
817
|
this.getAccountsReceivableCollectionsDefault(),
|
|
779
818
|
this.loadTags(),
|
|
780
819
|
this.loadAuditLogs(),
|
|
@@ -793,10 +832,10 @@ export class FinanceService {
|
|
|
793
832
|
costCentersResult.status === 'fulfilled' ? costCentersResult.value : [];
|
|
794
833
|
const bankAccounts =
|
|
795
834
|
bankAccountsResult.status === 'fulfilled' ? bankAccountsResult.value : [];
|
|
796
|
-
const bankStatements =
|
|
797
|
-
bankStatementsResult.status === 'fulfilled'
|
|
798
|
-
? bankStatementsResult.value
|
|
799
|
-
: [];
|
|
835
|
+
const bankStatements =
|
|
836
|
+
bankStatementsResult.status === 'fulfilled'
|
|
837
|
+
? bankStatementsResult.value.data
|
|
838
|
+
: [];
|
|
800
839
|
const collectionsDefault =
|
|
801
840
|
collectionsDefaultResult.status === 'fulfilled'
|
|
802
841
|
? collectionsDefaultResult.value
|
|
@@ -2114,7 +2153,7 @@ export class FinanceService {
|
|
|
2114
2153
|
throw new NotFoundException('Person not found');
|
|
2115
2154
|
}
|
|
2116
2155
|
|
|
2117
|
-
const firstDueDate =
|
|
2156
|
+
const firstDueDate = this.parseLocalDate(data.first_due_date);
|
|
2118
2157
|
|
|
2119
2158
|
if (Number.isNaN(firstDueDate.getTime())) {
|
|
2120
2159
|
throw new BadRequestException('Invalid first due date');
|
|
@@ -2333,15 +2372,23 @@ export class FinanceService {
|
|
|
2333
2372
|
async listAccountsPayableInstallments(
|
|
2334
2373
|
paginationParams: PaginationDTO,
|
|
2335
2374
|
status?: string,
|
|
2375
|
+
filters?: {
|
|
2376
|
+
from?: string;
|
|
2377
|
+
to?: string;
|
|
2378
|
+
},
|
|
2336
2379
|
) {
|
|
2337
|
-
return this.listTitles('payable', paginationParams, status);
|
|
2380
|
+
return this.listTitles('payable', paginationParams, status, filters);
|
|
2338
2381
|
}
|
|
2339
2382
|
|
|
2340
2383
|
async listAccountsReceivableInstallments(
|
|
2341
2384
|
paginationParams: PaginationDTO,
|
|
2342
2385
|
status?: string,
|
|
2386
|
+
filters?: {
|
|
2387
|
+
from?: string;
|
|
2388
|
+
to?: string;
|
|
2389
|
+
},
|
|
2343
2390
|
) {
|
|
2344
|
-
return this.listTitles('receivable', paginationParams, status);
|
|
2391
|
+
return this.listTitles('receivable', paginationParams, status, filters);
|
|
2345
2392
|
}
|
|
2346
2393
|
|
|
2347
2394
|
async getAccountsPayableInstallment(id: number, locale: string) {
|
|
@@ -2727,27 +2774,49 @@ export class FinanceService {
|
|
|
2727
2774
|
return this.updateTitleTags(id, 'receivable', tagIds, locale);
|
|
2728
2775
|
}
|
|
2729
2776
|
|
|
2730
|
-
async listBankAccounts() {
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2777
|
+
async listBankAccounts(paginationParams?: PaginationDTO) {
|
|
2778
|
+
const paginated = await this.paginationService.paginate(
|
|
2779
|
+
this.prisma.bank_account,
|
|
2780
|
+
{
|
|
2781
|
+
...paginationParams,
|
|
2782
|
+
sortField: paginationParams?.sortField || 'code',
|
|
2783
|
+
sortOrder: paginationParams?.sortOrder || PageOrderDirection.Asc,
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
include: {
|
|
2787
|
+
bank_statement_line: {
|
|
2788
|
+
select: {
|
|
2789
|
+
amount_cents: true,
|
|
2790
|
+
status: true,
|
|
2791
|
+
posted_date: true,
|
|
2792
|
+
description: true,
|
|
2793
|
+
},
|
|
2737
2794
|
},
|
|
2738
2795
|
},
|
|
2796
|
+
orderBy: [{ code: 'asc' }, { name: 'asc' }],
|
|
2739
2797
|
},
|
|
2740
|
-
|
|
2741
|
-
});
|
|
2798
|
+
);
|
|
2742
2799
|
|
|
2743
|
-
return
|
|
2800
|
+
return {
|
|
2801
|
+
...paginated,
|
|
2802
|
+
data: (paginated.data || []).map((bankAccount) =>
|
|
2803
|
+
this.mapBankAccountToFront(bankAccount),
|
|
2804
|
+
),
|
|
2805
|
+
};
|
|
2744
2806
|
}
|
|
2745
2807
|
|
|
2746
|
-
async listTransfers(
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2808
|
+
async listTransfers(
|
|
2809
|
+
paginationParams: PaginationDTO,
|
|
2810
|
+
filters?: {
|
|
2811
|
+
search?: string;
|
|
2812
|
+
bank_account_id?: string;
|
|
2813
|
+
from?: string;
|
|
2814
|
+
to?: string;
|
|
2815
|
+
},
|
|
2816
|
+
) {
|
|
2750
2817
|
const search = filters?.search?.trim();
|
|
2818
|
+
const fromDate = this.parseFilterDate(filters?.from);
|
|
2819
|
+
const toDate = this.parseFilterDate(filters?.to, true);
|
|
2751
2820
|
const parsedBankAccountId = filters?.bank_account_id
|
|
2752
2821
|
? Number.parseInt(filters.bank_account_id, 10)
|
|
2753
2822
|
: undefined;
|
|
@@ -2773,6 +2842,14 @@ export class FinanceService {
|
|
|
2773
2842
|
}
|
|
2774
2843
|
: {}),
|
|
2775
2844
|
...(bankAccountId ? { bank_account_id: bankAccountId } : {}),
|
|
2845
|
+
...((fromDate || toDate)
|
|
2846
|
+
? {
|
|
2847
|
+
posted_date: {
|
|
2848
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
2849
|
+
...(toDate ? { lte: toDate } : {}),
|
|
2850
|
+
},
|
|
2851
|
+
}
|
|
2852
|
+
: {}),
|
|
2776
2853
|
},
|
|
2777
2854
|
select: {
|
|
2778
2855
|
external_id: true,
|
|
@@ -2788,7 +2865,12 @@ export class FinanceService {
|
|
|
2788
2865
|
);
|
|
2789
2866
|
|
|
2790
2867
|
if (transferKeys.length === 0) {
|
|
2791
|
-
return
|
|
2868
|
+
return {
|
|
2869
|
+
...this.paginateCollection([], paginationParams),
|
|
2870
|
+
summary: {
|
|
2871
|
+
totalTransferido: 0,
|
|
2872
|
+
},
|
|
2873
|
+
};
|
|
2792
2874
|
}
|
|
2793
2875
|
}
|
|
2794
2876
|
|
|
@@ -2805,6 +2887,14 @@ export class FinanceService {
|
|
|
2805
2887
|
startsWith: 'transfer:',
|
|
2806
2888
|
},
|
|
2807
2889
|
}),
|
|
2890
|
+
...((fromDate || toDate)
|
|
2891
|
+
? {
|
|
2892
|
+
posted_date: {
|
|
2893
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
2894
|
+
...(toDate ? { lte: toDate } : {}),
|
|
2895
|
+
},
|
|
2896
|
+
}
|
|
2897
|
+
: {}),
|
|
2808
2898
|
},
|
|
2809
2899
|
select: {
|
|
2810
2900
|
id: true,
|
|
@@ -2844,20 +2934,28 @@ export class FinanceService {
|
|
|
2844
2934
|
contaOrigemId: String(sourceLine.bank_account_id),
|
|
2845
2935
|
contaDestinoId: String(destinationLine.bank_account_id),
|
|
2846
2936
|
data: sourceLine.posted_date.toISOString(),
|
|
2847
|
-
valor: this.fromCents(
|
|
2937
|
+
valor: Math.abs(this.fromCents(sourceLine.amount_cents)),
|
|
2848
2938
|
descricao: sourceLine.description || destinationLine.description || '',
|
|
2849
2939
|
};
|
|
2850
2940
|
})
|
|
2851
2941
|
.filter(Boolean);
|
|
2852
2942
|
|
|
2853
|
-
return
|
|
2943
|
+
return {
|
|
2944
|
+
...this.paginateCollection(transfers, paginationParams),
|
|
2945
|
+
summary: {
|
|
2946
|
+
totalTransferido: transfers.reduce(
|
|
2947
|
+
(acc, transfer) => acc + Number(transfer?.valor || 0),
|
|
2948
|
+
0,
|
|
2949
|
+
),
|
|
2950
|
+
},
|
|
2951
|
+
};
|
|
2854
2952
|
}
|
|
2855
2953
|
|
|
2856
2954
|
async createTransfer(data: CreateTransferDto, userId?: number) {
|
|
2857
2955
|
const sourceAccountId = Number(data.source_account_id);
|
|
2858
2956
|
const destinationAccountId = Number(data.destination_account_id);
|
|
2859
2957
|
const amount = Number(data.amount);
|
|
2860
|
-
const postedDate =
|
|
2958
|
+
const postedDate = this.parseLocalDate(data.date);
|
|
2861
2959
|
|
|
2862
2960
|
if (
|
|
2863
2961
|
Number.isNaN(sourceAccountId) ||
|
|
@@ -3182,8 +3280,18 @@ export class FinanceService {
|
|
|
3182
3280
|
};
|
|
3183
3281
|
}
|
|
3184
3282
|
|
|
3185
|
-
async listBankStatements(
|
|
3283
|
+
async listBankStatements(
|
|
3284
|
+
paginationParams: PaginationDTO,
|
|
3285
|
+
bankAccountId?: number,
|
|
3286
|
+
search?: string,
|
|
3287
|
+
filters?: {
|
|
3288
|
+
from?: string;
|
|
3289
|
+
to?: string;
|
|
3290
|
+
},
|
|
3291
|
+
) {
|
|
3186
3292
|
const trimmedSearch = search?.trim();
|
|
3293
|
+
const fromDate = this.parseFilterDate(filters?.from);
|
|
3294
|
+
const toDate = this.parseFilterDate(filters?.to, true);
|
|
3187
3295
|
|
|
3188
3296
|
const statements = await this.prisma.bank_statement_line.findMany({
|
|
3189
3297
|
where: {
|
|
@@ -3196,6 +3304,14 @@ export class FinanceService {
|
|
|
3196
3304
|
},
|
|
3197
3305
|
}
|
|
3198
3306
|
: {}),
|
|
3307
|
+
...((fromDate || toDate)
|
|
3308
|
+
? {
|
|
3309
|
+
posted_date: {
|
|
3310
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
3311
|
+
...(toDate ? { lte: toDate } : {}),
|
|
3312
|
+
},
|
|
3313
|
+
}
|
|
3314
|
+
: {}),
|
|
3199
3315
|
},
|
|
3200
3316
|
include: {
|
|
3201
3317
|
bank_account: {
|
|
@@ -3222,7 +3338,7 @@ export class FinanceService {
|
|
|
3222
3338
|
orderBy: [{ posted_date: 'desc' }, { id: 'desc' }],
|
|
3223
3339
|
});
|
|
3224
3340
|
|
|
3225
|
-
|
|
3341
|
+
const mappedStatements = statements.map((statement) => ({
|
|
3226
3342
|
id: String(statement.id),
|
|
3227
3343
|
contaBancariaId: String(statement.bank_account_id),
|
|
3228
3344
|
data: statement.posted_date.toISOString(),
|
|
@@ -3230,6 +3346,13 @@ export class FinanceService {
|
|
|
3230
3346
|
valor: this.fromCents(statement.amount_cents),
|
|
3231
3347
|
tipo: statement.amount_cents >= 0 ? 'entrada' : 'saida',
|
|
3232
3348
|
statusConciliacao: this.mapStatementStatusToPt(statement.status),
|
|
3349
|
+
isTransfer: statement.external_id?.startsWith('transfer:') || false,
|
|
3350
|
+
canEdit:
|
|
3351
|
+
!statement.external_id?.startsWith('transfer:') &&
|
|
3352
|
+
statement.bank_reconciliation.length === 0,
|
|
3353
|
+
canDelete:
|
|
3354
|
+
!statement.external_id?.startsWith('transfer:') &&
|
|
3355
|
+
statement.bank_reconciliation.length === 0,
|
|
3233
3356
|
reconciliationId: statement.bank_reconciliation[0]?.id
|
|
3234
3357
|
? String(statement.bank_reconciliation[0].id)
|
|
3235
3358
|
: null,
|
|
@@ -3237,6 +3360,18 @@ export class FinanceService {
|
|
|
3237
3360
|
? String(statement.bank_reconciliation[0].settlement_id)
|
|
3238
3361
|
: null,
|
|
3239
3362
|
}));
|
|
3363
|
+
|
|
3364
|
+
return {
|
|
3365
|
+
...this.paginateCollection(mappedStatements, paginationParams),
|
|
3366
|
+
summary: {
|
|
3367
|
+
totalEntradas: mappedStatements
|
|
3368
|
+
.filter((statement) => statement.tipo === 'entrada')
|
|
3369
|
+
.reduce((acc, statement) => acc + Number(statement.valor || 0), 0),
|
|
3370
|
+
totalSaidas: mappedStatements
|
|
3371
|
+
.filter((statement) => statement.tipo === 'saida')
|
|
3372
|
+
.reduce((acc, statement) => acc + Number(statement.valor || 0), 0),
|
|
3373
|
+
},
|
|
3374
|
+
};
|
|
3240
3375
|
}
|
|
3241
3376
|
|
|
3242
3377
|
async createBankReconciliation(
|
|
@@ -3322,7 +3457,7 @@ export class FinanceService {
|
|
|
3322
3457
|
title.id,
|
|
3323
3458
|
{
|
|
3324
3459
|
installment_id: installment.id,
|
|
3325
|
-
amount: this.fromCents(
|
|
3460
|
+
amount: Math.abs(this.fromCents(statementLine.amount_cents)),
|
|
3326
3461
|
settled_at: statementLine.posted_date.toISOString(),
|
|
3327
3462
|
bank_account_id: statementLine.bank_account_id,
|
|
3328
3463
|
bank_statement_line_id: statementLine.id,
|
|
@@ -3379,7 +3514,7 @@ export class FinanceService {
|
|
|
3379
3514
|
const receivableAmounts = new Set<number>();
|
|
3380
3515
|
|
|
3381
3516
|
for (const installment of openInstallments) {
|
|
3382
|
-
const amount =
|
|
3517
|
+
const amount = Number(installment.open_amount_cents);
|
|
3383
3518
|
if (installment.financial_title.title_type === 'payable') {
|
|
3384
3519
|
payableAmounts.add(amount);
|
|
3385
3520
|
} else if (installment.financial_title.title_type === 'receivable') {
|
|
@@ -3391,8 +3526,8 @@ export class FinanceService {
|
|
|
3391
3526
|
let differenceCents = 0;
|
|
3392
3527
|
|
|
3393
3528
|
for (const statement of pendingStatements) {
|
|
3394
|
-
const normalizedAmount = Math.abs(statement.amount_cents);
|
|
3395
|
-
differenceCents += statement.amount_cents;
|
|
3529
|
+
const normalizedAmount = Math.abs(Number(statement.amount_cents));
|
|
3530
|
+
differenceCents += Number(statement.amount_cents);
|
|
3396
3531
|
|
|
3397
3532
|
const hasPossibleMatch =
|
|
3398
3533
|
statement.amount_cents < 0
|
|
@@ -3410,8 +3545,28 @@ export class FinanceService {
|
|
|
3410
3545
|
};
|
|
3411
3546
|
}
|
|
3412
3547
|
|
|
3413
|
-
async exportBankStatementsCsv(
|
|
3414
|
-
|
|
3548
|
+
async exportBankStatementsCsv(
|
|
3549
|
+
bankAccountId: number,
|
|
3550
|
+
search?: string,
|
|
3551
|
+
filters?: {
|
|
3552
|
+
from?: string;
|
|
3553
|
+
to?: string;
|
|
3554
|
+
},
|
|
3555
|
+
) {
|
|
3556
|
+
const statementsResult = await this.listBankStatements(
|
|
3557
|
+
{
|
|
3558
|
+
page: 1,
|
|
3559
|
+
pageSize: 100000,
|
|
3560
|
+
search: undefined,
|
|
3561
|
+
sortField: undefined,
|
|
3562
|
+
sortOrder: undefined,
|
|
3563
|
+
fields: undefined,
|
|
3564
|
+
},
|
|
3565
|
+
bankAccountId,
|
|
3566
|
+
search,
|
|
3567
|
+
filters,
|
|
3568
|
+
);
|
|
3569
|
+
const statements = statementsResult.data;
|
|
3415
3570
|
|
|
3416
3571
|
const headers = [
|
|
3417
3572
|
'id',
|
|
@@ -3454,7 +3609,7 @@ export class FinanceService {
|
|
|
3454
3609
|
) {
|
|
3455
3610
|
const bankAccountId = Number(data.bank_account_id);
|
|
3456
3611
|
const amount = Number(data.amount);
|
|
3457
|
-
const postedAt = data.date ?
|
|
3612
|
+
const postedAt = data.date ? this.parseLocalDate(data.date) : new Date();
|
|
3458
3613
|
|
|
3459
3614
|
if (Number.isNaN(bankAccountId) || bankAccountId <= 0) {
|
|
3460
3615
|
throw new BadRequestException('bank_account_id is required');
|
|
@@ -3527,6 +3682,264 @@ export class FinanceService {
|
|
|
3527
3682
|
};
|
|
3528
3683
|
}
|
|
3529
3684
|
|
|
3685
|
+
async createBankStatementEntry(
|
|
3686
|
+
data: CreateBankStatementEntryDto,
|
|
3687
|
+
userId?: number,
|
|
3688
|
+
) {
|
|
3689
|
+
const bankAccountId = Number(data.bank_account_id);
|
|
3690
|
+
const amount = Number(data.amount);
|
|
3691
|
+
const postedDate = new Date(`${data.date}T00:00:00`);
|
|
3692
|
+
|
|
3693
|
+
if (Number.isNaN(bankAccountId) || bankAccountId <= 0) {
|
|
3694
|
+
throw new BadRequestException('bank_account_id is required');
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
if (Number.isNaN(amount) || amount <= 0) {
|
|
3698
|
+
throw new BadRequestException('amount must be greater than zero');
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
if (Number.isNaN(postedDate.getTime())) {
|
|
3702
|
+
throw new BadRequestException('Invalid statement date');
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
const description = data.description?.trim();
|
|
3706
|
+
if (!description) {
|
|
3707
|
+
throw new BadRequestException('description is required');
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
const bankAccount = await this.prisma.bank_account.findUnique({
|
|
3711
|
+
where: { id: bankAccountId },
|
|
3712
|
+
select: { id: true },
|
|
3713
|
+
});
|
|
3714
|
+
|
|
3715
|
+
if (!bankAccount) {
|
|
3716
|
+
throw new NotFoundException('Bank account not found');
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
const signedAmountCents =
|
|
3720
|
+
data.type === 'saida'
|
|
3721
|
+
? -Math.abs(this.toCents(amount))
|
|
3722
|
+
: Math.abs(this.toCents(amount));
|
|
3723
|
+
|
|
3724
|
+
const created = await this.prisma.$transaction(async (tx) => {
|
|
3725
|
+
await this.assertDateNotInClosedPeriod(
|
|
3726
|
+
tx,
|
|
3727
|
+
postedDate,
|
|
3728
|
+
'create bank statement entry',
|
|
3729
|
+
);
|
|
3730
|
+
|
|
3731
|
+
const reference = `manual-entry:${bankAccountId}:${Date.now()}`;
|
|
3732
|
+
const statement = await tx.bank_statement.create({
|
|
3733
|
+
data: {
|
|
3734
|
+
bank_account_id: bankAccountId,
|
|
3735
|
+
source_type: 'manual',
|
|
3736
|
+
imported_at: postedDate,
|
|
3737
|
+
imported_by_user_id: userId,
|
|
3738
|
+
idempotency_key: reference,
|
|
3739
|
+
period_start: postedDate,
|
|
3740
|
+
period_end: postedDate,
|
|
3741
|
+
},
|
|
3742
|
+
});
|
|
3743
|
+
|
|
3744
|
+
return tx.bank_statement_line.create({
|
|
3745
|
+
data: {
|
|
3746
|
+
bank_statement_id: statement.id,
|
|
3747
|
+
bank_account_id: bankAccountId,
|
|
3748
|
+
posted_date: postedDate,
|
|
3749
|
+
amount_cents: signedAmountCents,
|
|
3750
|
+
description,
|
|
3751
|
+
status: 'pending',
|
|
3752
|
+
dedupe_key: `${reference}:${Math.round(Math.random() * 1_000_000)}`,
|
|
3753
|
+
},
|
|
3754
|
+
});
|
|
3755
|
+
});
|
|
3756
|
+
|
|
3757
|
+
return {
|
|
3758
|
+
id: String(created.id),
|
|
3759
|
+
contaBancariaId: String(created.bank_account_id),
|
|
3760
|
+
data: created.posted_date.toISOString(),
|
|
3761
|
+
descricao: created.description,
|
|
3762
|
+
valor: this.fromCents(created.amount_cents),
|
|
3763
|
+
tipo: created.amount_cents >= 0 ? 'entrada' : 'saida',
|
|
3764
|
+
statusConciliacao: this.mapStatementStatusToPt(created.status),
|
|
3765
|
+
isTransfer: false,
|
|
3766
|
+
canEdit: true,
|
|
3767
|
+
canDelete: true,
|
|
3768
|
+
reconciliationId: null,
|
|
3769
|
+
settlementId: null,
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
async updateBankStatementEntry(
|
|
3774
|
+
id: number,
|
|
3775
|
+
data: UpdateBankStatementEntryDto,
|
|
3776
|
+
userId?: number,
|
|
3777
|
+
) {
|
|
3778
|
+
if (data.amount !== undefined) {
|
|
3779
|
+
const amount = Number(data.amount);
|
|
3780
|
+
if (Number.isNaN(amount) || amount <= 0) {
|
|
3781
|
+
throw new BadRequestException('amount must be greater than zero');
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3785
|
+
if (data.amount === undefined && data.description === undefined && data.date === undefined) {
|
|
3786
|
+
throw new BadRequestException('At least one field must be provided for update');
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
const updated = await this.prisma.$transaction(async (tx) => {
|
|
3790
|
+
const statementLine = await tx.bank_statement_line.findUnique({
|
|
3791
|
+
where: { id },
|
|
3792
|
+
include: {
|
|
3793
|
+
bank_reconciliation: {
|
|
3794
|
+
where: {
|
|
3795
|
+
status: {
|
|
3796
|
+
in: ['pending', 'reconciled', 'adjusted'],
|
|
3797
|
+
},
|
|
3798
|
+
},
|
|
3799
|
+
select: {
|
|
3800
|
+
id: true,
|
|
3801
|
+
},
|
|
3802
|
+
take: 1,
|
|
3803
|
+
},
|
|
3804
|
+
},
|
|
3805
|
+
});
|
|
3806
|
+
|
|
3807
|
+
if (!statementLine) {
|
|
3808
|
+
throw new NotFoundException('Bank statement line not found');
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
if (statementLine.external_id?.startsWith('transfer:')) {
|
|
3812
|
+
throw new ConflictException('Transfer movements cannot be edited');
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
if (statementLine.bank_reconciliation.length > 0) {
|
|
3816
|
+
throw new ConflictException('Reconciled movements cannot be edited');
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
const targetDate = data.date ? this.parseLocalDate(data.date) : statementLine.posted_date;
|
|
3820
|
+
await this.assertDateNotInClosedPeriod(
|
|
3821
|
+
tx,
|
|
3822
|
+
targetDate,
|
|
3823
|
+
'update bank statement entry',
|
|
3824
|
+
);
|
|
3825
|
+
|
|
3826
|
+
const updateData: Record<string, unknown> = {};
|
|
3827
|
+
|
|
3828
|
+
if (data.amount !== undefined) {
|
|
3829
|
+
const amount = Number(data.amount);
|
|
3830
|
+
updateData.amount_cents =
|
|
3831
|
+
statementLine.amount_cents < 0
|
|
3832
|
+
? -Math.abs(this.toCents(amount))
|
|
3833
|
+
: Math.abs(this.toCents(amount));
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
if (data.description !== undefined) {
|
|
3837
|
+
updateData.description = data.description.trim();
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
if (data.date !== undefined) {
|
|
3841
|
+
updateData.posted_date = this.parseLocalDate(data.date);
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
const result = await tx.bank_statement_line.update({
|
|
3845
|
+
where: { id },
|
|
3846
|
+
data: updateData,
|
|
3847
|
+
});
|
|
3848
|
+
|
|
3849
|
+
await this.createAuditLog(tx, {
|
|
3850
|
+
action: 'UPDATE_BANK_STATEMENT_LINE',
|
|
3851
|
+
entityTable: 'bank_statement_line',
|
|
3852
|
+
entityId: String(result.id),
|
|
3853
|
+
actorUserId: userId,
|
|
3854
|
+
summary: `Updated bank statement line ${result.id}`,
|
|
3855
|
+
});
|
|
3856
|
+
|
|
3857
|
+
return result;
|
|
3858
|
+
});
|
|
3859
|
+
|
|
3860
|
+
return {
|
|
3861
|
+
id: String(updated.id),
|
|
3862
|
+
contaBancariaId: String(updated.bank_account_id),
|
|
3863
|
+
data: updated.posted_date.toISOString(),
|
|
3864
|
+
descricao: updated.description,
|
|
3865
|
+
valor: this.fromCents(updated.amount_cents),
|
|
3866
|
+
tipo: updated.amount_cents >= 0 ? 'entrada' : 'saida',
|
|
3867
|
+
statusConciliacao: this.mapStatementStatusToPt(updated.status),
|
|
3868
|
+
isTransfer: false,
|
|
3869
|
+
canEdit: true,
|
|
3870
|
+
canDelete: true,
|
|
3871
|
+
reconciliationId: null,
|
|
3872
|
+
settlementId: null,
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
async deleteBankStatementEntry(id: number, userId?: number) {
|
|
3877
|
+
return this.prisma.$transaction(async (tx) => {
|
|
3878
|
+
const statementLine = await tx.bank_statement_line.findUnique({
|
|
3879
|
+
where: { id },
|
|
3880
|
+
include: {
|
|
3881
|
+
bank_reconciliation: {
|
|
3882
|
+
where: {
|
|
3883
|
+
status: {
|
|
3884
|
+
in: ['pending', 'reconciled', 'adjusted'],
|
|
3885
|
+
},
|
|
3886
|
+
},
|
|
3887
|
+
select: {
|
|
3888
|
+
id: true,
|
|
3889
|
+
},
|
|
3890
|
+
take: 1,
|
|
3891
|
+
},
|
|
3892
|
+
},
|
|
3893
|
+
});
|
|
3894
|
+
|
|
3895
|
+
if (!statementLine) {
|
|
3896
|
+
throw new NotFoundException('Bank statement line not found');
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
if (statementLine.external_id?.startsWith('transfer:')) {
|
|
3900
|
+
throw new ConflictException('Transfer movements cannot be deleted');
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
if (statementLine.bank_reconciliation.length > 0) {
|
|
3904
|
+
throw new ConflictException('Reconciled movements cannot be deleted');
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
await this.assertDateNotInClosedPeriod(
|
|
3908
|
+
tx,
|
|
3909
|
+
statementLine.posted_date,
|
|
3910
|
+
'delete bank statement entry',
|
|
3911
|
+
);
|
|
3912
|
+
|
|
3913
|
+
await tx.bank_statement_line.delete({
|
|
3914
|
+
where: { id },
|
|
3915
|
+
});
|
|
3916
|
+
|
|
3917
|
+
const remainingLines = await tx.bank_statement_line.count({
|
|
3918
|
+
where: {
|
|
3919
|
+
bank_statement_id: statementLine.bank_statement_id,
|
|
3920
|
+
},
|
|
3921
|
+
});
|
|
3922
|
+
|
|
3923
|
+
if (remainingLines === 0) {
|
|
3924
|
+
await tx.bank_statement.delete({
|
|
3925
|
+
where: {
|
|
3926
|
+
id: statementLine.bank_statement_id,
|
|
3927
|
+
},
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
await this.createAuditLog(tx, {
|
|
3932
|
+
action: 'DELETE_BANK_STATEMENT_LINE',
|
|
3933
|
+
entityTable: 'bank_statement_line',
|
|
3934
|
+
entityId: String(statementLine.id),
|
|
3935
|
+
actorUserId: userId,
|
|
3936
|
+
summary: `Deleted bank statement line ${statementLine.id}`,
|
|
3937
|
+
});
|
|
3938
|
+
|
|
3939
|
+
return { success: true };
|
|
3940
|
+
});
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3530
3943
|
async importBankStatements(
|
|
3531
3944
|
bankAccountId: number,
|
|
3532
3945
|
file: MulterFile,
|
|
@@ -4003,6 +4416,7 @@ export class FinanceService {
|
|
|
4003
4416
|
|
|
4004
4417
|
const code = this.generateBankAccountCode(data.bank, data.account);
|
|
4005
4418
|
const name = data.description?.trim() || data.bank;
|
|
4419
|
+
const startDate = this.parseFilterDate(data.start_date) || new Date();
|
|
4006
4420
|
|
|
4007
4421
|
const createdAccount = await this.prisma.$transaction(async (tx) => {
|
|
4008
4422
|
const account = await tx.bank_account.create({
|
|
@@ -4019,7 +4433,7 @@ export class FinanceService {
|
|
|
4019
4433
|
});
|
|
4020
4434
|
|
|
4021
4435
|
if (data.initial_balance && data.initial_balance > 0) {
|
|
4022
|
-
const postedDate =
|
|
4436
|
+
const postedDate = startDate;
|
|
4023
4437
|
await this.assertDateNotInClosedPeriod(
|
|
4024
4438
|
tx,
|
|
4025
4439
|
postedDate,
|
|
@@ -4058,6 +4472,8 @@ export class FinanceService {
|
|
|
4058
4472
|
select: {
|
|
4059
4473
|
amount_cents: true,
|
|
4060
4474
|
status: true,
|
|
4475
|
+
posted_date: true,
|
|
4476
|
+
description: true,
|
|
4061
4477
|
},
|
|
4062
4478
|
},
|
|
4063
4479
|
},
|
|
@@ -4099,8 +4515,8 @@ export class FinanceService {
|
|
|
4099
4515
|
}
|
|
4100
4516
|
|
|
4101
4517
|
async createPeriodClose(data: CreatePeriodCloseDto, userId?: number) {
|
|
4102
|
-
const periodStart =
|
|
4103
|
-
const periodEnd =
|
|
4518
|
+
const periodStart = this.parseLocalDate(data.period_start);
|
|
4519
|
+
const periodEnd = this.parseLocalDate(data.period_end);
|
|
4104
4520
|
|
|
4105
4521
|
if (
|
|
4106
4522
|
Number.isNaN(periodStart.getTime()) ||
|
|
@@ -4176,6 +4592,8 @@ export class FinanceService {
|
|
|
4176
4592
|
select: {
|
|
4177
4593
|
amount_cents: true,
|
|
4178
4594
|
status: true,
|
|
4595
|
+
posted_date: true,
|
|
4596
|
+
description: true,
|
|
4179
4597
|
},
|
|
4180
4598
|
},
|
|
4181
4599
|
},
|
|
@@ -4346,9 +4764,15 @@ export class FinanceService {
|
|
|
4346
4764
|
titleType: TitleType,
|
|
4347
4765
|
paginationParams: PaginationDTO,
|
|
4348
4766
|
status?: string,
|
|
4767
|
+
filters?: {
|
|
4768
|
+
from?: string;
|
|
4769
|
+
to?: string;
|
|
4770
|
+
},
|
|
4349
4771
|
) {
|
|
4350
4772
|
const prismaStatus = this.mapStatusFromPt(status);
|
|
4351
4773
|
const search = paginationParams?.search?.trim();
|
|
4774
|
+
const fromDate = this.parseFilterDate(filters?.from);
|
|
4775
|
+
const toDate = this.parseFilterDate(filters?.to, true);
|
|
4352
4776
|
const where: any = {
|
|
4353
4777
|
title_type: titleType,
|
|
4354
4778
|
};
|
|
@@ -4376,6 +4800,17 @@ export class FinanceService {
|
|
|
4376
4800
|
];
|
|
4377
4801
|
}
|
|
4378
4802
|
|
|
4803
|
+
if (fromDate || toDate) {
|
|
4804
|
+
where.financial_installment = {
|
|
4805
|
+
some: {
|
|
4806
|
+
due_date: {
|
|
4807
|
+
...(fromDate ? { gte: fromDate } : {}),
|
|
4808
|
+
...(toDate ? { lte: toDate } : {}),
|
|
4809
|
+
},
|
|
4810
|
+
},
|
|
4811
|
+
};
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4379
4814
|
const normalizedPaginationParams: PaginationDTO = {
|
|
4380
4815
|
...paginationParams,
|
|
4381
4816
|
sortField: paginationParams?.sortField || 'created_at',
|
|
@@ -4495,8 +4930,8 @@ export class FinanceService {
|
|
|
4495
4930
|
await this.assertDateNotInClosedPeriod(
|
|
4496
4931
|
tx,
|
|
4497
4932
|
data.competence_date
|
|
4498
|
-
?
|
|
4499
|
-
:
|
|
4933
|
+
? this.parseLocalDate(data.competence_date)
|
|
4934
|
+
: this.parseLocalDate(installments[0].due_date),
|
|
4500
4935
|
'create title',
|
|
4501
4936
|
);
|
|
4502
4937
|
|
|
@@ -4508,9 +4943,9 @@ export class FinanceService {
|
|
|
4508
4943
|
document_number: data.document_number,
|
|
4509
4944
|
description: data.description,
|
|
4510
4945
|
competence_date: data.competence_date
|
|
4511
|
-
?
|
|
4946
|
+
? this.parseLocalDate(data.competence_date)
|
|
4512
4947
|
: null,
|
|
4513
|
-
issue_date: data.issue_date ?
|
|
4948
|
+
issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
|
|
4514
4949
|
total_amount_cents: this.toCents(data.total_amount),
|
|
4515
4950
|
finance_category_id: data.finance_category_id,
|
|
4516
4951
|
created_by_user_id: userId,
|
|
@@ -4536,15 +4971,15 @@ export class FinanceService {
|
|
|
4536
4971
|
title_id: title.id,
|
|
4537
4972
|
installment_number: installment.installment_number,
|
|
4538
4973
|
competence_date: data.competence_date
|
|
4539
|
-
?
|
|
4540
|
-
:
|
|
4541
|
-
due_date:
|
|
4974
|
+
? this.parseLocalDate(data.competence_date)
|
|
4975
|
+
: this.parseLocalDate(installment.due_date),
|
|
4976
|
+
due_date: this.parseLocalDate(installment.due_date),
|
|
4542
4977
|
amount_cents: amountCents,
|
|
4543
4978
|
open_amount_cents: amountCents,
|
|
4544
4979
|
status: this.resolveInstallmentStatus(
|
|
4545
4980
|
amountCents,
|
|
4546
4981
|
amountCents,
|
|
4547
|
-
|
|
4982
|
+
this.parseLocalDate(installment.due_date),
|
|
4548
4983
|
),
|
|
4549
4984
|
notes: data.description,
|
|
4550
4985
|
},
|
|
@@ -4684,7 +5119,7 @@ export class FinanceService {
|
|
|
4684
5119
|
await this.assertDateNotInClosedPeriod(
|
|
4685
5120
|
tx,
|
|
4686
5121
|
data.competence_date
|
|
4687
|
-
?
|
|
5122
|
+
? this.parseLocalDate(data.competence_date)
|
|
4688
5123
|
: new Date(installments[0].due_date),
|
|
4689
5124
|
'update title',
|
|
4690
5125
|
);
|
|
@@ -4717,9 +5152,9 @@ export class FinanceService {
|
|
|
4717
5152
|
document_number: data.document_number,
|
|
4718
5153
|
description: data.description,
|
|
4719
5154
|
competence_date: data.competence_date
|
|
4720
|
-
?
|
|
5155
|
+
? this.parseLocalDate(data.competence_date)
|
|
4721
5156
|
: null,
|
|
4722
|
-
issue_date: data.issue_date ?
|
|
5157
|
+
issue_date: data.issue_date ? this.parseLocalDate(data.issue_date) : null,
|
|
4723
5158
|
total_amount_cents: this.toCents(data.total_amount),
|
|
4724
5159
|
finance_category_id: data.finance_category_id,
|
|
4725
5160
|
},
|
|
@@ -4757,15 +5192,15 @@ export class FinanceService {
|
|
|
4757
5192
|
title_id: title.id,
|
|
4758
5193
|
installment_number: installment.installment_number,
|
|
4759
5194
|
competence_date: data.competence_date
|
|
4760
|
-
?
|
|
4761
|
-
:
|
|
4762
|
-
due_date:
|
|
5195
|
+
? this.parseLocalDate(data.competence_date)
|
|
5196
|
+
: this.parseLocalDate(installment.due_date),
|
|
5197
|
+
due_date: this.parseLocalDate(installment.due_date),
|
|
4763
5198
|
amount_cents: amountCents,
|
|
4764
5199
|
open_amount_cents: amountCents,
|
|
4765
5200
|
status: this.resolveInstallmentStatus(
|
|
4766
5201
|
amountCents,
|
|
4767
5202
|
amountCents,
|
|
4768
|
-
|
|
5203
|
+
this.parseLocalDate(installment.due_date),
|
|
4769
5204
|
),
|
|
4770
5205
|
notes: data.description,
|
|
4771
5206
|
},
|
|
@@ -5292,7 +5727,7 @@ export class FinanceService {
|
|
|
5292
5727
|
);
|
|
5293
5728
|
}
|
|
5294
5729
|
|
|
5295
|
-
if (Math.abs(statementLine.amount_cents) !== amountCents) {
|
|
5730
|
+
if (Math.abs(Number(statementLine.amount_cents)) !== amountCents) {
|
|
5296
5731
|
throw new ConflictException(
|
|
5297
5732
|
'Bank statement amount and settlement amount must match',
|
|
5298
5733
|
);
|
|
@@ -5398,11 +5833,11 @@ export class FinanceService {
|
|
|
5398
5833
|
throw new NotFoundException('Installment not found');
|
|
5399
5834
|
}
|
|
5400
5835
|
|
|
5401
|
-
const nextInstallmentStatus = this.resolveInstallmentStatus(
|
|
5402
|
-
updatedInstallment.amount_cents,
|
|
5403
|
-
updatedInstallment.open_amount_cents,
|
|
5404
|
-
updatedInstallment.due_date,
|
|
5405
|
-
);
|
|
5836
|
+
const nextInstallmentStatus = this.resolveInstallmentStatus(
|
|
5837
|
+
Number(updatedInstallment.amount_cents),
|
|
5838
|
+
Number(updatedInstallment.open_amount_cents),
|
|
5839
|
+
updatedInstallment.due_date,
|
|
5840
|
+
);
|
|
5406
5841
|
|
|
5407
5842
|
if (updatedInstallment.status !== nextInstallmentStatus) {
|
|
5408
5843
|
await tx.financial_installment.update({
|
|
@@ -5930,6 +6365,8 @@ export class FinanceService {
|
|
|
5930
6365
|
select: {
|
|
5931
6366
|
amount_cents: true,
|
|
5932
6367
|
status: true,
|
|
6368
|
+
posted_date: true,
|
|
6369
|
+
description: true,
|
|
5933
6370
|
},
|
|
5934
6371
|
},
|
|
5935
6372
|
},
|
|
@@ -6334,6 +6771,31 @@ export class FinanceService {
|
|
|
6334
6771
|
return statusMap[status] || 'aberto';
|
|
6335
6772
|
}
|
|
6336
6773
|
|
|
6774
|
+
private paginateCollection<T>(items: T[], paginationParams?: PaginationDTO) {
|
|
6775
|
+
const requestedPage = Number(paginationParams?.page || 1);
|
|
6776
|
+
const requestedPageSize = Number(paginationParams?.pageSize || 10);
|
|
6777
|
+
const page = Number.isNaN(requestedPage) || requestedPage < 1
|
|
6778
|
+
? 1
|
|
6779
|
+
: requestedPage;
|
|
6780
|
+
const pageSize = Number.isNaN(requestedPageSize) || requestedPageSize < 1
|
|
6781
|
+
? 10
|
|
6782
|
+
: requestedPageSize;
|
|
6783
|
+
const total = items.length;
|
|
6784
|
+
const lastPage = Math.max(1, Math.ceil(total / pageSize));
|
|
6785
|
+
const currentPage = Math.min(page, lastPage);
|
|
6786
|
+
const start = (currentPage - 1) * pageSize;
|
|
6787
|
+
|
|
6788
|
+
return {
|
|
6789
|
+
data: items.slice(start, start + pageSize),
|
|
6790
|
+
total,
|
|
6791
|
+
page: currentPage,
|
|
6792
|
+
pageSize,
|
|
6793
|
+
prev: currentPage > 1 ? currentPage - 1 : null,
|
|
6794
|
+
next: currentPage < lastPage ? currentPage + 1 : null,
|
|
6795
|
+
lastPage,
|
|
6796
|
+
};
|
|
6797
|
+
}
|
|
6798
|
+
|
|
6337
6799
|
private normalizeTagSlug(value?: string | null) {
|
|
6338
6800
|
if (!value) {
|
|
6339
6801
|
return '';
|
|
@@ -6430,18 +6892,25 @@ export class FinanceService {
|
|
|
6430
6892
|
|
|
6431
6893
|
private mapBankAccountToFront(bankAccount: any) {
|
|
6432
6894
|
const currentCents = (bankAccount.bank_statement_line || []).reduce(
|
|
6433
|
-
(acc, line) => acc + line.amount_cents,
|
|
6895
|
+
(acc, line) => acc + Number(line.amount_cents),
|
|
6434
6896
|
0,
|
|
6435
6897
|
);
|
|
6436
6898
|
|
|
6437
6899
|
const reconciledCents = (bankAccount.bank_statement_line || []).reduce(
|
|
6438
6900
|
(acc, line) =>
|
|
6439
6901
|
line.status === 'reconciled' || line.status === 'adjusted'
|
|
6440
|
-
? acc + line.amount_cents
|
|
6902
|
+
? acc + Number(line.amount_cents)
|
|
6441
6903
|
: acc,
|
|
6442
6904
|
0,
|
|
6443
6905
|
);
|
|
6444
6906
|
|
|
6907
|
+
const initialLine = (bankAccount.bank_statement_line || []).find(
|
|
6908
|
+
(line: any) => line.description === 'Saldo inicial',
|
|
6909
|
+
);
|
|
6910
|
+
const dataInicial = initialLine?.posted_date
|
|
6911
|
+
? new Date(initialLine.posted_date).toISOString().slice(0, 10)
|
|
6912
|
+
: null;
|
|
6913
|
+
|
|
6445
6914
|
return {
|
|
6446
6915
|
id: String(bankAccount.id),
|
|
6447
6916
|
codigo: bankAccount.code,
|
|
@@ -6454,6 +6923,7 @@ export class FinanceService {
|
|
|
6454
6923
|
saldoAtual: this.fromCents(currentCents),
|
|
6455
6924
|
saldoConciliado: this.fromCents(reconciledCents),
|
|
6456
6925
|
ativo: bankAccount.status === 'active',
|
|
6926
|
+
dataInicial,
|
|
6457
6927
|
};
|
|
6458
6928
|
}
|
|
6459
6929
|
|
|
@@ -6796,6 +7266,18 @@ export class FinanceService {
|
|
|
6796
7266
|
return Math.round(value * 100);
|
|
6797
7267
|
}
|
|
6798
7268
|
|
|
7269
|
+
/**
|
|
7270
|
+
* Parses a date-only string (YYYY-MM-DD) as local noon to avoid
|
|
7271
|
+
* timezone shifts that would cause the date to appear as the previous day
|
|
7272
|
+
* in UTC-negative timezones.
|
|
7273
|
+
*/
|
|
7274
|
+
private parseLocalDate(dateString: string): Date {
|
|
7275
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
|
|
7276
|
+
return new Date(dateString + 'T12:00:00.000Z');
|
|
7277
|
+
}
|
|
7278
|
+
return new Date(dateString);
|
|
7279
|
+
}
|
|
7280
|
+
|
|
6799
7281
|
private fromCents(value: number | bigint | string | null | undefined) {
|
|
6800
7282
|
if (value === null || value === undefined) {
|
|
6801
7283
|
return 0;
|