@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
@@ -1,5 +1,59 @@
1
- - url: /finance
1
+ - url: /finance/data
2
2
  method: GET
3
+ relations:
4
+ role:
5
+ - where:
6
+ slug: admin
7
+ - where:
8
+ slug: admin-finance
9
+
10
+ - url: /finance/accounts-payable/installments
11
+ method: GET
12
+ relations:
13
+ role:
14
+ - where:
15
+ slug: admin
16
+ - where:
17
+ slug: admin-finance
18
+
19
+ - url: /finance/accounts-payable/installments/:id
20
+ method: GET
21
+ relations:
22
+ role:
23
+ - where:
24
+ slug: admin
25
+ - where:
26
+ slug: admin-finance
27
+
28
+ - url: /finance/accounts-payable/installments
29
+ method: POST
30
+ relations:
31
+ role:
32
+ - where:
33
+ slug: admin
34
+ - where:
35
+ slug: admin-finance
36
+
37
+ - url: /finance/accounts-receivable/installments
38
+ method: GET
39
+ relations:
40
+ role:
41
+ - where:
42
+ slug: admin
43
+ - where:
44
+ slug: admin-finance
45
+
46
+ - url: /finance/accounts-receivable/installments/:id
47
+ method: GET
48
+ relations:
49
+ role:
50
+ - where:
51
+ slug: admin
52
+ - where:
53
+ slug: admin-finance
54
+
55
+ - url: /finance/accounts-receivable/installments
56
+ method: POST
3
57
  relations:
4
58
  role:
5
59
  - where:
@@ -0,0 +1,20 @@
1
+ export function formatarData(value?: string | Date | null) {
2
+ if (!value) {
3
+ return '-';
4
+ }
5
+
6
+ const date = value instanceof Date ? value : new Date(value);
7
+
8
+ if (Number.isNaN(date.getTime())) {
9
+ return String(value);
10
+ }
11
+
12
+ return new Intl.DateTimeFormat('pt-BR').format(date);
13
+ }
14
+
15
+ export function formatarMoeda(value?: number | null) {
16
+ return new Intl.NumberFormat('pt-BR', {
17
+ style: 'currency',
18
+ currency: 'BRL',
19
+ }).format(value || 0);
20
+ }
@@ -0,0 +1,87 @@
1
+ 'use client';
2
+
3
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
4
+
5
+ type FinanceData = {
6
+ kpis: {
7
+ saldoCaixa: number;
8
+ aPagar30dias: number;
9
+ aPagar7dias: number;
10
+ aReceber30dias: number;
11
+ aReceber7dias: number;
12
+ inadimplencia: number;
13
+ };
14
+ fluxoCaixaPrevisto: any[];
15
+ titulosPagar: any[];
16
+ titulosReceber: any[];
17
+ extratos: any[];
18
+ contasBancarias: any[];
19
+ pessoas: any[];
20
+ categorias: any[];
21
+ centrosCusto: any[];
22
+ aprovacoesPendentes: any[];
23
+ agingInadimplencia: any[];
24
+ cenarios: any[];
25
+ transferencias: any[];
26
+ tags: any[];
27
+ logsAuditoria: any[];
28
+ recebiveis: any[];
29
+ adquirentes: any[];
30
+ historicoContatos: any[];
31
+ entradasPrevistas: any[];
32
+ saidasPrevistas: any[];
33
+ };
34
+
35
+ const EMPTY_FINANCE_DATA: FinanceData = {
36
+ kpis: {
37
+ saldoCaixa: 0,
38
+ aPagar30dias: 0,
39
+ aPagar7dias: 0,
40
+ aReceber30dias: 0,
41
+ aReceber7dias: 0,
42
+ inadimplencia: 0,
43
+ },
44
+ fluxoCaixaPrevisto: [],
45
+ titulosPagar: [],
46
+ titulosReceber: [],
47
+ extratos: [],
48
+ contasBancarias: [],
49
+ pessoas: [],
50
+ categorias: [],
51
+ centrosCusto: [],
52
+ aprovacoesPendentes: [],
53
+ agingInadimplencia: [],
54
+ cenarios: [],
55
+ transferencias: [],
56
+ tags: [],
57
+ logsAuditoria: [],
58
+ recebiveis: [],
59
+ adquirentes: [],
60
+ historicoContatos: [],
61
+ entradasPrevistas: [],
62
+ saidasPrevistas: [],
63
+ };
64
+
65
+ export function useFinanceData() {
66
+ const { request } = useApp();
67
+
68
+ return useQuery<FinanceData>({
69
+ queryKey: ['finance-data'],
70
+ queryFn: async () => {
71
+ try {
72
+ const response = await request({
73
+ url: '/finance/data',
74
+ method: 'GET',
75
+ });
76
+
77
+ return {
78
+ ...EMPTY_FINANCE_DATA,
79
+ ...(response?.data || {}),
80
+ } as FinanceData;
81
+ } catch {
82
+ return EMPTY_FINANCE_DATA;
83
+ }
84
+ },
85
+ initialData: EMPTY_FINANCE_DATA,
86
+ });
87
+ }
@@ -0,0 +1,290 @@
1
+ 'use client';
2
+
3
+ import { Badge } from '@/components/ui/badge';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@/components/ui/card';
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogFooter,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ DialogTrigger,
20
+ } from '@/components/ui/dialog';
21
+ import { Label } from '@/components/ui/label';
22
+ import { Money } from '@/components/ui/money';
23
+
24
+ import { Page, PageHeader } from '@/components/entity-list';
25
+ import {
26
+ Table,
27
+ TableBody,
28
+ TableCell,
29
+ TableHead,
30
+ TableHeader,
31
+ TableRow,
32
+ } from '@/components/ui/table';
33
+ import { Textarea } from '@/components/ui/textarea';
34
+ import { AlertTriangle, CheckCircle, Clock, XCircle } from 'lucide-react';
35
+ import { useTranslations } from 'next-intl';
36
+ import { useEffect, useState } from 'react';
37
+ import { formatarData } from '../../_lib/formatters';
38
+ import { useFinanceData } from '../../_lib/use-finance-data';
39
+
40
+ function AprovacaoDialog({
41
+ tipo,
42
+ onConfirm,
43
+ t,
44
+ }: {
45
+ tipo: 'aprovar' | 'reprovar';
46
+ onConfirm: () => void;
47
+ t: ReturnType<typeof useTranslations>;
48
+ }) {
49
+ const isAprovar = tipo === 'aprovar';
50
+
51
+ return (
52
+ <Dialog>
53
+ <DialogTrigger asChild>
54
+ <Button variant={isAprovar ? 'default' : 'outline'} size="sm">
55
+ {isAprovar ? (
56
+ <>
57
+ <CheckCircle className="mr-2 h-4 w-4" />
58
+ {t('dialog.approveAction')}
59
+ </>
60
+ ) : (
61
+ <>
62
+ <XCircle className="mr-2 h-4 w-4" />
63
+ {t('dialog.rejectAction')}
64
+ </>
65
+ )}
66
+ </Button>
67
+ </DialogTrigger>
68
+ <DialogContent>
69
+ <DialogHeader>
70
+ <DialogTitle>
71
+ {isAprovar ? t('dialog.approveTitle') : t('dialog.rejectTitle')}
72
+ </DialogTitle>
73
+ <DialogDescription>
74
+ {isAprovar
75
+ ? t('dialog.approveDescription')
76
+ : t('dialog.rejectDescription')}
77
+ </DialogDescription>
78
+ </DialogHeader>
79
+ <div className="space-y-4">
80
+ <div className="space-y-2">
81
+ <Label htmlFor="comentario">{t('dialog.comment')}</Label>
82
+ <Textarea
83
+ id="comentario"
84
+ placeholder={
85
+ isAprovar
86
+ ? t('dialog.optionalCommentPlaceholder')
87
+ : t('dialog.rejectReasonPlaceholder')
88
+ }
89
+ />
90
+ </div>
91
+ </div>
92
+ <DialogFooter>
93
+ <Button variant="outline">{t('dialog.cancel')}</Button>
94
+ <Button
95
+ variant={isAprovar ? 'default' : 'destructive'}
96
+ onClick={onConfirm}
97
+ >
98
+ {isAprovar ? t('dialog.confirmApprove') : t('dialog.confirmReject')}
99
+ </Button>
100
+ </DialogFooter>
101
+ </DialogContent>
102
+ </Dialog>
103
+ );
104
+ }
105
+
106
+ export default function AprovacoesPage() {
107
+ const t = useTranslations('finance.PayableApprovalsPage');
108
+ const { data } = useFinanceData();
109
+ const { aprovacoesPendentes, titulosPagar, pessoas } = data;
110
+
111
+ const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
112
+
113
+ const [aprovacoes, setAprovacoes] = useState(aprovacoesPendentes);
114
+
115
+ useEffect(() => {
116
+ setAprovacoes(aprovacoesPendentes);
117
+ }, [aprovacoesPendentes]);
118
+
119
+ const handleAprovacao = (id: string) => {
120
+ setAprovacoes(aprovacoes.filter((a) => a.id !== id));
121
+ };
122
+
123
+ const urgenciaConfig = {
124
+ baixa: {
125
+ label: t('urgency.low'),
126
+ className: 'bg-slate-100 text-slate-700',
127
+ },
128
+ media: {
129
+ label: t('urgency.medium'),
130
+ className: 'bg-blue-100 text-blue-700',
131
+ },
132
+ alta: {
133
+ label: t('urgency.high'),
134
+ className: 'bg-orange-100 text-orange-700',
135
+ },
136
+ critica: {
137
+ label: t('urgency.critical'),
138
+ className: 'bg-red-100 text-red-700',
139
+ },
140
+ };
141
+
142
+ const totalPendente = aprovacoes.reduce((acc, a) => acc + a.valor, 0);
143
+
144
+ return (
145
+ <Page>
146
+ <PageHeader
147
+ title={t('header.title')}
148
+ description={t('header.description')}
149
+ breadcrumbs={[
150
+ { label: t('breadcrumbs.home'), href: '/' },
151
+ { label: t('breadcrumbs.finance'), href: '/finance' },
152
+ { label: t('breadcrumbs.current') },
153
+ ]}
154
+ />
155
+
156
+ <div className="grid gap-4 md:grid-cols-3">
157
+ <Card>
158
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
159
+ <CardTitle className="text-sm font-medium">
160
+ {t('cards.pending')}
161
+ </CardTitle>
162
+ <Clock className="h-4 w-4 text-muted-foreground" />
163
+ </CardHeader>
164
+ <CardContent>
165
+ <div className="text-2xl font-bold">{aprovacoes.length}</div>
166
+ </CardContent>
167
+ </Card>
168
+ <Card>
169
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
170
+ <CardTitle className="text-sm font-medium">
171
+ {t('cards.totalValue')}
172
+ </CardTitle>
173
+ <AlertTriangle className="h-4 w-4 text-muted-foreground" />
174
+ </CardHeader>
175
+ <CardContent>
176
+ <div className="text-2xl font-bold">
177
+ <Money value={totalPendente} />
178
+ </div>
179
+ </CardContent>
180
+ </Card>
181
+ <Card>
182
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
183
+ <CardTitle className="text-sm font-medium">
184
+ {t('cards.urgent')}
185
+ </CardTitle>
186
+ <AlertTriangle className="h-4 w-4 text-orange-500" />
187
+ </CardHeader>
188
+ <CardContent>
189
+ <div className="text-2xl font-bold">
190
+ {
191
+ aprovacoes.filter(
192
+ (a) => a.urgencia === 'alta' || a.urgencia === 'critica'
193
+ ).length
194
+ }
195
+ </div>
196
+ </CardContent>
197
+ </Card>
198
+ </div>
199
+
200
+ <Card>
201
+ <CardHeader>
202
+ <CardTitle>{t('table.title')}</CardTitle>
203
+ <CardDescription>{t('table.description')}</CardDescription>
204
+ </CardHeader>
205
+ <CardContent>
206
+ {aprovacoes.length > 0 ? (
207
+ <Table>
208
+ <TableHeader>
209
+ <TableRow>
210
+ <TableHead>{t('table.headers.document')}</TableHead>
211
+ <TableHead>{t('table.headers.supplier')}</TableHead>
212
+ <TableHead>{t('table.headers.requester')}</TableHead>
213
+ <TableHead className="text-right">
214
+ {t('table.headers.value')}
215
+ </TableHead>
216
+ <TableHead>{t('table.headers.policy')}</TableHead>
217
+ <TableHead>{t('table.headers.urgency')}</TableHead>
218
+ <TableHead>{t('table.headers.date')}</TableHead>
219
+ <TableHead className="text-right">
220
+ {t('table.headers.actions')}
221
+ </TableHead>
222
+ </TableRow>
223
+ </TableHeader>
224
+ <TableBody>
225
+ {aprovacoes.map((aprovacao) => {
226
+ const titulo = titulosPagar.find(
227
+ (t) => t.id === aprovacao.tituloId
228
+ );
229
+ const fornecedor = titulo
230
+ ? getPessoaById(titulo.fornecedorId)
231
+ : null;
232
+ const urgencia =
233
+ urgenciaConfig[
234
+ aprovacao.urgencia as keyof typeof urgenciaConfig
235
+ ] || urgenciaConfig.media;
236
+
237
+ return (
238
+ <TableRow key={aprovacao.id}>
239
+ <TableCell className="font-medium">
240
+ {titulo?.documento}
241
+ </TableCell>
242
+ <TableCell>{fornecedor?.nome}</TableCell>
243
+ <TableCell>{aprovacao.solicitante}</TableCell>
244
+ <TableCell className="text-right">
245
+ <Money value={aprovacao.valor} />
246
+ </TableCell>
247
+ <TableCell>
248
+ <span className="text-sm text-muted-foreground">
249
+ {aprovacao.politica}
250
+ </span>
251
+ </TableCell>
252
+ <TableCell>
253
+ <Badge className={urgencia.className} variant="outline">
254
+ {urgencia.label}
255
+ </Badge>
256
+ </TableCell>
257
+ <TableCell>
258
+ {formatarData(aprovacao.dataSolicitacao)}
259
+ </TableCell>
260
+ <TableCell>
261
+ <div className="flex justify-end gap-2">
262
+ <AprovacaoDialog
263
+ tipo="aprovar"
264
+ t={t}
265
+ onConfirm={() => handleAprovacao(aprovacao.id)}
266
+ />
267
+ <AprovacaoDialog
268
+ tipo="reprovar"
269
+ t={t}
270
+ onConfirm={() => handleAprovacao(aprovacao.id)}
271
+ />
272
+ </div>
273
+ </TableCell>
274
+ </TableRow>
275
+ );
276
+ })}
277
+ </TableBody>
278
+ </Table>
279
+ ) : (
280
+ <div className="flex flex-col items-center justify-center py-12 text-center">
281
+ <CheckCircle className="h-12 w-12 text-green-500" />
282
+ <h3 className="mt-4 text-lg font-semibold">{t('empty.title')}</h3>
283
+ <p className="text-muted-foreground">{t('empty.description')}</p>
284
+ </div>
285
+ )}
286
+ </CardContent>
287
+ </Card>
288
+ </Page>
289
+ );
290
+ }