@hed-hog/finance 0.0.3 → 0.0.224
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/hedhog/data/menu.yaml +18 -23
- package/hedhog/frontend/app/_lib/formatters.ts.ejs +20 -0
- package/hedhog/frontend/app/_lib/use-finance-data.ts.ejs +87 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +265 -0
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +388 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +364 -0
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +388 -0
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +385 -0
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +364 -0
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +274 -0
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +401 -0
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +266 -0
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +244 -0
- package/hedhog/frontend/app/page.tsx.ejs +313 -15
- package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +285 -0
- package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +195 -0
- package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +321 -0
- package/hedhog/query/constraints.sql +169 -0
- package/package.json +4 -4
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import {
|
|
5
|
+
DropdownMenu,
|
|
6
|
+
DropdownMenuContent,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuSeparator,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from '@/components/ui/dropdown-menu';
|
|
11
|
+
import { FilterBar } from '@/components/ui/filter-bar';
|
|
12
|
+
import { Input } from '@/components/ui/input';
|
|
13
|
+
import { Label } from '@/components/ui/label';
|
|
14
|
+
import { Money } from '@/components/ui/money';
|
|
15
|
+
import { PageHeader } from '@/components/ui/page-header';
|
|
16
|
+
import {
|
|
17
|
+
Select,
|
|
18
|
+
SelectContent,
|
|
19
|
+
SelectItem,
|
|
20
|
+
SelectTrigger,
|
|
21
|
+
SelectValue,
|
|
22
|
+
} from '@/components/ui/select';
|
|
23
|
+
import {
|
|
24
|
+
Sheet,
|
|
25
|
+
SheetContent,
|
|
26
|
+
SheetDescription,
|
|
27
|
+
SheetHeader,
|
|
28
|
+
SheetTitle,
|
|
29
|
+
SheetTrigger,
|
|
30
|
+
} from '@/components/ui/sheet';
|
|
31
|
+
import { StatusBadge } from '@/components/ui/status-badge';
|
|
32
|
+
import {
|
|
33
|
+
Table,
|
|
34
|
+
TableBody,
|
|
35
|
+
TableCell,
|
|
36
|
+
TableHead,
|
|
37
|
+
TableHeader,
|
|
38
|
+
TableRow,
|
|
39
|
+
} from '@/components/ui/table';
|
|
40
|
+
import { Textarea } from '@/components/ui/textarea';
|
|
41
|
+
import {
|
|
42
|
+
CheckCircle,
|
|
43
|
+
Download,
|
|
44
|
+
Edit,
|
|
45
|
+
Eye,
|
|
46
|
+
MoreHorizontal,
|
|
47
|
+
Paperclip,
|
|
48
|
+
Plus,
|
|
49
|
+
Undo,
|
|
50
|
+
XCircle,
|
|
51
|
+
} from 'lucide-react';
|
|
52
|
+
import Link from 'next/link';
|
|
53
|
+
import { useState } from 'react';
|
|
54
|
+
import { formatarData } from '../../_lib/formatters';
|
|
55
|
+
import { useFinanceData } from '../../_lib/use-finance-data';
|
|
56
|
+
|
|
57
|
+
function NovoTituloSheet({
|
|
58
|
+
pessoas,
|
|
59
|
+
categorias,
|
|
60
|
+
centrosCusto,
|
|
61
|
+
}: {
|
|
62
|
+
pessoas: any[];
|
|
63
|
+
categorias: any[];
|
|
64
|
+
centrosCusto: any[];
|
|
65
|
+
}) {
|
|
66
|
+
return (
|
|
67
|
+
<Sheet>
|
|
68
|
+
<SheetTrigger asChild>
|
|
69
|
+
<Button>
|
|
70
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
71
|
+
Novo Título
|
|
72
|
+
</Button>
|
|
73
|
+
</SheetTrigger>
|
|
74
|
+
<SheetContent className="w-full sm:max-w-lg overflow-y-auto">
|
|
75
|
+
<SheetHeader>
|
|
76
|
+
<SheetTitle>Novo Título a Pagar</SheetTitle>
|
|
77
|
+
<SheetDescription>
|
|
78
|
+
Preencha os dados para criar um novo título a pagar.
|
|
79
|
+
</SheetDescription>
|
|
80
|
+
</SheetHeader>
|
|
81
|
+
<form className="mt-6 space-y-4">
|
|
82
|
+
<div className="grid gap-4">
|
|
83
|
+
<div className="space-y-2">
|
|
84
|
+
<Label htmlFor="documento">Documento</Label>
|
|
85
|
+
<Input id="documento" placeholder="NF-00000" />
|
|
86
|
+
</div>
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<Label htmlFor="fornecedor">Fornecedor</Label>
|
|
89
|
+
<Select>
|
|
90
|
+
<SelectTrigger>
|
|
91
|
+
<SelectValue placeholder="Selecione..." />
|
|
92
|
+
</SelectTrigger>
|
|
93
|
+
<SelectContent>
|
|
94
|
+
{pessoas
|
|
95
|
+
.filter(
|
|
96
|
+
(p) => p.tipo === 'fornecedor' || p.tipo === 'ambos'
|
|
97
|
+
)
|
|
98
|
+
.map((p) => (
|
|
99
|
+
<SelectItem key={p.id} value={p.id}>
|
|
100
|
+
{p.nome}
|
|
101
|
+
</SelectItem>
|
|
102
|
+
))}
|
|
103
|
+
</SelectContent>
|
|
104
|
+
</Select>
|
|
105
|
+
</div>
|
|
106
|
+
<div className="grid grid-cols-2 gap-4">
|
|
107
|
+
<div className="space-y-2">
|
|
108
|
+
<Label htmlFor="competencia">Competência</Label>
|
|
109
|
+
<Input id="competencia" type="month" />
|
|
110
|
+
</div>
|
|
111
|
+
<div className="space-y-2">
|
|
112
|
+
<Label htmlFor="vencimento">Vencimento</Label>
|
|
113
|
+
<Input id="vencimento" type="date" />
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div className="space-y-2">
|
|
117
|
+
<Label htmlFor="valor">Valor Total</Label>
|
|
118
|
+
<Input id="valor" type="number" placeholder="0,00" step="0.01" />
|
|
119
|
+
</div>
|
|
120
|
+
<div className="space-y-2">
|
|
121
|
+
<Label htmlFor="categoria">Categoria</Label>
|
|
122
|
+
<Select>
|
|
123
|
+
<SelectTrigger>
|
|
124
|
+
<SelectValue placeholder="Selecione..." />
|
|
125
|
+
</SelectTrigger>
|
|
126
|
+
<SelectContent>
|
|
127
|
+
{categorias
|
|
128
|
+
.filter((c) => c.natureza === 'despesa')
|
|
129
|
+
.map((c) => (
|
|
130
|
+
<SelectItem key={c.id} value={c.id}>
|
|
131
|
+
{c.codigo} - {c.nome}
|
|
132
|
+
</SelectItem>
|
|
133
|
+
))}
|
|
134
|
+
</SelectContent>
|
|
135
|
+
</Select>
|
|
136
|
+
</div>
|
|
137
|
+
<div className="space-y-2">
|
|
138
|
+
<Label htmlFor="centroCusto">Centro de Custo</Label>
|
|
139
|
+
<Select>
|
|
140
|
+
<SelectTrigger>
|
|
141
|
+
<SelectValue placeholder="Selecione..." />
|
|
142
|
+
</SelectTrigger>
|
|
143
|
+
<SelectContent>
|
|
144
|
+
{centrosCusto.map((c) => (
|
|
145
|
+
<SelectItem key={c.id} value={c.id}>
|
|
146
|
+
{c.codigo} - {c.nome}
|
|
147
|
+
</SelectItem>
|
|
148
|
+
))}
|
|
149
|
+
</SelectContent>
|
|
150
|
+
</Select>
|
|
151
|
+
</div>
|
|
152
|
+
<div className="space-y-2">
|
|
153
|
+
<Label htmlFor="metodo">Forma de Pagamento</Label>
|
|
154
|
+
<Select>
|
|
155
|
+
<SelectTrigger>
|
|
156
|
+
<SelectValue placeholder="Selecione..." />
|
|
157
|
+
</SelectTrigger>
|
|
158
|
+
<SelectContent>
|
|
159
|
+
<SelectItem value="boleto">Boleto</SelectItem>
|
|
160
|
+
<SelectItem value="pix">PIX</SelectItem>
|
|
161
|
+
<SelectItem value="transferencia">Transferência</SelectItem>
|
|
162
|
+
<SelectItem value="cartao">Cartão</SelectItem>
|
|
163
|
+
<SelectItem value="dinheiro">Dinheiro</SelectItem>
|
|
164
|
+
<SelectItem value="cheque">Cheque</SelectItem>
|
|
165
|
+
</SelectContent>
|
|
166
|
+
</Select>
|
|
167
|
+
</div>
|
|
168
|
+
<div className="space-y-2">
|
|
169
|
+
<Label htmlFor="descricao">Descrição</Label>
|
|
170
|
+
<Textarea id="descricao" placeholder="Descrição do título..." />
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="flex justify-end gap-2 pt-4">
|
|
174
|
+
<Button type="button" variant="outline">
|
|
175
|
+
Cancelar
|
|
176
|
+
</Button>
|
|
177
|
+
<Button type="submit">Salvar</Button>
|
|
178
|
+
</div>
|
|
179
|
+
</form>
|
|
180
|
+
</SheetContent>
|
|
181
|
+
</Sheet>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default function TitulosPagarPage() {
|
|
186
|
+
const { data } = useFinanceData();
|
|
187
|
+
const { titulosPagar, pessoas, categorias, centrosCusto } = data;
|
|
188
|
+
|
|
189
|
+
const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
|
|
190
|
+
const getCategoriaById = (id?: string) => categorias.find((c) => c.id === id);
|
|
191
|
+
|
|
192
|
+
const [search, setSearch] = useState('');
|
|
193
|
+
const [statusFilter, setStatusFilter] = useState<string>('');
|
|
194
|
+
|
|
195
|
+
const filteredTitulos = titulosPagar.filter((titulo) => {
|
|
196
|
+
const matchesSearch =
|
|
197
|
+
titulo.documento.toLowerCase().includes(search.toLowerCase()) ||
|
|
198
|
+
getPessoaById(titulo.fornecedorId)
|
|
199
|
+
?.nome.toLowerCase()
|
|
200
|
+
.includes(search.toLowerCase());
|
|
201
|
+
|
|
202
|
+
const matchesStatus = !statusFilter || titulo.status === statusFilter;
|
|
203
|
+
|
|
204
|
+
return matchesSearch && matchesStatus;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="space-y-6">
|
|
209
|
+
<PageHeader
|
|
210
|
+
title="Títulos a Pagar"
|
|
211
|
+
description="Gerencie suas contas a pagar"
|
|
212
|
+
breadcrumbs={[
|
|
213
|
+
{ label: 'Contas a Pagar', href: '/finance/accounts-payable/installments' },
|
|
214
|
+
{ label: 'Títulos e Parcelas' },
|
|
215
|
+
]}
|
|
216
|
+
actions={
|
|
217
|
+
<NovoTituloSheet
|
|
218
|
+
pessoas={pessoas}
|
|
219
|
+
categorias={categorias}
|
|
220
|
+
centrosCusto={centrosCusto}
|
|
221
|
+
/>
|
|
222
|
+
}
|
|
223
|
+
/>
|
|
224
|
+
|
|
225
|
+
<FilterBar
|
|
226
|
+
searchPlaceholder="Buscar por documento ou fornecedor..."
|
|
227
|
+
searchValue={search}
|
|
228
|
+
onSearchChange={setSearch}
|
|
229
|
+
filters={[
|
|
230
|
+
{
|
|
231
|
+
id: 'status',
|
|
232
|
+
label: 'Status',
|
|
233
|
+
value: statusFilter,
|
|
234
|
+
onChange: setStatusFilter,
|
|
235
|
+
options: [
|
|
236
|
+
{ value: 'all', label: 'Todos' },
|
|
237
|
+
{ value: 'rascunho', label: 'Rascunho' },
|
|
238
|
+
{ value: 'aprovado', label: 'Aprovado' },
|
|
239
|
+
{ value: 'aberto', label: 'Aberto' },
|
|
240
|
+
{ value: 'parcial', label: 'Parcial' },
|
|
241
|
+
{ value: 'liquidado', label: 'Liquidado' },
|
|
242
|
+
{ value: 'vencido', label: 'Vencido' },
|
|
243
|
+
{ value: 'cancelado', label: 'Cancelado' },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
]}
|
|
247
|
+
activeFilters={statusFilter && statusFilter !== 'all' ? 1 : 0}
|
|
248
|
+
onClearFilters={() => setStatusFilter('')}
|
|
249
|
+
/>
|
|
250
|
+
|
|
251
|
+
<div className="rounded-md border">
|
|
252
|
+
<Table>
|
|
253
|
+
<TableHeader>
|
|
254
|
+
<TableRow>
|
|
255
|
+
<TableHead>Documento</TableHead>
|
|
256
|
+
<TableHead>Fornecedor</TableHead>
|
|
257
|
+
<TableHead>Competência</TableHead>
|
|
258
|
+
<TableHead>Vencimento</TableHead>
|
|
259
|
+
<TableHead className="text-right">Valor</TableHead>
|
|
260
|
+
<TableHead>Categoria</TableHead>
|
|
261
|
+
<TableHead>Status</TableHead>
|
|
262
|
+
<TableHead className="w-[50px]" />
|
|
263
|
+
</TableRow>
|
|
264
|
+
</TableHeader>
|
|
265
|
+
<TableBody>
|
|
266
|
+
{filteredTitulos.map((titulo) => {
|
|
267
|
+
const fornecedor = getPessoaById(titulo.fornecedorId);
|
|
268
|
+
const categoria = getCategoriaById(titulo.categoriaId);
|
|
269
|
+
const proximaParcela = titulo.parcelas.find(
|
|
270
|
+
(p: any) => p.status === 'aberto' || p.status === 'vencido'
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
return (
|
|
274
|
+
<TableRow key={titulo.id}>
|
|
275
|
+
<TableCell className="font-medium">
|
|
276
|
+
<Link
|
|
277
|
+
href={`/finance/accounts-payable/installments/${titulo.id}`}
|
|
278
|
+
className="hover:underline"
|
|
279
|
+
>
|
|
280
|
+
{titulo.documento}
|
|
281
|
+
</Link>
|
|
282
|
+
{titulo.anexos.length > 0 && (
|
|
283
|
+
<Paperclip className="ml-1 inline h-3 w-3 text-muted-foreground" />
|
|
284
|
+
)}
|
|
285
|
+
</TableCell>
|
|
286
|
+
<TableCell>{fornecedor?.nome}</TableCell>
|
|
287
|
+
<TableCell>{titulo.competencia}</TableCell>
|
|
288
|
+
<TableCell>
|
|
289
|
+
{proximaParcela
|
|
290
|
+
? formatarData(proximaParcela.vencimento)
|
|
291
|
+
: '-'}
|
|
292
|
+
</TableCell>
|
|
293
|
+
<TableCell className="text-right">
|
|
294
|
+
<Money value={titulo.valorTotal} />
|
|
295
|
+
</TableCell>
|
|
296
|
+
<TableCell>{categoria?.nome}</TableCell>
|
|
297
|
+
<TableCell>
|
|
298
|
+
<StatusBadge status={titulo.status} />
|
|
299
|
+
</TableCell>
|
|
300
|
+
<TableCell>
|
|
301
|
+
<DropdownMenu>
|
|
302
|
+
<DropdownMenuTrigger asChild>
|
|
303
|
+
<Button variant="ghost" size="icon">
|
|
304
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
305
|
+
<span className="sr-only">Ações</span>
|
|
306
|
+
</Button>
|
|
307
|
+
</DropdownMenuTrigger>
|
|
308
|
+
<DropdownMenuContent align="end">
|
|
309
|
+
<DropdownMenuItem asChild>
|
|
310
|
+
<Link href={`/finance/accounts-payable/installments/${titulo.id}`}>
|
|
311
|
+
<Eye className="mr-2 h-4 w-4" />
|
|
312
|
+
Ver Detalhes
|
|
313
|
+
</Link>
|
|
314
|
+
</DropdownMenuItem>
|
|
315
|
+
<DropdownMenuItem>
|
|
316
|
+
<Edit className="mr-2 h-4 w-4" />
|
|
317
|
+
Editar
|
|
318
|
+
</DropdownMenuItem>
|
|
319
|
+
<DropdownMenuSeparator />
|
|
320
|
+
<DropdownMenuItem>
|
|
321
|
+
<CheckCircle className="mr-2 h-4 w-4" />
|
|
322
|
+
Aprovar
|
|
323
|
+
</DropdownMenuItem>
|
|
324
|
+
<DropdownMenuItem>
|
|
325
|
+
<Download className="mr-2 h-4 w-4" />
|
|
326
|
+
Baixar
|
|
327
|
+
</DropdownMenuItem>
|
|
328
|
+
<DropdownMenuItem>
|
|
329
|
+
<Undo className="mr-2 h-4 w-4" />
|
|
330
|
+
Estornar
|
|
331
|
+
</DropdownMenuItem>
|
|
332
|
+
<DropdownMenuSeparator />
|
|
333
|
+
<DropdownMenuItem className="text-destructive">
|
|
334
|
+
<XCircle className="mr-2 h-4 w-4" />
|
|
335
|
+
Cancelar
|
|
336
|
+
</DropdownMenuItem>
|
|
337
|
+
</DropdownMenuContent>
|
|
338
|
+
</DropdownMenu>
|
|
339
|
+
</TableCell>
|
|
340
|
+
</TableRow>
|
|
341
|
+
);
|
|
342
|
+
})}
|
|
343
|
+
</TableBody>
|
|
344
|
+
</Table>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<div className="flex items-center justify-between">
|
|
348
|
+
<p className="text-sm text-muted-foreground">
|
|
349
|
+
Mostrando {filteredTitulos.length} de {titulosPagar.length} registros
|
|
350
|
+
</p>
|
|
351
|
+
<div className="flex items-center gap-2">
|
|
352
|
+
<Button variant="outline" size="sm" disabled>
|
|
353
|
+
Anterior
|
|
354
|
+
</Button>
|
|
355
|
+
<Button variant="outline" size="sm" disabled>
|
|
356
|
+
Próximo
|
|
357
|
+
</Button>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|