@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,388 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuTrigger,
11
+ } from '@/components/ui/dropdown-menu';
12
+ import { FilterBar } from '@/components/ui/filter-bar';
13
+ import { Input } from '@/components/ui/input';
14
+ import { Label } from '@/components/ui/label';
15
+ import { Money } from '@/components/ui/money';
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 { useTranslations } from 'next-intl';
53
+ import Link from 'next/link';
54
+ import { useState } from 'react';
55
+ import { formatarData } from '../../_lib/formatters';
56
+ import { useFinanceData } from '../../_lib/use-finance-data';
57
+
58
+ function NovoTituloSheet({
59
+ pessoas,
60
+ categorias,
61
+ centrosCusto,
62
+ t,
63
+ }: {
64
+ pessoas: any[];
65
+ categorias: any[];
66
+ centrosCusto: any[];
67
+ t: ReturnType<typeof useTranslations>;
68
+ }) {
69
+ return (
70
+ <Sheet>
71
+ <SheetTrigger asChild>
72
+ <Button>
73
+ <Plus className="mr-2 h-4 w-4" />
74
+ {t('newTitle.action')}
75
+ </Button>
76
+ </SheetTrigger>
77
+ <SheetContent className="w-full sm:max-w-lg overflow-y-auto">
78
+ <SheetHeader>
79
+ <SheetTitle>{t('newTitle.title')}</SheetTitle>
80
+ <SheetDescription>{t('newTitle.description')}</SheetDescription>
81
+ </SheetHeader>
82
+ <form className="mt-6 space-y-4">
83
+ <div className="grid gap-4">
84
+ <div className="space-y-2">
85
+ <Label htmlFor="documento">{t('fields.document')}</Label>
86
+ <Input id="documento" placeholder="NF-00000" />
87
+ </div>
88
+ <div className="space-y-2">
89
+ <Label htmlFor="fornecedor">{t('fields.supplier')}</Label>
90
+ <Select>
91
+ <SelectTrigger>
92
+ <SelectValue placeholder={t('common.select')} />
93
+ </SelectTrigger>
94
+ <SelectContent>
95
+ {pessoas
96
+ .filter(
97
+ (p) => p.tipo === 'fornecedor' || p.tipo === 'ambos'
98
+ )
99
+ .map((p) => (
100
+ <SelectItem key={p.id} value={p.id}>
101
+ {p.nome}
102
+ </SelectItem>
103
+ ))}
104
+ </SelectContent>
105
+ </Select>
106
+ </div>
107
+ <div className="grid grid-cols-2 gap-4">
108
+ <div className="space-y-2">
109
+ <Label htmlFor="competencia">{t('fields.competency')}</Label>
110
+ <Input id="competencia" type="month" />
111
+ </div>
112
+ <div className="space-y-2">
113
+ <Label htmlFor="vencimento">{t('fields.dueDate')}</Label>
114
+ <Input id="vencimento" type="date" />
115
+ </div>
116
+ </div>
117
+ <div className="space-y-2">
118
+ <Label htmlFor="valor">{t('fields.totalValue')}</Label>
119
+ <Input id="valor" type="number" placeholder="0,00" step="0.01" />
120
+ </div>
121
+ <div className="space-y-2">
122
+ <Label htmlFor="categoria">{t('fields.category')}</Label>
123
+ <Select>
124
+ <SelectTrigger>
125
+ <SelectValue placeholder={t('common.select')} />
126
+ </SelectTrigger>
127
+ <SelectContent>
128
+ {categorias
129
+ .filter((c) => c.natureza === 'despesa')
130
+ .map((c) => (
131
+ <SelectItem key={c.id} value={c.id}>
132
+ {c.codigo} - {c.nome}
133
+ </SelectItem>
134
+ ))}
135
+ </SelectContent>
136
+ </Select>
137
+ </div>
138
+ <div className="space-y-2">
139
+ <Label htmlFor="centroCusto">{t('fields.costCenter')}</Label>
140
+ <Select>
141
+ <SelectTrigger>
142
+ <SelectValue placeholder={t('common.select')} />
143
+ </SelectTrigger>
144
+ <SelectContent>
145
+ {centrosCusto.map((c) => (
146
+ <SelectItem key={c.id} value={c.id}>
147
+ {c.codigo} - {c.nome}
148
+ </SelectItem>
149
+ ))}
150
+ </SelectContent>
151
+ </Select>
152
+ </div>
153
+ <div className="space-y-2">
154
+ <Label htmlFor="metodo">{t('fields.paymentMethod')}</Label>
155
+ <Select>
156
+ <SelectTrigger>
157
+ <SelectValue placeholder={t('common.select')} />
158
+ </SelectTrigger>
159
+ <SelectContent>
160
+ <SelectItem value="boleto">
161
+ {t('paymentMethods.boleto')}
162
+ </SelectItem>
163
+ <SelectItem value="pix">PIX</SelectItem>
164
+ <SelectItem value="transferencia">
165
+ {t('paymentMethods.transfer')}
166
+ </SelectItem>
167
+ <SelectItem value="cartao">
168
+ {t('paymentMethods.card')}
169
+ </SelectItem>
170
+ <SelectItem value="dinheiro">
171
+ {t('paymentMethods.cash')}
172
+ </SelectItem>
173
+ <SelectItem value="cheque">
174
+ {t('paymentMethods.check')}
175
+ </SelectItem>
176
+ </SelectContent>
177
+ </Select>
178
+ </div>
179
+ <div className="space-y-2">
180
+ <Label htmlFor="descricao">{t('fields.description')}</Label>
181
+ <Textarea
182
+ id="descricao"
183
+ placeholder={t('newTitle.descriptionPlaceholder')}
184
+ />
185
+ </div>
186
+ </div>
187
+ <div className="flex justify-end gap-2 pt-4">
188
+ <Button type="button" variant="outline">
189
+ {t('common.cancel')}
190
+ </Button>
191
+ <Button type="submit">{t('common.save')}</Button>
192
+ </div>
193
+ </form>
194
+ </SheetContent>
195
+ </Sheet>
196
+ );
197
+ }
198
+
199
+ export default function TitulosPagarPage() {
200
+ const t = useTranslations('finance.PayableInstallmentsPage');
201
+ const { data } = useFinanceData();
202
+ const { titulosPagar, pessoas, categorias, centrosCusto } = data;
203
+
204
+ const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
205
+ const getCategoriaById = (id?: string) => categorias.find((c) => c.id === id);
206
+
207
+ const [search, setSearch] = useState('');
208
+ const [statusFilter, setStatusFilter] = useState<string>('');
209
+
210
+ const filteredTitulos = titulosPagar.filter((titulo) => {
211
+ const matchesSearch =
212
+ titulo.documento.toLowerCase().includes(search.toLowerCase()) ||
213
+ getPessoaById(titulo.fornecedorId)
214
+ ?.nome.toLowerCase()
215
+ .includes(search.toLowerCase());
216
+
217
+ const matchesStatus = !statusFilter || titulo.status === statusFilter;
218
+
219
+ return matchesSearch && matchesStatus;
220
+ });
221
+
222
+ return (
223
+ <Page>
224
+ <PageHeader
225
+ title={t('header.title')}
226
+ description={t('header.description')}
227
+ breadcrumbs={[
228
+ { label: t('breadcrumbs.home'), href: '/' },
229
+ { label: t('breadcrumbs.finance'), href: '/finance' },
230
+ { label: t('breadcrumbs.current') },
231
+ ]}
232
+ actions={
233
+ <NovoTituloSheet
234
+ pessoas={pessoas}
235
+ categorias={categorias}
236
+ centrosCusto={centrosCusto}
237
+ t={t}
238
+ />
239
+ }
240
+ />
241
+
242
+ <FilterBar
243
+ searchPlaceholder={t('filters.searchPlaceholder')}
244
+ searchValue={search}
245
+ onSearchChange={setSearch}
246
+ filters={[
247
+ {
248
+ id: 'status',
249
+ label: t('filters.status'),
250
+ value: statusFilter,
251
+ onChange: setStatusFilter,
252
+ options: [
253
+ { value: 'all', label: t('statuses.all') },
254
+ { value: 'rascunho', label: t('statuses.rascunho') },
255
+ { value: 'aprovado', label: t('statuses.aprovado') },
256
+ { value: 'aberto', label: t('statuses.aberto') },
257
+ { value: 'parcial', label: t('statuses.parcial') },
258
+ { value: 'liquidado', label: t('statuses.liquidado') },
259
+ { value: 'vencido', label: t('statuses.vencido') },
260
+ { value: 'cancelado', label: t('statuses.cancelado') },
261
+ ],
262
+ },
263
+ ]}
264
+ activeFilters={statusFilter && statusFilter !== 'all' ? 1 : 0}
265
+ onClearFilters={() => setStatusFilter('')}
266
+ />
267
+
268
+ <div className="rounded-md border">
269
+ <Table>
270
+ <TableHeader>
271
+ <TableRow>
272
+ <TableHead>{t('table.headers.document')}</TableHead>
273
+ <TableHead>{t('table.headers.supplier')}</TableHead>
274
+ <TableHead>{t('table.headers.competency')}</TableHead>
275
+ <TableHead>{t('table.headers.dueDate')}</TableHead>
276
+ <TableHead className="text-right">
277
+ {t('table.headers.value')}
278
+ </TableHead>
279
+ <TableHead>{t('table.headers.category')}</TableHead>
280
+ <TableHead>{t('table.headers.status')}</TableHead>
281
+ <TableHead className="w-[50px]" />
282
+ </TableRow>
283
+ </TableHeader>
284
+ <TableBody>
285
+ {filteredTitulos.map((titulo) => {
286
+ const fornecedor = getPessoaById(titulo.fornecedorId);
287
+ const categoria = getCategoriaById(titulo.categoriaId);
288
+ const proximaParcela = titulo.parcelas.find(
289
+ (p: any) => p.status === 'aberto' || p.status === 'vencido'
290
+ );
291
+
292
+ return (
293
+ <TableRow key={titulo.id}>
294
+ <TableCell className="font-medium">
295
+ <Link
296
+ href={`/finance/accounts-payable/installments/${titulo.id}`}
297
+ className="hover:underline"
298
+ >
299
+ {titulo.documento}
300
+ </Link>
301
+ {titulo.anexos.length > 0 && (
302
+ <Paperclip className="ml-1 inline h-3 w-3 text-muted-foreground" />
303
+ )}
304
+ </TableCell>
305
+ <TableCell>{fornecedor?.nome}</TableCell>
306
+ <TableCell>{titulo.competencia}</TableCell>
307
+ <TableCell>
308
+ {proximaParcela
309
+ ? formatarData(proximaParcela.vencimento)
310
+ : '-'}
311
+ </TableCell>
312
+ <TableCell className="text-right">
313
+ <Money value={titulo.valorTotal} />
314
+ </TableCell>
315
+ <TableCell>{categoria?.nome}</TableCell>
316
+ <TableCell>
317
+ <StatusBadge status={titulo.status} />
318
+ </TableCell>
319
+ <TableCell>
320
+ <DropdownMenu>
321
+ <DropdownMenuTrigger asChild>
322
+ <Button variant="ghost" size="icon">
323
+ <MoreHorizontal className="h-4 w-4" />
324
+ <span className="sr-only">
325
+ {t('table.actions.srActions')}
326
+ </span>
327
+ </Button>
328
+ </DropdownMenuTrigger>
329
+ <DropdownMenuContent align="end">
330
+ <DropdownMenuItem asChild>
331
+ <Link
332
+ href={`/finance/accounts-payable/installments/${titulo.id}`}
333
+ >
334
+ <Eye className="mr-2 h-4 w-4" />
335
+ {t('table.actions.viewDetails')}
336
+ </Link>
337
+ </DropdownMenuItem>
338
+ <DropdownMenuItem>
339
+ <Edit className="mr-2 h-4 w-4" />
340
+ {t('table.actions.edit')}
341
+ </DropdownMenuItem>
342
+ <DropdownMenuSeparator />
343
+ <DropdownMenuItem>
344
+ <CheckCircle className="mr-2 h-4 w-4" />
345
+ {t('table.actions.approve')}
346
+ </DropdownMenuItem>
347
+ <DropdownMenuItem>
348
+ <Download className="mr-2 h-4 w-4" />
349
+ {t('table.actions.settle')}
350
+ </DropdownMenuItem>
351
+ <DropdownMenuItem>
352
+ <Undo className="mr-2 h-4 w-4" />
353
+ {t('table.actions.reverse')}
354
+ </DropdownMenuItem>
355
+ <DropdownMenuSeparator />
356
+ <DropdownMenuItem className="text-destructive">
357
+ <XCircle className="mr-2 h-4 w-4" />
358
+ {t('table.actions.cancel')}
359
+ </DropdownMenuItem>
360
+ </DropdownMenuContent>
361
+ </DropdownMenu>
362
+ </TableCell>
363
+ </TableRow>
364
+ );
365
+ })}
366
+ </TableBody>
367
+ </Table>
368
+ </div>
369
+
370
+ <div className="flex items-center justify-between">
371
+ <p className="text-sm text-muted-foreground">
372
+ {t('footer.showing', {
373
+ filtered: filteredTitulos.length,
374
+ total: titulosPagar.length,
375
+ })}
376
+ </p>
377
+ <div className="flex items-center gap-2">
378
+ <Button variant="outline" size="sm" disabled>
379
+ {t('footer.previous')}
380
+ </Button>
381
+ <Button variant="outline" size="sm" disabled>
382
+ {t('footer.next')}
383
+ </Button>
384
+ </div>
385
+ </div>
386
+ </Page>
387
+ );
388
+ }