@hed-hog/finance 0.0.223 → 0.0.225

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/dto/create-financial-title.dto.d.ts +19 -0
  2. package/dist/dto/create-financial-title.dto.d.ts.map +1 -0
  3. package/dist/dto/create-financial-title.dto.js +128 -0
  4. package/dist/dto/create-financial-title.dto.js.map +1 -0
  5. package/dist/finance.controller.d.ts +276 -0
  6. package/dist/finance.controller.d.ts.map +1 -0
  7. package/dist/finance.controller.js +110 -0
  8. package/dist/finance.controller.js.map +1 -0
  9. package/dist/finance.module.d.ts.map +1 -1
  10. package/dist/finance.module.js +8 -3
  11. package/dist/finance.module.js.map +1 -1
  12. package/dist/finance.service.d.ts +295 -0
  13. package/dist/finance.service.d.ts.map +1 -0
  14. package/dist/finance.service.js +416 -0
  15. package/dist/finance.service.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -0
  19. package/dist/index.js.map +1 -1
  20. package/hedhog/data/menu.yaml +72 -25
  21. package/hedhog/data/route.yaml +55 -1
  22. package/hedhog/frontend/app/_lib/formatters.ts.ejs +20 -0
  23. package/hedhog/frontend/app/_lib/use-finance-data.ts.ejs +87 -0
  24. package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +290 -0
  25. package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +410 -0
  26. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +388 -0
  27. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +423 -0
  28. package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +411 -0
  29. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +385 -0
  30. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +296 -0
  31. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +427 -0
  32. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +273 -0
  33. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +253 -0
  34. package/hedhog/frontend/app/page.tsx.ejs +338 -17
  35. package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +298 -0
  36. package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +225 -0
  37. package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +338 -0
  38. package/hedhog/frontend/messages/en.json +776 -0
  39. package/hedhog/frontend/messages/pt.json +776 -0
  40. package/hedhog/query/constraints.sql +169 -0
  41. package/package.json +3 -3
  42. package/src/dto/create-financial-title.dto.ts +142 -0
  43. package/src/finance.controller.ts +89 -0
  44. package/src/finance.module.ts +8 -3
  45. package/src/finance.service.ts +529 -0
  46. package/src/index.ts +4 -0
@@ -0,0 +1,338 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Badge } from '@/components/ui/badge';
5
+ import { Button } from '@/components/ui/button';
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from '@/components/ui/card';
13
+ import { Label } from '@/components/ui/label';
14
+ import { Money } from '@/components/ui/money';
15
+ import { Slider } from '@/components/ui/slider';
16
+ import {
17
+ Check,
18
+ Lightbulb,
19
+ Target,
20
+ TrendingDown,
21
+ TrendingUp,
22
+ } from 'lucide-react';
23
+ import { useTranslations } from 'next-intl';
24
+ import { useState } from 'react';
25
+ import { formatarMoeda } from '../../_lib/formatters';
26
+ import { useFinanceData } from '../../_lib/use-finance-data';
27
+
28
+ export default function CenariosPage() {
29
+ const t = useTranslations('finance.ScenariosPage');
30
+ const { data } = useFinanceData();
31
+ const { cenarios, kpis } = data;
32
+
33
+ const [cenarioAtivo, setCenarioAtivo] = useState('1');
34
+ const [premissas, setPremissas] = useState({
35
+ atrasoMedio: 5,
36
+ taxaInadimplencia: 3,
37
+ crescimentoReceita: 5,
38
+ });
39
+
40
+ const cenarioSelecionado = cenarios.find((c) => c.id === cenarioAtivo);
41
+
42
+ const impactoSaldo =
43
+ kpis.saldoCaixa * (1 + premissas.crescimentoReceita / 100);
44
+ const impactoInadimplencia =
45
+ kpis.inadimplencia * (1 + premissas.taxaInadimplencia / 10);
46
+
47
+ return (
48
+ <Page>
49
+ <PageHeader
50
+ title={t('header.title')}
51
+ description={t('header.description')}
52
+ breadcrumbs={[
53
+ { label: t('breadcrumbs.home'), href: '/' },
54
+ { label: t('breadcrumbs.finance'), href: '/finance' },
55
+ { label: t('breadcrumbs.current') },
56
+ ]}
57
+ />
58
+
59
+ <div className="grid gap-4 md:grid-cols-3">
60
+ {cenarios.map((cenario) => {
61
+ const isAtivo = cenario.id === cenarioAtivo;
62
+ const Icon =
63
+ cenario.nome === 'Otimista'
64
+ ? TrendingUp
65
+ : cenario.nome === 'Pessimista'
66
+ ? TrendingDown
67
+ : Target;
68
+
69
+ return (
70
+ <Card
71
+ key={cenario.id}
72
+ className={`cursor-pointer transition-all ${
73
+ isAtivo
74
+ ? 'border-primary ring-2 ring-primary/20'
75
+ : 'hover:border-muted-foreground/50'
76
+ }`}
77
+ onClick={() => {
78
+ setCenarioAtivo(cenario.id);
79
+ setPremissas({
80
+ atrasoMedio: cenario.atrasoMedio,
81
+ taxaInadimplencia: cenario.taxaInadimplencia,
82
+ crescimentoReceita: cenario.crescimentoReceita,
83
+ });
84
+ }}
85
+ >
86
+ <CardHeader>
87
+ <div className="flex items-center justify-between">
88
+ <div className="flex items-center gap-3">
89
+ <div
90
+ className={`flex h-10 w-10 items-center justify-center rounded-lg ${
91
+ cenario.nome === 'Otimista'
92
+ ? 'bg-green-100'
93
+ : cenario.nome === 'Pessimista'
94
+ ? 'bg-red-100'
95
+ : 'bg-blue-100'
96
+ }`}
97
+ >
98
+ <Icon
99
+ className={`h-5 w-5 ${
100
+ cenario.nome === 'Otimista'
101
+ ? 'text-green-600'
102
+ : cenario.nome === 'Pessimista'
103
+ ? 'text-red-600'
104
+ : 'text-blue-600'
105
+ }`}
106
+ />
107
+ </div>
108
+ <div>
109
+ <CardTitle className="text-base">
110
+ {cenario.nome}
111
+ </CardTitle>
112
+ <CardDescription>{cenario.descricao}</CardDescription>
113
+ </div>
114
+ </div>
115
+ {isAtivo && (
116
+ <Badge className="bg-primary text-primary-foreground">
117
+ <Check className="mr-1 h-3 w-3" />
118
+ {t('cards.active')}
119
+ </Badge>
120
+ )}
121
+ </div>
122
+ </CardHeader>
123
+ <CardContent>
124
+ <div className="grid grid-cols-3 gap-4 text-center">
125
+ <div>
126
+ <p className="text-xs text-muted-foreground">
127
+ {t('cards.delay')}
128
+ </p>
129
+ <p className="font-semibold">
130
+ {t('cards.days', { value: cenario.atrasoMedio })}
131
+ </p>
132
+ </div>
133
+ <div>
134
+ <p className="text-xs text-muted-foreground">
135
+ {t('cards.defaultRate')}
136
+ </p>
137
+ <p className="font-semibold">
138
+ {cenario.taxaInadimplencia}%
139
+ </p>
140
+ </div>
141
+ <div>
142
+ <p className="text-xs text-muted-foreground">
143
+ {t('cards.growth')}
144
+ </p>
145
+ <p
146
+ className={`font-semibold ${
147
+ cenario.crescimentoReceita >= 0
148
+ ? 'text-green-600'
149
+ : 'text-red-600'
150
+ }`}
151
+ >
152
+ {cenario.crescimentoReceita > 0 && '+'}
153
+ {cenario.crescimentoReceita}%
154
+ </p>
155
+ </div>
156
+ </div>
157
+ </CardContent>
158
+ </Card>
159
+ );
160
+ })}
161
+ </div>
162
+
163
+ <div className="grid gap-6 lg:grid-cols-2">
164
+ <Card>
165
+ <CardHeader>
166
+ <CardTitle className="flex items-center gap-2">
167
+ <Lightbulb className="h-4 w-4" />
168
+ {t('assumptions.title')}
169
+ </CardTitle>
170
+ <CardDescription>
171
+ {t('assumptions.description', {
172
+ scenario: cenarioSelecionado?.nome ?? '',
173
+ })}
174
+ </CardDescription>
175
+ </CardHeader>
176
+ <CardContent className="space-y-6">
177
+ <div className="space-y-4">
178
+ <div className="space-y-2">
179
+ <div className="flex items-center justify-between">
180
+ <Label>{t('assumptions.averageDelay')}</Label>
181
+ <span className="text-sm font-medium">
182
+ {t('cards.days', { value: premissas.atrasoMedio })}
183
+ </span>
184
+ </div>
185
+ <Slider
186
+ value={[premissas.atrasoMedio]}
187
+ onValueChange={([value]) =>
188
+ setPremissas({
189
+ ...premissas,
190
+ atrasoMedio: value ?? premissas.atrasoMedio,
191
+ })
192
+ }
193
+ max={30}
194
+ step={1}
195
+ />
196
+ </div>
197
+
198
+ <div className="space-y-2">
199
+ <div className="flex items-center justify-between">
200
+ <Label>{t('assumptions.defaultRate')}</Label>
201
+ <span className="text-sm font-medium">
202
+ {premissas.taxaInadimplencia}%
203
+ </span>
204
+ </div>
205
+ <Slider
206
+ value={[premissas.taxaInadimplencia]}
207
+ onValueChange={([value]) =>
208
+ setPremissas({
209
+ ...premissas,
210
+ taxaInadimplencia: value ?? premissas.taxaInadimplencia,
211
+ })
212
+ }
213
+ max={15}
214
+ step={0.5}
215
+ />
216
+ </div>
217
+
218
+ <div className="space-y-2">
219
+ <div className="flex items-center justify-between">
220
+ <Label>{t('assumptions.revenueGrowth')}</Label>
221
+ <span
222
+ className={`text-sm font-medium ${
223
+ premissas.crescimentoReceita >= 0
224
+ ? 'text-green-600'
225
+ : 'text-red-600'
226
+ }`}
227
+ >
228
+ {premissas.crescimentoReceita > 0 && '+'}
229
+ {premissas.crescimentoReceita}%
230
+ </span>
231
+ </div>
232
+ <Slider
233
+ value={[premissas.crescimentoReceita + 10]} // Offset para permitir valores negativos
234
+ onValueChange={([value]) =>
235
+ setPremissas({
236
+ ...premissas,
237
+ crescimentoReceita:
238
+ (value ?? premissas.crescimentoReceita + 10) - 10,
239
+ })
240
+ }
241
+ max={25}
242
+ step={1}
243
+ />
244
+ </div>
245
+ </div>
246
+
247
+ <div className="flex justify-end gap-2 pt-4">
248
+ <Button
249
+ variant="outline"
250
+ onClick={() => {
251
+ if (cenarioSelecionado) {
252
+ setPremissas({
253
+ atrasoMedio: cenarioSelecionado.atrasoMedio,
254
+ taxaInadimplencia: cenarioSelecionado.taxaInadimplencia,
255
+ crescimentoReceita: cenarioSelecionado.crescimentoReceita,
256
+ });
257
+ }
258
+ }}
259
+ >
260
+ {t('assumptions.restore')}
261
+ </Button>
262
+ <Button>{t('assumptions.apply')}</Button>
263
+ </div>
264
+ </CardContent>
265
+ </Card>
266
+
267
+ <Card>
268
+ <CardHeader>
269
+ <CardTitle>{t('impact.title')}</CardTitle>
270
+ <CardDescription>{t('impact.description')}</CardDescription>
271
+ </CardHeader>
272
+ <CardContent>
273
+ <div className="space-y-6">
274
+ <div className="rounded-lg border p-4">
275
+ <p className="text-sm text-muted-foreground">
276
+ {t('impact.projectedBalance30d')}
277
+ </p>
278
+ <p className="text-3xl font-bold">
279
+ <Money value={impactoSaldo} />
280
+ </p>
281
+ <p
282
+ className={`text-sm ${
283
+ impactoSaldo >= kpis.saldoCaixa
284
+ ? 'text-green-600'
285
+ : 'text-red-600'
286
+ }`}
287
+ >
288
+ {impactoSaldo >= kpis.saldoCaixa ? '+' : ''}
289
+ {t('impact.vsCurrent', {
290
+ value: formatarMoeda(impactoSaldo - kpis.saldoCaixa),
291
+ })}
292
+ </p>
293
+ </div>
294
+
295
+ <div className="rounded-lg border p-4">
296
+ <p className="text-sm text-muted-foreground">
297
+ {t('impact.projectedDefault')}
298
+ </p>
299
+ <p className="text-3xl font-bold text-red-600">
300
+ <Money value={impactoInadimplencia} />
301
+ </p>
302
+ <p className="text-sm text-muted-foreground">
303
+ {t('impact.vsCurrent', {
304
+ value: formatarMoeda(
305
+ impactoInadimplencia - kpis.inadimplencia
306
+ ),
307
+ })}
308
+ </p>
309
+ </div>
310
+
311
+ <div className="rounded-lg border p-4">
312
+ <p className="text-sm text-muted-foreground">
313
+ {t('impact.cashFlowForecast')}
314
+ </p>
315
+ <p
316
+ className={`text-3xl font-bold ${
317
+ premissas.crescimentoReceita >= 0
318
+ ? 'text-green-600'
319
+ : 'text-red-600'
320
+ }`}
321
+ >
322
+ {premissas.crescimentoReceita >= 0
323
+ ? t('impact.positive')
324
+ : t('impact.negative')}
325
+ </p>
326
+ <p className="text-sm text-muted-foreground">
327
+ {t('impact.withGrowth', {
328
+ value: premissas.crescimentoReceita,
329
+ })}
330
+ </p>
331
+ </div>
332
+ </div>
333
+ </CardContent>
334
+ </Card>
335
+ </div>
336
+ </Page>
337
+ );
338
+ }