@hed-hog/finance 0.0.233 → 0.0.236

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 (76) hide show
  1. package/dist/dto/create-cost-center.dto.d.ts +4 -0
  2. package/dist/dto/create-cost-center.dto.d.ts.map +1 -0
  3. package/dist/dto/create-cost-center.dto.js +24 -0
  4. package/dist/dto/create-cost-center.dto.js.map +1 -0
  5. package/dist/dto/create-finance-category.dto.d.ts +6 -0
  6. package/dist/dto/create-finance-category.dto.d.ts.map +1 -0
  7. package/dist/dto/create-finance-category.dto.js +37 -0
  8. package/dist/dto/create-finance-category.dto.js.map +1 -0
  9. package/dist/dto/create-period-close.dto.d.ts +7 -0
  10. package/dist/dto/create-period-close.dto.d.ts.map +1 -0
  11. package/dist/dto/create-period-close.dto.js +44 -0
  12. package/dist/dto/create-period-close.dto.js.map +1 -0
  13. package/dist/dto/move-finance-category.dto.d.ts +5 -0
  14. package/dist/dto/move-finance-category.dto.d.ts.map +1 -0
  15. package/dist/dto/move-finance-category.dto.js +32 -0
  16. package/dist/dto/move-finance-category.dto.js.map +1 -0
  17. package/dist/dto/update-cost-center.dto.d.ts +5 -0
  18. package/dist/dto/update-cost-center.dto.d.ts.map +1 -0
  19. package/dist/dto/update-cost-center.dto.js +32 -0
  20. package/dist/dto/update-cost-center.dto.js.map +1 -0
  21. package/dist/dto/update-finance-category.dto.d.ts +7 -0
  22. package/dist/dto/update-finance-category.dto.d.ts.map +1 -0
  23. package/dist/dto/update-finance-category.dto.js +46 -0
  24. package/dist/dto/update-finance-category.dto.js.map +1 -0
  25. package/dist/finance-audit-logs.controller.d.ts +13 -0
  26. package/dist/finance-audit-logs.controller.d.ts.map +1 -0
  27. package/dist/finance-audit-logs.controller.js +54 -0
  28. package/dist/finance-audit-logs.controller.js.map +1 -0
  29. package/dist/finance-categories.controller.d.ts +42 -0
  30. package/dist/finance-categories.controller.d.ts.map +1 -0
  31. package/dist/finance-categories.controller.js +84 -0
  32. package/dist/finance-categories.controller.js.map +1 -0
  33. package/dist/finance-cost-centers.controller.d.ts +32 -0
  34. package/dist/finance-cost-centers.controller.d.ts.map +1 -0
  35. package/dist/finance-cost-centers.controller.js +72 -0
  36. package/dist/finance-cost-centers.controller.js.map +1 -0
  37. package/dist/finance-installments.controller.d.ts +4 -0
  38. package/dist/finance-installments.controller.d.ts.map +1 -1
  39. package/dist/finance-period-close.controller.d.ts +27 -0
  40. package/dist/finance-period-close.controller.d.ts.map +1 -0
  41. package/dist/finance-period-close.controller.js +64 -0
  42. package/dist/finance-period-close.controller.js.map +1 -0
  43. package/dist/finance.module.d.ts.map +1 -1
  44. package/dist/finance.module.js +8 -0
  45. package/dist/finance.module.js.map +1 -1
  46. package/dist/finance.service.d.ts +119 -0
  47. package/dist/finance.service.d.ts.map +1 -1
  48. package/dist/finance.service.js +733 -36
  49. package/dist/finance.service.js.map +1 -1
  50. package/dist/index.d.ts +4 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +4 -0
  53. package/dist/index.js.map +1 -1
  54. package/hedhog/data/route.yaml +108 -0
  55. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +76 -6
  56. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +76 -6
  57. package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +309 -0
  58. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +642 -0
  59. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +371 -0
  60. package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +502 -0
  61. package/hedhog/frontend/messages/en.json +225 -0
  62. package/hedhog/frontend/messages/pt.json +225 -0
  63. package/package.json +5 -5
  64. package/src/dto/create-cost-center.dto.ts +9 -0
  65. package/src/dto/create-finance-category.dto.ts +21 -0
  66. package/src/dto/create-period-close.dto.ts +34 -0
  67. package/src/dto/move-finance-category.dto.ts +18 -0
  68. package/src/dto/update-cost-center.dto.ts +17 -0
  69. package/src/dto/update-finance-category.dto.ts +30 -0
  70. package/src/finance-audit-logs.controller.ts +30 -0
  71. package/src/finance-categories.controller.ts +52 -0
  72. package/src/finance-cost-centers.controller.ts +43 -0
  73. package/src/finance-period-close.controller.ts +34 -0
  74. package/src/finance.module.ts +8 -0
  75. package/src/finance.service.ts +1020 -29
  76. package/src/index.ts +4 -0
@@ -54,6 +54,7 @@ import {
54
54
  Download,
55
55
  Edit,
56
56
  Eye,
57
+ Loader2,
57
58
  MoreHorizontal,
58
59
  Paperclip,
59
60
  Plus,
@@ -100,6 +101,10 @@ function NovoTituloSheet({
100
101
  const [uploadedFileName, setUploadedFileName] = useState('');
101
102
  const [isUploadingFile, setIsUploadingFile] = useState(false);
102
103
  const [isExtractingFileData, setIsExtractingFileData] = useState(false);
104
+ const [extractionConfidence, setExtractionConfidence] = useState<
105
+ number | null
106
+ >(null);
107
+ const [extractionWarnings, setExtractionWarnings] = useState<string[]>([]);
103
108
  const [uploadProgress, setUploadProgress] = useState(0);
104
109
 
105
110
  const normalizeFilenameForDisplay = (filename: string) => {
@@ -164,6 +169,8 @@ function NovoTituloSheet({
164
169
  form.reset();
165
170
  setUploadedFileId(null);
166
171
  setUploadedFileName('');
172
+ setExtractionConfidence(null);
173
+ setExtractionWarnings([]);
167
174
  setOpen(false);
168
175
  showToastHandler?.('success', 'Título criado com sucesso');
169
176
  } catch {
@@ -175,6 +182,8 @@ function NovoTituloSheet({
175
182
  form.reset();
176
183
  setUploadedFileId(null);
177
184
  setUploadedFileName('');
185
+ setExtractionConfidence(null);
186
+ setExtractionWarnings([]);
178
187
  setUploadProgress(0);
179
188
  setOpen(false);
180
189
  };
@@ -228,6 +237,9 @@ function NovoTituloSheet({
228
237
  centroCustoId?: string;
229
238
  canal?: string;
230
239
  descricao?: string | null;
240
+ confidence?: number | null;
241
+ confidenceLevel?: 'low' | 'high' | null;
242
+ warnings?: string[];
231
243
  }>({
232
244
  url: '/finance/accounts-receivable/installments/extract-from-file',
233
245
  method: 'POST',
@@ -237,6 +249,14 @@ function NovoTituloSheet({
237
249
  });
238
250
 
239
251
  const extracted = extraction.data || {};
252
+ setExtractionConfidence(
253
+ typeof extracted.confidence === 'number' ? extracted.confidence : null
254
+ );
255
+ setExtractionWarnings(
256
+ Array.isArray(extracted.warnings)
257
+ ? extracted.warnings.filter(Boolean)
258
+ : []
259
+ );
240
260
 
241
261
  if (extracted.documento) {
242
262
  form.setValue('documento', extracted.documento, {
@@ -297,6 +317,8 @@ function NovoTituloSheet({
297
317
  'Dados da fatura extraídos e preenchidos automaticamente'
298
318
  );
299
319
  } catch {
320
+ setExtractionConfidence(null);
321
+ setExtractionWarnings([]);
300
322
  showToastHandler?.(
301
323
  'error',
302
324
  'Não foi possível extrair os dados automaticamente'
@@ -307,6 +329,8 @@ function NovoTituloSheet({
307
329
  } catch {
308
330
  setUploadedFileId(null);
309
331
  setUploadedFileName('');
332
+ setExtractionConfidence(null);
333
+ setExtractionWarnings([]);
310
334
  setUploadProgress(0);
311
335
  showToastHandler?.('error', 'Não foi possível enviar o arquivo');
312
336
  } finally {
@@ -344,10 +368,16 @@ function NovoTituloSheet({
344
368
 
345
369
  setUploadedFileId(null);
346
370
  setUploadedFileName('');
371
+ setExtractionConfidence(null);
372
+ setExtractionWarnings([]);
347
373
  setUploadProgress(0);
348
374
  void uploadRelatedFile(file);
349
375
  }}
350
- disabled={isUploadingFile || form.formState.isSubmitting}
376
+ disabled={
377
+ isUploadingFile ||
378
+ isExtractingFileData ||
379
+ form.formState.isSubmitting
380
+ }
351
381
  />
352
382
  </div>
353
383
  {isUploadingFile && (
@@ -364,10 +394,36 @@ function NovoTituloSheet({
364
394
  </p>
365
395
  )}
366
396
  {isExtractingFileData && (
367
- <p className="text-xs text-muted-foreground">
368
- Extraindo dados da fatura com IA...
369
- </p>
397
+ <div className="rounded-md border border-primary/30 bg-primary/5 p-3">
398
+ <div className="flex items-center gap-2 text-sm font-medium text-primary">
399
+ <Loader2 className="h-4 w-4 animate-spin" />
400
+ Analisando documento com IA
401
+ </div>
402
+ <p className="mt-1 text-xs text-muted-foreground">
403
+ Os campos serão preenchidos automaticamente em instantes.
404
+ Revise os dados antes de salvar.
405
+ </p>
406
+ </div>
370
407
  )}
408
+ {!isExtractingFileData &&
409
+ extractionConfidence !== null &&
410
+ extractionConfidence < 70 && (
411
+ <div className="rounded-md border border-destructive/30 bg-destructive/5 p-3">
412
+ <div className="text-sm font-medium text-destructive">
413
+ Confiança da extração:{' '}
414
+ {Math.round(extractionConfidence)}%
415
+ </div>
416
+ <p className="mt-1 text-xs text-muted-foreground">
417
+ Revise principalmente valor e vencimento antes de
418
+ salvar.
419
+ </p>
420
+ {extractionWarnings.length > 0 && (
421
+ <p className="mt-1 text-xs text-muted-foreground">
422
+ {extractionWarnings[0]}
423
+ </p>
424
+ )}
425
+ </div>
426
+ )}
371
427
  </div>
372
428
 
373
429
  <FormField
@@ -577,8 +633,22 @@ function NovoTituloSheet({
577
633
  <Button type="button" variant="outline" onClick={handleCancel}>
578
634
  {t('common.cancel')}
579
635
  </Button>
580
- <Button type="submit" disabled={form.formState.isSubmitting}>
581
- {t('common.save')}
636
+ <Button
637
+ type="submit"
638
+ disabled={
639
+ form.formState.isSubmitting ||
640
+ isUploadingFile ||
641
+ isExtractingFileData
642
+ }
643
+ >
644
+ {(isUploadingFile || isExtractingFileData) && (
645
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
646
+ )}
647
+ {isExtractingFileData
648
+ ? 'Preenchendo com IA...'
649
+ : isUploadingFile
650
+ ? 'Enviando arquivo...'
651
+ : t('common.save')}
582
652
  </Button>
583
653
  </div>
584
654
  </form>
@@ -0,0 +1,309 @@
1
+ 'use client';
2
+
3
+ import { Page, PageHeader, PaginationFooter } 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 { Input } from '@/components/ui/input';
14
+ import {
15
+ Select,
16
+ SelectContent,
17
+ SelectItem,
18
+ SelectTrigger,
19
+ SelectValue,
20
+ } from '@/components/ui/select';
21
+ import {
22
+ Table,
23
+ TableBody,
24
+ TableCell,
25
+ TableHead,
26
+ TableHeader,
27
+ TableRow,
28
+ } from '@/components/ui/table';
29
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
30
+ import { Search, X } from 'lucide-react';
31
+ import { useTranslations } from 'next-intl';
32
+ import { useMemo, useState } from 'react';
33
+
34
+ type AuditLog = {
35
+ id: string;
36
+ actorUserId: string | null;
37
+ actorName: string;
38
+ actorEmail: string;
39
+ action: string;
40
+ entityTable: string;
41
+ entityId: string;
42
+ summary: string;
43
+ ipAddress: string;
44
+ createdAt: string | null;
45
+ };
46
+
47
+ type PaginatedAuditLogs = {
48
+ total: number;
49
+ lastPage: number;
50
+ page: number;
51
+ pageSize: number;
52
+ data: AuditLog[];
53
+ };
54
+
55
+ export default function AuditLogsPage() {
56
+ const t = useTranslations('finance.AdminAuditLogsPage');
57
+ const { request } = useApp();
58
+
59
+ const [page, setPage] = useState(1);
60
+ const [pageSize, setPageSize] = useState(10);
61
+ const [search, setSearch] = useState('');
62
+ const [action, setAction] = useState('all');
63
+ const [entityTable, setEntityTable] = useState('all');
64
+ const [actorUserId, setActorUserId] = useState('');
65
+ const [from, setFrom] = useState('');
66
+ const [to, setTo] = useState('');
67
+
68
+ const { data, isLoading, refetch } = useQuery<PaginatedAuditLogs>({
69
+ queryKey: [
70
+ 'finance-audit-logs',
71
+ page,
72
+ pageSize,
73
+ search,
74
+ action,
75
+ entityTable,
76
+ actorUserId,
77
+ from,
78
+ to,
79
+ ],
80
+ queryFn: async () => {
81
+ const params = new URLSearchParams();
82
+ params.set('page', String(page));
83
+ params.set('pageSize', String(pageSize));
84
+ params.set('sortField', 'created_at');
85
+ params.set('sortOrder', 'desc');
86
+
87
+ if (search.trim()) params.set('search', search.trim());
88
+ if (action !== 'all') params.set('action', action);
89
+ if (entityTable !== 'all') params.set('entity_table', entityTable);
90
+ if (actorUserId.trim()) params.set('actor_user_id', actorUserId.trim());
91
+ if (from) params.set('from', from);
92
+ if (to) params.set('to', to);
93
+
94
+ const response = await request({
95
+ url: `/finance/audit-logs?${params.toString()}`,
96
+ method: 'GET',
97
+ });
98
+
99
+ return (response.data || {
100
+ total: 0,
101
+ lastPage: 1,
102
+ page: 1,
103
+ pageSize,
104
+ data: [],
105
+ }) as PaginatedAuditLogs;
106
+ },
107
+ placeholderData: (old) => old,
108
+ });
109
+
110
+ const rows = data?.data || [];
111
+
112
+ const entityOptions = useMemo(() => {
113
+ const unique = new Set(rows.map((row) => row.entityTable).filter(Boolean));
114
+ return ['all', ...Array.from(unique).sort()];
115
+ }, [rows]);
116
+
117
+ return (
118
+ <Page>
119
+ <PageHeader
120
+ title={t('header.title')}
121
+ description={t('header.description')}
122
+ breadcrumbs={[
123
+ { label: t('breadcrumbs.finance'), href: '/finance' },
124
+ {
125
+ label: t('breadcrumbs.administration'),
126
+ href: '/finance/administration',
127
+ },
128
+ { label: t('breadcrumbs.current') },
129
+ ]}
130
+ />
131
+
132
+ <Card>
133
+ <CardHeader>
134
+ <CardTitle>{t('card.title')}</CardTitle>
135
+ <CardDescription>{t('card.description')}</CardDescription>
136
+ </CardHeader>
137
+
138
+ <CardContent className="space-y-4">
139
+ <div className="grid gap-3 md:grid-cols-2 lg:grid-cols-4">
140
+ <div className="relative lg:col-span-2">
141
+ <Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
142
+ <Input
143
+ placeholder={t('filters.searchPlaceholder')}
144
+ value={search}
145
+ onChange={(event) => {
146
+ setSearch(event.target.value);
147
+ setPage(1);
148
+ }}
149
+ className="pl-8"
150
+ />
151
+ </div>
152
+
153
+ <Select
154
+ value={action}
155
+ onValueChange={(value) => {
156
+ setAction(value);
157
+ setPage(1);
158
+ }}
159
+ >
160
+ <SelectTrigger>
161
+ <SelectValue placeholder={t('filters.action')} />
162
+ </SelectTrigger>
163
+ <SelectContent>
164
+ <SelectItem value="all">{t('filters.allActions')}</SelectItem>
165
+ <SelectItem value="create">create</SelectItem>
166
+ <SelectItem value="update">update</SelectItem>
167
+ <SelectItem value="delete">delete</SelectItem>
168
+ </SelectContent>
169
+ </Select>
170
+
171
+ <Input
172
+ placeholder={t('filters.userPlaceholder')}
173
+ value={actorUserId}
174
+ onChange={(event) => {
175
+ setActorUserId(event.target.value);
176
+ setPage(1);
177
+ }}
178
+ />
179
+
180
+ <Select
181
+ value={entityTable}
182
+ onValueChange={(value) => {
183
+ setEntityTable(value);
184
+ setPage(1);
185
+ }}
186
+ >
187
+ <SelectTrigger>
188
+ <SelectValue placeholder={t('filters.entity')} />
189
+ </SelectTrigger>
190
+ <SelectContent>
191
+ {entityOptions.map((option) => (
192
+ <SelectItem key={option} value={option}>
193
+ {option === 'all' ? t('filters.allEntities') : option}
194
+ </SelectItem>
195
+ ))}
196
+ </SelectContent>
197
+ </Select>
198
+
199
+ <Input
200
+ type="date"
201
+ value={from}
202
+ onChange={(event) => {
203
+ setFrom(event.target.value);
204
+ setPage(1);
205
+ }}
206
+ />
207
+
208
+ <Input
209
+ type="date"
210
+ value={to}
211
+ onChange={(event) => {
212
+ setTo(event.target.value);
213
+ setPage(1);
214
+ }}
215
+ />
216
+
217
+ <Button
218
+ variant="outline"
219
+ className="gap-2"
220
+ onClick={() => {
221
+ setSearch('');
222
+ setAction('all');
223
+ setEntityTable('all');
224
+ setActorUserId('');
225
+ setFrom('');
226
+ setTo('');
227
+ setPage(1);
228
+ refetch();
229
+ }}
230
+ >
231
+ <X className="h-4 w-4" />
232
+ {t('filters.clear')}
233
+ </Button>
234
+ </div>
235
+
236
+ <div className="rounded-md border">
237
+ <Table>
238
+ <TableHeader>
239
+ <TableRow>
240
+ <TableHead>{t('table.headers.date')}</TableHead>
241
+ <TableHead>{t('table.headers.author')}</TableHead>
242
+ <TableHead>{t('table.headers.action')}</TableHead>
243
+ <TableHead>{t('table.headers.entity')}</TableHead>
244
+ <TableHead>{t('table.headers.record')}</TableHead>
245
+ <TableHead>{t('table.headers.summary')}</TableHead>
246
+ <TableHead>IP</TableHead>
247
+ </TableRow>
248
+ </TableHeader>
249
+ <TableBody>
250
+ {isLoading ? (
251
+ <TableRow>
252
+ <TableCell colSpan={7} className="h-24 text-center">
253
+ {t('table.loading')}
254
+ </TableCell>
255
+ </TableRow>
256
+ ) : rows.length === 0 ? (
257
+ <TableRow>
258
+ <TableCell colSpan={7} className="h-24 text-center">
259
+ {t('table.empty')}
260
+ </TableCell>
261
+ </TableRow>
262
+ ) : (
263
+ rows.map((row) => (
264
+ <TableRow key={row.id}>
265
+ <TableCell>
266
+ {row.createdAt
267
+ ? new Date(row.createdAt).toLocaleString('pt-BR')
268
+ : '-'}
269
+ </TableCell>
270
+ <TableCell>
271
+ <div className="flex flex-col">
272
+ <span>{row.actorName || '-'}</span>
273
+ <span className="text-xs text-muted-foreground">
274
+ {row.actorEmail || row.actorUserId || '-'}
275
+ </span>
276
+ </div>
277
+ </TableCell>
278
+ <TableCell>
279
+ <Badge variant="outline">{row.action}</Badge>
280
+ </TableCell>
281
+ <TableCell>{row.entityTable}</TableCell>
282
+ <TableCell>{row.entityId}</TableCell>
283
+ <TableCell className="max-w-[360px] truncate">
284
+ {row.summary || '-'}
285
+ </TableCell>
286
+ <TableCell>{row.ipAddress || '-'}</TableCell>
287
+ </TableRow>
288
+ ))
289
+ )}
290
+ </TableBody>
291
+ </Table>
292
+ </div>
293
+
294
+ <PaginationFooter
295
+ currentPage={data?.page || page}
296
+ pageSize={data?.pageSize || pageSize}
297
+ totalItems={data?.total || 0}
298
+ onPageChange={(newPage) => setPage(newPage)}
299
+ onPageSizeChange={(newPageSize) => {
300
+ setPageSize(newPageSize);
301
+ setPage(1);
302
+ }}
303
+ pageSizeOptions={[10, 20, 30, 40, 50]}
304
+ />
305
+ </CardContent>
306
+ </Card>
307
+ </Page>
308
+ );
309
+ }