@hed-hog/finance 0.0.322 → 0.0.326
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/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/finance-bank-accounts.controller.d.ts +21 -0
- package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
- package/dist/finance-data.controller.d.ts +13 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance.service.d.ts +37 -2
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +125 -19
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/integration_event_catalog.yaml +274 -0
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +83 -37
- package/hedhog/frontend/widgets/bank-reconciliation-status.tsx.ejs +33 -9
- package/hedhog/frontend/widgets/cash-balance-kpi.tsx.ejs +30 -9
- package/package.json +7 -7
- package/src/dto/create-bank-account.dto.ts +12 -6
- package/src/dto/update-bank-account.dto.ts +6 -0
- package/src/finance.service.ts +156 -26
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
- slug: finance.payable.created_from_contract
|
|
2
|
+
name: Finance payable created from contract
|
|
3
|
+
module: finance
|
|
4
|
+
aggregate_type: financial_title
|
|
5
|
+
description: Disparado quando um título de contas a pagar é criado a partir de um contrato ativado.
|
|
6
|
+
status: active
|
|
7
|
+
payload_schema:
|
|
8
|
+
correlationId:
|
|
9
|
+
type: string
|
|
10
|
+
description: ID de correlação para rastreamento entre eventos
|
|
11
|
+
contractId:
|
|
12
|
+
type: number
|
|
13
|
+
description: ID do contrato de origem
|
|
14
|
+
proposalId:
|
|
15
|
+
type: number
|
|
16
|
+
nullable: true
|
|
17
|
+
description: ID da proposta de origem (quando aplicável)
|
|
18
|
+
personId:
|
|
19
|
+
type: number
|
|
20
|
+
description: ID da pessoa (fornecedor/credor)
|
|
21
|
+
financialTitle:
|
|
22
|
+
type: object
|
|
23
|
+
description: Dados do título financeiro criado
|
|
24
|
+
installments:
|
|
25
|
+
type: array
|
|
26
|
+
description: Lista de parcelas geradas
|
|
27
|
+
contract:
|
|
28
|
+
type: object
|
|
29
|
+
description: Dados do contrato de origem
|
|
30
|
+
metadata_schema: null
|
|
31
|
+
|
|
32
|
+
- slug: finance.receivable.created_from_contract
|
|
33
|
+
name: Finance receivable created from contract
|
|
34
|
+
module: finance
|
|
35
|
+
aggregate_type: financial_title
|
|
36
|
+
description: Disparado quando um título de contas a receber é criado a partir de um contrato ativado.
|
|
37
|
+
status: active
|
|
38
|
+
payload_schema:
|
|
39
|
+
correlationId:
|
|
40
|
+
type: string
|
|
41
|
+
description: ID de correlação para rastreamento entre eventos
|
|
42
|
+
contractId:
|
|
43
|
+
type: number
|
|
44
|
+
description: ID do contrato de origem
|
|
45
|
+
proposalId:
|
|
46
|
+
type: number
|
|
47
|
+
nullable: true
|
|
48
|
+
description: ID da proposta de origem (quando aplicável)
|
|
49
|
+
personId:
|
|
50
|
+
type: number
|
|
51
|
+
description: ID da pessoa (cliente/devedor)
|
|
52
|
+
financialTitle:
|
|
53
|
+
type: object
|
|
54
|
+
description: Dados do título financeiro criado
|
|
55
|
+
installments:
|
|
56
|
+
type: array
|
|
57
|
+
description: Lista de parcelas geradas
|
|
58
|
+
contract:
|
|
59
|
+
type: object
|
|
60
|
+
description: Dados do contrato de origem
|
|
61
|
+
metadata_schema: null
|
|
62
|
+
|
|
63
|
+
- slug: finance.payable.created
|
|
64
|
+
name: Finance payable created
|
|
65
|
+
module: finance
|
|
66
|
+
aggregate_type: financial_title
|
|
67
|
+
description: Disparado quando um título a pagar é criado manualmente.
|
|
68
|
+
status: active
|
|
69
|
+
payload_schema:
|
|
70
|
+
id:
|
|
71
|
+
type: number
|
|
72
|
+
description: ID do título a pagar criado
|
|
73
|
+
personId:
|
|
74
|
+
type: number
|
|
75
|
+
description: ID da pessoa (fornecedor/credor)
|
|
76
|
+
totalAmount:
|
|
77
|
+
type: number
|
|
78
|
+
description: Valor total do título
|
|
79
|
+
dueDate:
|
|
80
|
+
type: string
|
|
81
|
+
format: date-time
|
|
82
|
+
description: Data de vencimento
|
|
83
|
+
status:
|
|
84
|
+
type: string
|
|
85
|
+
description: Status do título
|
|
86
|
+
metadata_schema: null
|
|
87
|
+
|
|
88
|
+
- slug: finance.payable.updated
|
|
89
|
+
name: Finance payable updated
|
|
90
|
+
module: finance
|
|
91
|
+
aggregate_type: financial_title
|
|
92
|
+
description: Disparado quando um título a pagar é atualizado.
|
|
93
|
+
status: active
|
|
94
|
+
payload_schema:
|
|
95
|
+
id:
|
|
96
|
+
type: number
|
|
97
|
+
description: ID do título atualizado
|
|
98
|
+
status:
|
|
99
|
+
type: string
|
|
100
|
+
nullable: true
|
|
101
|
+
description: Novo status do título
|
|
102
|
+
totalAmount:
|
|
103
|
+
type: number
|
|
104
|
+
nullable: true
|
|
105
|
+
description: Novo valor total
|
|
106
|
+
metadata_schema: null
|
|
107
|
+
|
|
108
|
+
- slug: finance.receivable.created
|
|
109
|
+
name: Finance receivable created
|
|
110
|
+
module: finance
|
|
111
|
+
aggregate_type: financial_title
|
|
112
|
+
description: Disparado quando um título a receber é criado manualmente.
|
|
113
|
+
status: active
|
|
114
|
+
payload_schema:
|
|
115
|
+
id:
|
|
116
|
+
type: number
|
|
117
|
+
description: ID do título a receber criado
|
|
118
|
+
personId:
|
|
119
|
+
type: number
|
|
120
|
+
description: ID da pessoa (cliente/devedor)
|
|
121
|
+
totalAmount:
|
|
122
|
+
type: number
|
|
123
|
+
description: Valor total do título
|
|
124
|
+
dueDate:
|
|
125
|
+
type: string
|
|
126
|
+
format: date-time
|
|
127
|
+
description: Data de vencimento
|
|
128
|
+
status:
|
|
129
|
+
type: string
|
|
130
|
+
description: Status do título
|
|
131
|
+
metadata_schema: null
|
|
132
|
+
|
|
133
|
+
- slug: finance.receivable.updated
|
|
134
|
+
name: Finance receivable updated
|
|
135
|
+
module: finance
|
|
136
|
+
aggregate_type: financial_title
|
|
137
|
+
description: Disparado quando um título a receber é atualizado.
|
|
138
|
+
status: active
|
|
139
|
+
payload_schema:
|
|
140
|
+
id:
|
|
141
|
+
type: number
|
|
142
|
+
description: ID do título atualizado
|
|
143
|
+
status:
|
|
144
|
+
type: string
|
|
145
|
+
nullable: true
|
|
146
|
+
description: Novo status do título
|
|
147
|
+
totalAmount:
|
|
148
|
+
type: number
|
|
149
|
+
nullable: true
|
|
150
|
+
description: Novo valor total
|
|
151
|
+
metadata_schema: null
|
|
152
|
+
|
|
153
|
+
- slug: finance.bank_account.created
|
|
154
|
+
name: Finance bank account created
|
|
155
|
+
module: finance
|
|
156
|
+
aggregate_type: bank_account
|
|
157
|
+
description: Disparado quando uma conta bancária é criada.
|
|
158
|
+
status: active
|
|
159
|
+
payload_schema:
|
|
160
|
+
id:
|
|
161
|
+
type: number
|
|
162
|
+
description: ID da conta bancária criada
|
|
163
|
+
name:
|
|
164
|
+
type: string
|
|
165
|
+
description: Nome da conta bancária
|
|
166
|
+
bankCode:
|
|
167
|
+
type: string
|
|
168
|
+
nullable: true
|
|
169
|
+
description: Código do banco
|
|
170
|
+
accountNumber:
|
|
171
|
+
type: string
|
|
172
|
+
nullable: true
|
|
173
|
+
description: Número da conta
|
|
174
|
+
metadata_schema: null
|
|
175
|
+
|
|
176
|
+
- slug: finance.bank_account.updated
|
|
177
|
+
name: Finance bank account updated
|
|
178
|
+
module: finance
|
|
179
|
+
aggregate_type: bank_account
|
|
180
|
+
description: Disparado quando uma conta bancária é atualizada.
|
|
181
|
+
status: active
|
|
182
|
+
payload_schema:
|
|
183
|
+
id:
|
|
184
|
+
type: number
|
|
185
|
+
description: ID da conta bancária atualizada
|
|
186
|
+
name:
|
|
187
|
+
type: string
|
|
188
|
+
nullable: true
|
|
189
|
+
description: Novo nome da conta
|
|
190
|
+
metadata_schema: null
|
|
191
|
+
|
|
192
|
+
- slug: finance.bank_account.deleted
|
|
193
|
+
name: Finance bank account deleted
|
|
194
|
+
module: finance
|
|
195
|
+
aggregate_type: bank_account
|
|
196
|
+
description: Disparado quando uma conta bancária é excluída.
|
|
197
|
+
status: active
|
|
198
|
+
payload_schema:
|
|
199
|
+
id:
|
|
200
|
+
type: number
|
|
201
|
+
description: ID da conta bancária excluída
|
|
202
|
+
metadata_schema: null
|
|
203
|
+
|
|
204
|
+
- slug: finance.cost_center.created
|
|
205
|
+
name: Finance cost center created
|
|
206
|
+
module: finance
|
|
207
|
+
aggregate_type: cost_center
|
|
208
|
+
description: Disparado quando um centro de custo é criado.
|
|
209
|
+
status: active
|
|
210
|
+
payload_schema:
|
|
211
|
+
id:
|
|
212
|
+
type: number
|
|
213
|
+
description: ID do centro de custo criado
|
|
214
|
+
name:
|
|
215
|
+
type: string
|
|
216
|
+
description: Nome do centro de custo
|
|
217
|
+
code:
|
|
218
|
+
type: string
|
|
219
|
+
nullable: true
|
|
220
|
+
description: Código do centro de custo
|
|
221
|
+
metadata_schema: null
|
|
222
|
+
|
|
223
|
+
- slug: finance.cost_center.updated
|
|
224
|
+
name: Finance cost center updated
|
|
225
|
+
module: finance
|
|
226
|
+
aggregate_type: cost_center
|
|
227
|
+
description: Disparado quando um centro de custo é atualizado.
|
|
228
|
+
status: active
|
|
229
|
+
payload_schema:
|
|
230
|
+
id:
|
|
231
|
+
type: number
|
|
232
|
+
description: ID do centro de custo atualizado
|
|
233
|
+
name:
|
|
234
|
+
type: string
|
|
235
|
+
nullable: true
|
|
236
|
+
description: Novo nome do centro de custo
|
|
237
|
+
metadata_schema: null
|
|
238
|
+
|
|
239
|
+
- slug: finance.cost_center.deleted
|
|
240
|
+
name: Finance cost center deleted
|
|
241
|
+
module: finance
|
|
242
|
+
aggregate_type: cost_center
|
|
243
|
+
description: Disparado quando um centro de custo é excluído.
|
|
244
|
+
status: active
|
|
245
|
+
payload_schema:
|
|
246
|
+
id:
|
|
247
|
+
type: number
|
|
248
|
+
description: ID do centro de custo excluído
|
|
249
|
+
metadata_schema: null
|
|
250
|
+
|
|
251
|
+
- slug: finance.transfer.created
|
|
252
|
+
name: Finance transfer created
|
|
253
|
+
module: finance
|
|
254
|
+
aggregate_type: transfer
|
|
255
|
+
description: Disparado quando uma transferência entre contas é registrada.
|
|
256
|
+
status: active
|
|
257
|
+
payload_schema:
|
|
258
|
+
id:
|
|
259
|
+
type: number
|
|
260
|
+
description: ID da transferência criada
|
|
261
|
+
fromAccountId:
|
|
262
|
+
type: number
|
|
263
|
+
description: ID da conta de origem
|
|
264
|
+
toAccountId:
|
|
265
|
+
type: number
|
|
266
|
+
description: ID da conta de destino
|
|
267
|
+
amount:
|
|
268
|
+
type: number
|
|
269
|
+
description: Valor transferido
|
|
270
|
+
transferDate:
|
|
271
|
+
type: string
|
|
272
|
+
format: date-time
|
|
273
|
+
description: Data da transferência
|
|
274
|
+
metadata_schema: null
|
|
@@ -300,7 +300,7 @@ function CategoriaSheet({
|
|
|
300
300
|
|
|
301
301
|
return (
|
|
302
302
|
<Sheet open={open} onOpenChange={handleOpenChange}>
|
|
303
|
-
<SheetContent className="w-full sm:max-w-lg">
|
|
303
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-lg">
|
|
304
304
|
<SheetHeader>
|
|
305
305
|
<SheetTitle>
|
|
306
306
|
{editing ? t('sheet.editTitle') : t('sheet.newTitle')}
|
|
@@ -216,7 +216,7 @@ function CentroCustoSheet({
|
|
|
216
216
|
|
|
217
217
|
return (
|
|
218
218
|
<Sheet open={open} onOpenChange={handleOpenChange}>
|
|
219
|
-
<SheetContent className="w-full sm:max-w-lg">
|
|
219
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-lg">
|
|
220
220
|
<SheetHeader>
|
|
221
221
|
<SheetTitle>
|
|
222
222
|
{editingCostCenter ? t('sheet.editTitle') : t('sheet.newTitle')}
|
|
@@ -225,7 +225,7 @@ function CurrencySheet({
|
|
|
225
225
|
|
|
226
226
|
return (
|
|
227
227
|
<Sheet open={open} onOpenChange={handleOpenChange}>
|
|
228
|
-
<SheetContent className="w-full sm:max-w-lg">
|
|
228
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-lg">
|
|
229
229
|
<SheetHeader>
|
|
230
230
|
<SheetTitle>
|
|
231
231
|
{editingCurrency ? t('sheet.editTitle') : t('sheet.newTitle')}
|
|
@@ -212,7 +212,7 @@ function ClosePeriodSheet({
|
|
|
212
212
|
onOpenChange(nextOpen);
|
|
213
213
|
}}
|
|
214
214
|
>
|
|
215
|
-
<SheetContent className="w-full sm:max-w-lg">
|
|
215
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-lg">
|
|
216
216
|
<SheetHeader>
|
|
217
217
|
<SheetTitle>{t('sheet.title')}</SheetTitle>
|
|
218
218
|
<SheetDescription>{t('sheet.description')}</SheetDescription>
|
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
import { Input } from '@/components/ui/input';
|
|
38
38
|
import { InputMoney } from '@/components/ui/input-money';
|
|
39
39
|
import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
|
|
40
|
-
import { Money } from '@/components/ui/money';
|
|
41
40
|
import {
|
|
42
41
|
Select,
|
|
43
42
|
SelectContent,
|
|
@@ -105,6 +104,13 @@ type Currency = {
|
|
|
105
104
|
ativo: boolean;
|
|
106
105
|
};
|
|
107
106
|
|
|
107
|
+
type BankAccountCurrency = {
|
|
108
|
+
id: string;
|
|
109
|
+
code: string;
|
|
110
|
+
symbol: string;
|
|
111
|
+
name: string;
|
|
112
|
+
};
|
|
113
|
+
|
|
108
114
|
type BankAccount = {
|
|
109
115
|
id: string;
|
|
110
116
|
codigo: string;
|
|
@@ -115,7 +121,7 @@ type BankAccount = {
|
|
|
115
121
|
tipo: 'corrente' | 'poupanca' | 'investimento' | 'caixa';
|
|
116
122
|
logoFileId: number | null;
|
|
117
123
|
currencyId: number | null;
|
|
118
|
-
currency:
|
|
124
|
+
currency: BankAccountCurrency | null;
|
|
119
125
|
saldoAtual: number;
|
|
120
126
|
saldoConciliado: number;
|
|
121
127
|
ativo: boolean;
|
|
@@ -147,6 +153,14 @@ type BankAccountDraftPayload = {
|
|
|
147
153
|
|
|
148
154
|
const BANK_ACCOUNT_FORM_DRAFT_STORAGE_KEY = 'finance-bank-account-form-draft';
|
|
149
155
|
|
|
156
|
+
function formatCurrency(value: number, currencyCode: string, locale: string) {
|
|
157
|
+
const localeTag = locale.startsWith('pt') ? 'pt-BR' : 'en-US';
|
|
158
|
+
return new Intl.NumberFormat(localeTag, {
|
|
159
|
+
style: 'currency',
|
|
160
|
+
currency: currencyCode,
|
|
161
|
+
}).format(value);
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
function BankAccountLogo({
|
|
151
165
|
account,
|
|
152
166
|
icon: Icon,
|
|
@@ -1049,40 +1063,52 @@ export default function ContasBancariasPage() {
|
|
|
1049
1063
|
caixa: { label: t('types.caixa'), icon: Wallet },
|
|
1050
1064
|
};
|
|
1051
1065
|
|
|
1052
|
-
const saldoTotal = accounts
|
|
1053
|
-
.filter((c) => c.ativo)
|
|
1054
|
-
.reduce((acc, c) => acc + c.saldoAtual, 0);
|
|
1055
|
-
|
|
1056
|
-
const saldoConciliadoTotal = accounts
|
|
1057
|
-
.filter((c) => c.ativo)
|
|
1058
|
-
.reduce((acc, c) => acc + c.saldoConciliado, 0);
|
|
1059
1066
|
const activeAccountsCount = accounts.filter((c) => c.ativo).length;
|
|
1060
1067
|
const inactiveAccountsCount = accounts.length - activeAccountsCount;
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
}),
|
|
1069
|
-
icon: Landmark,
|
|
1070
|
-
layout: 'compact' as const,
|
|
1071
|
-
},
|
|
1072
|
-
{
|
|
1073
|
-
key: 'reconciled',
|
|
1074
|
-
title: t('cards.reconciledBalance'),
|
|
1075
|
-
value: <Money value={saldoConciliadoTotal} />,
|
|
1076
|
-
description: `${t('cards.difference')}: ${new Intl.NumberFormat(
|
|
1077
|
-
currentLocaleCode === 'pt' ? 'pt-BR' : 'en-US',
|
|
1068
|
+
|
|
1069
|
+
// Group active account balances by currency (accounts without currency default to BRL)
|
|
1070
|
+
const balanceByCurrency = accounts
|
|
1071
|
+
.filter((c) => c.ativo)
|
|
1072
|
+
.reduce<
|
|
1073
|
+
Record<
|
|
1074
|
+
string,
|
|
1078
1075
|
{
|
|
1079
|
-
|
|
1080
|
-
|
|
1076
|
+
total: number;
|
|
1077
|
+
reconciled: number;
|
|
1078
|
+
symbol: string;
|
|
1079
|
+
code: string;
|
|
1080
|
+
name: string;
|
|
1081
|
+
count: number;
|
|
1081
1082
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1083
|
+
>
|
|
1084
|
+
>((acc, c) => {
|
|
1085
|
+
const code = c.currency?.code ?? 'BRL';
|
|
1086
|
+
const symbol = c.currency?.symbol ?? 'R$';
|
|
1087
|
+
const name = c.currency?.name ?? 'Real Brasileiro';
|
|
1088
|
+
if (!acc[code]) {
|
|
1089
|
+
acc[code] = { total: 0, reconciled: 0, symbol, code, name, count: 0 };
|
|
1090
|
+
}
|
|
1091
|
+
acc[code].total += c.saldoAtual;
|
|
1092
|
+
acc[code].reconciled += c.saldoConciliado;
|
|
1093
|
+
acc[code].count += 1;
|
|
1094
|
+
return acc;
|
|
1095
|
+
}, {});
|
|
1096
|
+
|
|
1097
|
+
const currencyGroups = Object.values(balanceByCurrency);
|
|
1098
|
+
|
|
1099
|
+
const summaryCards = [
|
|
1100
|
+
...currencyGroups.map((g) => ({
|
|
1101
|
+
key: `balance-${g.code}`,
|
|
1102
|
+
title: `${t('cards.totalBalance')} (${g.code})`,
|
|
1103
|
+
value: (
|
|
1104
|
+
<span className="tabular-nums">
|
|
1105
|
+
{formatCurrency(g.total, g.code, currentLocaleCode)}
|
|
1106
|
+
</span>
|
|
1107
|
+
),
|
|
1108
|
+
description: `${t('cards.reconciledBalance')}: ${formatCurrency(g.reconciled, g.code, currentLocaleCode)}`,
|
|
1109
|
+
icon: Landmark,
|
|
1084
1110
|
layout: 'compact' as const,
|
|
1085
|
-
},
|
|
1111
|
+
})),
|
|
1086
1112
|
{
|
|
1087
1113
|
key: 'accounts',
|
|
1088
1114
|
title: t('cards.accountsOverview'),
|
|
@@ -1175,7 +1201,10 @@ export default function ContasBancariasPage() {
|
|
|
1175
1201
|
</AlertDialogContent>
|
|
1176
1202
|
</AlertDialog>
|
|
1177
1203
|
|
|
1178
|
-
<KpiCardsGrid
|
|
1204
|
+
<KpiCardsGrid
|
|
1205
|
+
items={summaryCards}
|
|
1206
|
+
columns={Math.min(summaryCards.length, 4) as 2 | 3 | 4}
|
|
1207
|
+
/>
|
|
1179
1208
|
|
|
1180
1209
|
<FinancePageSection variant="flat" contentClassName="p-0">
|
|
1181
1210
|
{accounts.length > 0 ? (
|
|
@@ -1236,15 +1265,25 @@ export default function ContasBancariasPage() {
|
|
|
1236
1265
|
<p className="text-sm text-muted-foreground">
|
|
1237
1266
|
{t('accountCard.currentBalance')}
|
|
1238
1267
|
</p>
|
|
1239
|
-
<p className="text-2xl font-bold">
|
|
1240
|
-
|
|
1268
|
+
<p className="text-2xl font-bold tabular-nums">
|
|
1269
|
+
{formatCurrency(
|
|
1270
|
+
conta.saldoAtual,
|
|
1271
|
+
conta.currency?.code ?? 'BRL',
|
|
1272
|
+
currentLocaleCode
|
|
1273
|
+
)}
|
|
1241
1274
|
</p>
|
|
1242
1275
|
</div>
|
|
1243
1276
|
<div className="flex items-center justify-between text-sm">
|
|
1244
1277
|
<span className="text-muted-foreground">
|
|
1245
1278
|
{t('accountCard.reconciledBalance')}
|
|
1246
1279
|
</span>
|
|
1247
|
-
<
|
|
1280
|
+
<span className="tabular-nums">
|
|
1281
|
+
{formatCurrency(
|
|
1282
|
+
conta.saldoConciliado,
|
|
1283
|
+
conta.currency?.code ?? 'BRL',
|
|
1284
|
+
currentLocaleCode
|
|
1285
|
+
)}
|
|
1286
|
+
</span>
|
|
1248
1287
|
</div>
|
|
1249
1288
|
{diferenca !== 0 ? (
|
|
1250
1289
|
<div className="flex items-center justify-between text-sm">
|
|
@@ -1253,10 +1292,17 @@ export default function ContasBancariasPage() {
|
|
|
1253
1292
|
</span>
|
|
1254
1293
|
<span
|
|
1255
1294
|
className={
|
|
1256
|
-
diferenca > 0
|
|
1295
|
+
diferenca > 0
|
|
1296
|
+
? 'tabular-nums text-green-600'
|
|
1297
|
+
: 'tabular-nums text-red-600'
|
|
1257
1298
|
}
|
|
1258
1299
|
>
|
|
1259
|
-
|
|
1300
|
+
{diferenca > 0 ? '+' : '-'}
|
|
1301
|
+
{formatCurrency(
|
|
1302
|
+
Math.abs(diferenca),
|
|
1303
|
+
conta.currency?.code ?? 'BRL',
|
|
1304
|
+
currentLocaleCode
|
|
1305
|
+
)}
|
|
1260
1306
|
</span>
|
|
1261
1307
|
</div>
|
|
1262
1308
|
) : null}
|
|
@@ -30,6 +30,7 @@ interface FinanceData {
|
|
|
30
30
|
saldoAtual?: number | null;
|
|
31
31
|
saldoConciliado?: number | null;
|
|
32
32
|
ativo?: boolean | null;
|
|
33
|
+
currency?: { code: string; symbol: string } | null;
|
|
33
34
|
}>;
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -68,13 +69,22 @@ export default function BankReconciliationStatus({
|
|
|
68
69
|
difference: Math.abs(
|
|
69
70
|
Number(account.saldoAtual || 0) - Number(account.saldoConciliado || 0)
|
|
70
71
|
),
|
|
72
|
+
currencyCode: account.currency?.code ?? 'BRL',
|
|
73
|
+
currencySymbol: account.currency?.symbol ?? 'R$',
|
|
71
74
|
}))
|
|
72
75
|
.sort((a, b) => b.difference - a.difference);
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
77
|
+
// Group differences by currency — never mix different currencies in a single total
|
|
78
|
+
const differencesByCurrency = accountDiffs.reduce<
|
|
79
|
+
Record<string, { total: number; symbol: string }>
|
|
80
|
+
>((acc, account) => {
|
|
81
|
+
const code = account.currencyCode;
|
|
82
|
+
if (!acc[code]) {
|
|
83
|
+
acc[code] = { total: 0, symbol: account.currencySymbol };
|
|
84
|
+
}
|
|
85
|
+
acc[code].total += account.difference;
|
|
86
|
+
return acc;
|
|
87
|
+
}, {});
|
|
78
88
|
|
|
79
89
|
return (
|
|
80
90
|
<WidgetWrapper
|
|
@@ -109,9 +119,20 @@ export default function BankReconciliationStatus({
|
|
|
109
119
|
<p className="text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">
|
|
110
120
|
{t('bankReconciliation.difference')}
|
|
111
121
|
</p>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
{Object.entries(differencesByCurrency).length > 0 ? (
|
|
123
|
+
Object.entries(differencesByCurrency).map(([code, entry]) => (
|
|
124
|
+
<p key={code} className="text-lg font-semibold text-foreground tabular-nums">
|
|
125
|
+
{new Intl.NumberFormat('pt-BR', {
|
|
126
|
+
style: 'currency',
|
|
127
|
+
currency: code,
|
|
128
|
+
}).format(entry.total)}
|
|
129
|
+
</p>
|
|
130
|
+
))
|
|
131
|
+
) : (
|
|
132
|
+
<p className="text-lg font-semibold text-foreground tabular-nums">
|
|
133
|
+
<Money value={0} />
|
|
134
|
+
</p>
|
|
135
|
+
)}
|
|
115
136
|
</div>
|
|
116
137
|
|
|
117
138
|
<div className="space-y-2">
|
|
@@ -123,8 +144,11 @@ export default function BankReconciliationStatus({
|
|
|
123
144
|
<span className="truncate pr-3 text-sm text-foreground">
|
|
124
145
|
{account.name}
|
|
125
146
|
</span>
|
|
126
|
-
<span className="text-sm font-medium text-muted-foreground">
|
|
127
|
-
|
|
147
|
+
<span className="text-sm font-medium text-muted-foreground tabular-nums">
|
|
148
|
+
{new Intl.NumberFormat('pt-BR', {
|
|
149
|
+
style: 'currency',
|
|
150
|
+
currency: account.currencyCode,
|
|
151
|
+
}).format(account.difference)}
|
|
128
152
|
</span>
|
|
129
153
|
</div>
|
|
130
154
|
))}
|
|
@@ -14,6 +14,10 @@ interface CashBalanceKpiProps {
|
|
|
14
14
|
interface FinanceData {
|
|
15
15
|
kpis?: {
|
|
16
16
|
saldoCaixa: number;
|
|
17
|
+
saldoCaixaPorMoeda?: Record<
|
|
18
|
+
string,
|
|
19
|
+
{ total: number; reconciled: number; symbol: string; name: string }
|
|
20
|
+
>;
|
|
17
21
|
};
|
|
18
22
|
}
|
|
19
23
|
|
|
@@ -29,11 +33,11 @@ export default function CashBalanceKpi({
|
|
|
29
33
|
queryKey: 'finance-kpi-cash-balance',
|
|
30
34
|
});
|
|
31
35
|
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const saldoCaixaPorMoeda = data?.kpis?.saldoCaixaPorMoeda ?? {};
|
|
37
|
+
const currencyEntries = Object.entries(saldoCaixaPorMoeda);
|
|
38
|
+
// Fallback to the legacy single-value when the new field is absent
|
|
39
|
+
const legacyValue = data?.kpis?.saldoCaixa ?? 0;
|
|
40
|
+
const hasCurrencyBreakdown = currencyEntries.length > 0;
|
|
37
41
|
|
|
38
42
|
return (
|
|
39
43
|
<WidgetWrapper
|
|
@@ -48,13 +52,30 @@ export default function CashBalanceKpi({
|
|
|
48
52
|
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-emerald-100/80">
|
|
49
53
|
<Wallet className="h-4.5 w-4.5 text-emerald-600" />
|
|
50
54
|
</div>
|
|
51
|
-
<div className="flex min-w-0 flex-1 flex-col justify-center">
|
|
55
|
+
<div className="flex min-w-0 flex-1 flex-col justify-center gap-0.5">
|
|
52
56
|
<span className="text-[9px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">
|
|
53
57
|
{t('kpis.cashBalance.title')}
|
|
54
58
|
</span>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
{hasCurrencyBreakdown ? (
|
|
60
|
+
currencyEntries.map(([code, entry]) => (
|
|
61
|
+
<span
|
|
62
|
+
key={code}
|
|
63
|
+
className="truncate text-base font-bold leading-none tracking-tight text-foreground sm:text-lg"
|
|
64
|
+
>
|
|
65
|
+
{new Intl.NumberFormat('pt-BR', {
|
|
66
|
+
style: 'currency',
|
|
67
|
+
currency: code,
|
|
68
|
+
}).format(entry.total)}
|
|
69
|
+
</span>
|
|
70
|
+
))
|
|
71
|
+
) : (
|
|
72
|
+
<span className="truncate text-base font-bold leading-none tracking-tight text-foreground sm:text-lg">
|
|
73
|
+
{new Intl.NumberFormat('pt-BR', {
|
|
74
|
+
style: 'currency',
|
|
75
|
+
currency: 'BRL',
|
|
76
|
+
}).format(legacyValue)}
|
|
77
|
+
</span>
|
|
78
|
+
)}
|
|
58
79
|
<span className="truncate text-[9px] text-muted-foreground">
|
|
59
80
|
{t('kpis.cashBalance.description')}
|
|
60
81
|
</span>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/finance",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.326",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"@nestjs/core": "^11",
|
|
10
10
|
"@nestjs/jwt": "^11",
|
|
11
11
|
"@nestjs/mapped-types": "*",
|
|
12
|
-
"@hed-hog/api": "0.0.
|
|
12
|
+
"@hed-hog/api-prisma": "0.0.6",
|
|
13
13
|
"@hed-hog/api-locale": "0.0.14",
|
|
14
|
+
"@hed-hog/contact": "0.0.326",
|
|
15
|
+
"@hed-hog/tag": "0.0.326",
|
|
16
|
+
"@hed-hog/api": "0.0.8",
|
|
14
17
|
"@hed-hog/api-pagination": "0.0.7",
|
|
15
|
-
"@hed-hog/core": "0.0.
|
|
16
|
-
"@hed-hog/api-types": "0.0.1"
|
|
17
|
-
"@hed-hog/contact": "0.0.322",
|
|
18
|
-
"@hed-hog/api-prisma": "0.0.6",
|
|
19
|
-
"@hed-hog/tag": "0.0.322"
|
|
18
|
+
"@hed-hog/core": "0.0.326",
|
|
19
|
+
"@hed-hog/api-types": "0.0.1"
|
|
20
20
|
},
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|