@hed-hog/finance 0.0.232 → 0.0.235
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/dist/dto/extract-financial-title-from-file.dto.d.ts +4 -0
- package/dist/dto/extract-financial-title-from-file.dto.d.ts.map +1 -0
- package/dist/dto/extract-financial-title-from-file.dto.js +24 -0
- package/dist/dto/extract-financial-title-from-file.dto.js.map +1 -0
- package/dist/finance-installments.controller.d.ts +43 -0
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.js +26 -0
- package/dist/finance-installments.controller.js.map +1 -1
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +3 -1
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +41 -1
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +483 -4
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/route.yaml +18 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +168 -3
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +277 -2
- package/package.json +4 -3
- package/src/dto/extract-financial-title-from-file.dto.ts +9 -0
- package/src/finance-installments.controller.ts +33 -7
- package/src/finance.module.ts +3 -1
- package/src/finance.service.ts +671 -1
package/hedhog/data/route.yaml
CHANGED
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
- where:
|
|
35
35
|
slug: admin-finance
|
|
36
36
|
|
|
37
|
+
- url: /finance/accounts-payable/installments/extract-from-file
|
|
38
|
+
method: POST
|
|
39
|
+
relations:
|
|
40
|
+
role:
|
|
41
|
+
- where:
|
|
42
|
+
slug: admin
|
|
43
|
+
- where:
|
|
44
|
+
slug: admin-finance
|
|
45
|
+
|
|
37
46
|
- url: /finance/accounts-receivable/installments
|
|
38
47
|
method: GET
|
|
39
48
|
relations:
|
|
@@ -61,6 +70,15 @@
|
|
|
61
70
|
- where:
|
|
62
71
|
slug: admin-finance
|
|
63
72
|
|
|
73
|
+
- url: /finance/accounts-receivable/installments/extract-from-file
|
|
74
|
+
method: POST
|
|
75
|
+
relations:
|
|
76
|
+
role:
|
|
77
|
+
- where:
|
|
78
|
+
slug: admin
|
|
79
|
+
- where:
|
|
80
|
+
slug: admin-finance
|
|
81
|
+
|
|
64
82
|
- url: /finance/bank-accounts
|
|
65
83
|
method: GET
|
|
66
84
|
relations:
|
|
@@ -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,11 @@ function NovoTituloSheet({
|
|
|
100
101
|
const [uploadedFileId, setUploadedFileId] = useState<number | null>(null);
|
|
101
102
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
|
102
103
|
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
|
104
|
+
const [isExtractingFileData, setIsExtractingFileData] = useState(false);
|
|
105
|
+
const [extractionConfidence, setExtractionConfidence] = useState<
|
|
106
|
+
number | null
|
|
107
|
+
>(null);
|
|
108
|
+
const [extractionWarnings, setExtractionWarnings] = useState<string[]>([]);
|
|
103
109
|
const [uploadProgress, setUploadProgress] = useState(0);
|
|
104
110
|
|
|
105
111
|
const normalizeFilenameForDisplay = (filename: string) => {
|
|
@@ -164,6 +170,8 @@ function NovoTituloSheet({
|
|
|
164
170
|
form.reset();
|
|
165
171
|
setUploadedFileId(null);
|
|
166
172
|
setUploadedFileName('');
|
|
173
|
+
setExtractionConfidence(null);
|
|
174
|
+
setExtractionWarnings([]);
|
|
167
175
|
setOpen(false);
|
|
168
176
|
showToastHandler?.('success', 'Título criado com sucesso');
|
|
169
177
|
} catch {
|
|
@@ -175,6 +183,8 @@ function NovoTituloSheet({
|
|
|
175
183
|
form.reset();
|
|
176
184
|
setUploadedFileId(null);
|
|
177
185
|
setUploadedFileName('');
|
|
186
|
+
setExtractionConfidence(null);
|
|
187
|
+
setExtractionWarnings([]);
|
|
178
188
|
setUploadProgress(0);
|
|
179
189
|
setOpen(false);
|
|
180
190
|
};
|
|
@@ -215,9 +225,113 @@ function NovoTituloSheet({
|
|
|
215
225
|
);
|
|
216
226
|
setUploadProgress(100);
|
|
217
227
|
showToastHandler?.('success', 'Arquivo relacionado com sucesso');
|
|
228
|
+
|
|
229
|
+
setIsExtractingFileData(true);
|
|
230
|
+
try {
|
|
231
|
+
const extraction = await request<{
|
|
232
|
+
documento?: string | null;
|
|
233
|
+
fornecedorId?: string;
|
|
234
|
+
competencia?: string;
|
|
235
|
+
vencimento?: string;
|
|
236
|
+
valor?: number | null;
|
|
237
|
+
categoriaId?: string;
|
|
238
|
+
centroCustoId?: string;
|
|
239
|
+
metodo?: string;
|
|
240
|
+
descricao?: string | null;
|
|
241
|
+
confidence?: number | null;
|
|
242
|
+
confidenceLevel?: 'low' | 'high' | null;
|
|
243
|
+
warnings?: string[];
|
|
244
|
+
}>({
|
|
245
|
+
url: '/finance/accounts-payable/installments/extract-from-file',
|
|
246
|
+
method: 'POST',
|
|
247
|
+
data: {
|
|
248
|
+
file_id: data.id,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const extracted = extraction.data || {};
|
|
253
|
+
setExtractionConfidence(
|
|
254
|
+
typeof extracted.confidence === 'number' ? extracted.confidence : null
|
|
255
|
+
);
|
|
256
|
+
setExtractionWarnings(
|
|
257
|
+
Array.isArray(extracted.warnings)
|
|
258
|
+
? extracted.warnings.filter(Boolean)
|
|
259
|
+
: []
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (extracted.documento) {
|
|
263
|
+
form.setValue('documento', extracted.documento, {
|
|
264
|
+
shouldValidate: true,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (extracted.fornecedorId) {
|
|
269
|
+
form.setValue('fornecedorId', extracted.fornecedorId, {
|
|
270
|
+
shouldValidate: true,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (extracted.competencia) {
|
|
275
|
+
form.setValue('competencia', extracted.competencia, {
|
|
276
|
+
shouldValidate: true,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (extracted.vencimento) {
|
|
281
|
+
form.setValue('vencimento', extracted.vencimento, {
|
|
282
|
+
shouldValidate: true,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (typeof extracted.valor === 'number' && extracted.valor > 0) {
|
|
287
|
+
form.setValue('valor', extracted.valor, {
|
|
288
|
+
shouldValidate: true,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (extracted.categoriaId) {
|
|
293
|
+
form.setValue('categoriaId', extracted.categoriaId, {
|
|
294
|
+
shouldValidate: true,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (extracted.centroCustoId) {
|
|
299
|
+
form.setValue('centroCustoId', extracted.centroCustoId, {
|
|
300
|
+
shouldValidate: true,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (extracted.metodo) {
|
|
305
|
+
form.setValue('metodo', extracted.metodo, {
|
|
306
|
+
shouldValidate: true,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (extracted.descricao) {
|
|
311
|
+
form.setValue('descricao', extracted.descricao, {
|
|
312
|
+
shouldValidate: true,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
showToastHandler?.(
|
|
317
|
+
'success',
|
|
318
|
+
'Dados da fatura extraídos e preenchidos automaticamente'
|
|
319
|
+
);
|
|
320
|
+
} catch {
|
|
321
|
+
setExtractionConfidence(null);
|
|
322
|
+
setExtractionWarnings([]);
|
|
323
|
+
showToastHandler?.(
|
|
324
|
+
'error',
|
|
325
|
+
'Não foi possível extrair os dados automaticamente'
|
|
326
|
+
);
|
|
327
|
+
} finally {
|
|
328
|
+
setIsExtractingFileData(false);
|
|
329
|
+
}
|
|
218
330
|
} catch {
|
|
219
331
|
setUploadedFileId(null);
|
|
220
332
|
setUploadedFileName('');
|
|
333
|
+
setExtractionConfidence(null);
|
|
334
|
+
setExtractionWarnings([]);
|
|
221
335
|
setUploadProgress(0);
|
|
222
336
|
showToastHandler?.('error', 'Não foi possível enviar o arquivo');
|
|
223
337
|
} finally {
|
|
@@ -280,10 +394,16 @@ function NovoTituloSheet({
|
|
|
280
394
|
|
|
281
395
|
setUploadedFileId(null);
|
|
282
396
|
setUploadedFileName('');
|
|
397
|
+
setExtractionConfidence(null);
|
|
398
|
+
setExtractionWarnings([]);
|
|
283
399
|
setUploadProgress(0);
|
|
284
400
|
void uploadRelatedFile(file);
|
|
285
401
|
}}
|
|
286
|
-
disabled={
|
|
402
|
+
disabled={
|
|
403
|
+
isUploadingFile ||
|
|
404
|
+
isExtractingFileData ||
|
|
405
|
+
form.formState.isSubmitting
|
|
406
|
+
}
|
|
287
407
|
/>
|
|
288
408
|
</div>
|
|
289
409
|
{isUploadingFile && (
|
|
@@ -299,6 +419,37 @@ function NovoTituloSheet({
|
|
|
299
419
|
Arquivo relacionado: {uploadedFileName}
|
|
300
420
|
</p>
|
|
301
421
|
)}
|
|
422
|
+
{isExtractingFileData && (
|
|
423
|
+
<div className="rounded-md border border-primary/30 bg-primary/5 p-3">
|
|
424
|
+
<div className="flex items-center gap-2 text-sm font-medium text-primary">
|
|
425
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
426
|
+
Analisando documento com IA
|
|
427
|
+
</div>
|
|
428
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
429
|
+
Os campos serão preenchidos automaticamente em instantes.
|
|
430
|
+
Revise os dados antes de salvar.
|
|
431
|
+
</p>
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
{!isExtractingFileData &&
|
|
435
|
+
extractionConfidence !== null &&
|
|
436
|
+
extractionConfidence < 70 && (
|
|
437
|
+
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3">
|
|
438
|
+
<div className="text-sm font-medium text-destructive">
|
|
439
|
+
Confiança da extração:{' '}
|
|
440
|
+
{Math.round(extractionConfidence)}%
|
|
441
|
+
</div>
|
|
442
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
443
|
+
Revise principalmente valor e vencimento antes de
|
|
444
|
+
salvar.
|
|
445
|
+
</p>
|
|
446
|
+
{extractionWarnings.length > 0 && (
|
|
447
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
448
|
+
{extractionWarnings[0]}
|
|
449
|
+
</p>
|
|
450
|
+
)}
|
|
451
|
+
</div>
|
|
452
|
+
)}
|
|
302
453
|
</div>
|
|
303
454
|
|
|
304
455
|
<FormField
|
|
@@ -514,8 +665,22 @@ function NovoTituloSheet({
|
|
|
514
665
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
515
666
|
{t('common.cancel')}
|
|
516
667
|
</Button>
|
|
517
|
-
<Button
|
|
518
|
-
|
|
668
|
+
<Button
|
|
669
|
+
type="submit"
|
|
670
|
+
disabled={
|
|
671
|
+
form.formState.isSubmitting ||
|
|
672
|
+
isUploadingFile ||
|
|
673
|
+
isExtractingFileData
|
|
674
|
+
}
|
|
675
|
+
>
|
|
676
|
+
{(isUploadingFile || isExtractingFileData) && (
|
|
677
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
678
|
+
)}
|
|
679
|
+
{isExtractingFileData
|
|
680
|
+
? 'Preenchendo com IA...'
|
|
681
|
+
: isUploadingFile
|
|
682
|
+
? 'Enviando arquivo...'
|
|
683
|
+
: t('common.save')}
|
|
519
684
|
</Button>
|
|
520
685
|
</div>
|
|
521
686
|
</form>
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { Input } from '@/components/ui/input';
|
|
23
23
|
import { InputMoney } from '@/components/ui/input-money';
|
|
24
24
|
import { Money } from '@/components/ui/money';
|
|
25
|
+
import { Progress } from '@/components/ui/progress';
|
|
25
26
|
import {
|
|
26
27
|
Select,
|
|
27
28
|
SelectContent,
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
Download,
|
|
54
55
|
Edit,
|
|
55
56
|
Eye,
|
|
57
|
+
Loader2,
|
|
56
58
|
MoreHorizontal,
|
|
57
59
|
Paperclip,
|
|
58
60
|
Plus,
|
|
@@ -95,6 +97,33 @@ function NovoTituloSheet({
|
|
|
95
97
|
}) {
|
|
96
98
|
const { request, showToastHandler } = useApp();
|
|
97
99
|
const [open, setOpen] = useState(false);
|
|
100
|
+
const [uploadedFileId, setUploadedFileId] = useState<number | null>(null);
|
|
101
|
+
const [uploadedFileName, setUploadedFileName] = useState('');
|
|
102
|
+
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
|
103
|
+
const [isExtractingFileData, setIsExtractingFileData] = useState(false);
|
|
104
|
+
const [extractionConfidence, setExtractionConfidence] = useState<
|
|
105
|
+
number | null
|
|
106
|
+
>(null);
|
|
107
|
+
const [extractionWarnings, setExtractionWarnings] = useState<string[]>([]);
|
|
108
|
+
const [uploadProgress, setUploadProgress] = useState(0);
|
|
109
|
+
|
|
110
|
+
const normalizeFilenameForDisplay = (filename: string) => {
|
|
111
|
+
if (!filename) {
|
|
112
|
+
return filename;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!/Ã.|Â.|â[\u0080-\u00BF]/.test(filename)) {
|
|
116
|
+
return filename;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const bytes = Uint8Array.from(filename, (char) => char.charCodeAt(0));
|
|
121
|
+
const decoded = new TextDecoder('utf-8').decode(bytes);
|
|
122
|
+
return /Ã.|Â.|â[\u0080-\u00BF]/.test(decoded) ? filename : decoded;
|
|
123
|
+
} catch {
|
|
124
|
+
return filename;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
98
127
|
|
|
99
128
|
const form = useForm<NewTitleFormValues>({
|
|
100
129
|
resolver: zodResolver(newTitleFormSchema),
|
|
@@ -132,11 +161,16 @@ function NovoTituloSheet({
|
|
|
132
161
|
: undefined,
|
|
133
162
|
payment_channel: values.canal || undefined,
|
|
134
163
|
description: values.descricao?.trim() || undefined,
|
|
164
|
+
attachment_file_ids: uploadedFileId ? [uploadedFileId] : undefined,
|
|
135
165
|
},
|
|
136
166
|
});
|
|
137
167
|
|
|
138
168
|
await onCreated();
|
|
139
169
|
form.reset();
|
|
170
|
+
setUploadedFileId(null);
|
|
171
|
+
setUploadedFileName('');
|
|
172
|
+
setExtractionConfidence(null);
|
|
173
|
+
setExtractionWarnings([]);
|
|
140
174
|
setOpen(false);
|
|
141
175
|
showToastHandler?.('success', 'Título criado com sucesso');
|
|
142
176
|
} catch {
|
|
@@ -146,9 +180,164 @@ function NovoTituloSheet({
|
|
|
146
180
|
|
|
147
181
|
const handleCancel = () => {
|
|
148
182
|
form.reset();
|
|
183
|
+
setUploadedFileId(null);
|
|
184
|
+
setUploadedFileName('');
|
|
185
|
+
setExtractionConfidence(null);
|
|
186
|
+
setExtractionWarnings([]);
|
|
187
|
+
setUploadProgress(0);
|
|
149
188
|
setOpen(false);
|
|
150
189
|
};
|
|
151
190
|
|
|
191
|
+
const uploadRelatedFile = async (file: File) => {
|
|
192
|
+
setIsUploadingFile(true);
|
|
193
|
+
setUploadProgress(0);
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const formData = new FormData();
|
|
197
|
+
formData.append('file', file);
|
|
198
|
+
formData.append('destination', 'finance/titles');
|
|
199
|
+
|
|
200
|
+
const { data } = await request<{ id: number; filename: string }>({
|
|
201
|
+
url: '/file',
|
|
202
|
+
method: 'POST',
|
|
203
|
+
data: formData,
|
|
204
|
+
headers: {
|
|
205
|
+
'Content-Type': 'multipart/form-data',
|
|
206
|
+
},
|
|
207
|
+
onUploadProgress: (event) => {
|
|
208
|
+
if (!event.total) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const progress = Math.round((event.loaded * 100) / event.total);
|
|
213
|
+
setUploadProgress(progress);
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (!data?.id) {
|
|
218
|
+
throw new Error('Arquivo inválido');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
setUploadedFileId(data.id);
|
|
222
|
+
setUploadedFileName(
|
|
223
|
+
normalizeFilenameForDisplay(data.filename || file.name)
|
|
224
|
+
);
|
|
225
|
+
setUploadProgress(100);
|
|
226
|
+
showToastHandler?.('success', 'Arquivo relacionado com sucesso');
|
|
227
|
+
|
|
228
|
+
setIsExtractingFileData(true);
|
|
229
|
+
try {
|
|
230
|
+
const extraction = await request<{
|
|
231
|
+
documento?: string | null;
|
|
232
|
+
clienteId?: string;
|
|
233
|
+
competencia?: string;
|
|
234
|
+
vencimento?: string;
|
|
235
|
+
valor?: number | null;
|
|
236
|
+
categoriaId?: string;
|
|
237
|
+
centroCustoId?: string;
|
|
238
|
+
canal?: string;
|
|
239
|
+
descricao?: string | null;
|
|
240
|
+
confidence?: number | null;
|
|
241
|
+
confidenceLevel?: 'low' | 'high' | null;
|
|
242
|
+
warnings?: string[];
|
|
243
|
+
}>({
|
|
244
|
+
url: '/finance/accounts-receivable/installments/extract-from-file',
|
|
245
|
+
method: 'POST',
|
|
246
|
+
data: {
|
|
247
|
+
file_id: data.id,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
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
|
+
);
|
|
260
|
+
|
|
261
|
+
if (extracted.documento) {
|
|
262
|
+
form.setValue('documento', extracted.documento, {
|
|
263
|
+
shouldValidate: true,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (extracted.clienteId) {
|
|
268
|
+
form.setValue('clienteId', extracted.clienteId, {
|
|
269
|
+
shouldValidate: true,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (extracted.competencia) {
|
|
274
|
+
form.setValue('competencia', extracted.competencia, {
|
|
275
|
+
shouldValidate: true,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (extracted.vencimento) {
|
|
280
|
+
form.setValue('vencimento', extracted.vencimento, {
|
|
281
|
+
shouldValidate: true,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (typeof extracted.valor === 'number' && extracted.valor > 0) {
|
|
286
|
+
form.setValue('valor', extracted.valor, {
|
|
287
|
+
shouldValidate: true,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (extracted.categoriaId) {
|
|
292
|
+
form.setValue('categoriaId', extracted.categoriaId, {
|
|
293
|
+
shouldValidate: true,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (extracted.centroCustoId) {
|
|
298
|
+
form.setValue('centroCustoId', extracted.centroCustoId, {
|
|
299
|
+
shouldValidate: true,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (extracted.canal) {
|
|
304
|
+
form.setValue('canal', extracted.canal, {
|
|
305
|
+
shouldValidate: true,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (extracted.descricao) {
|
|
310
|
+
form.setValue('descricao', extracted.descricao, {
|
|
311
|
+
shouldValidate: true,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
showToastHandler?.(
|
|
316
|
+
'success',
|
|
317
|
+
'Dados da fatura extraídos e preenchidos automaticamente'
|
|
318
|
+
);
|
|
319
|
+
} catch {
|
|
320
|
+
setExtractionConfidence(null);
|
|
321
|
+
setExtractionWarnings([]);
|
|
322
|
+
showToastHandler?.(
|
|
323
|
+
'error',
|
|
324
|
+
'Não foi possível extrair os dados automaticamente'
|
|
325
|
+
);
|
|
326
|
+
} finally {
|
|
327
|
+
setIsExtractingFileData(false);
|
|
328
|
+
}
|
|
329
|
+
} catch {
|
|
330
|
+
setUploadedFileId(null);
|
|
331
|
+
setUploadedFileName('');
|
|
332
|
+
setExtractionConfidence(null);
|
|
333
|
+
setExtractionWarnings([]);
|
|
334
|
+
setUploadProgress(0);
|
|
335
|
+
showToastHandler?.('error', 'Não foi possível enviar o arquivo');
|
|
336
|
+
} finally {
|
|
337
|
+
setIsUploadingFile(false);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
152
341
|
return (
|
|
153
342
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
154
343
|
<SheetTrigger asChild>
|
|
@@ -165,6 +354,78 @@ function NovoTituloSheet({
|
|
|
165
354
|
<Form {...form}>
|
|
166
355
|
<form className="px-4" onSubmit={form.handleSubmit(handleSubmit)}>
|
|
167
356
|
<div className="grid gap-4">
|
|
357
|
+
<div className="grid gap-2">
|
|
358
|
+
<FormLabel>Arquivo da fatura (opcional)</FormLabel>
|
|
359
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
|
360
|
+
<Input
|
|
361
|
+
type="file"
|
|
362
|
+
accept=".pdf,.png,.jpg,.jpeg,.xml,.txt"
|
|
363
|
+
onChange={(event) => {
|
|
364
|
+
const file = event.target.files?.[0];
|
|
365
|
+
if (!file) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
setUploadedFileId(null);
|
|
370
|
+
setUploadedFileName('');
|
|
371
|
+
setExtractionConfidence(null);
|
|
372
|
+
setExtractionWarnings([]);
|
|
373
|
+
setUploadProgress(0);
|
|
374
|
+
void uploadRelatedFile(file);
|
|
375
|
+
}}
|
|
376
|
+
disabled={
|
|
377
|
+
isUploadingFile ||
|
|
378
|
+
isExtractingFileData ||
|
|
379
|
+
form.formState.isSubmitting
|
|
380
|
+
}
|
|
381
|
+
/>
|
|
382
|
+
</div>
|
|
383
|
+
{isUploadingFile && (
|
|
384
|
+
<div className="space-y-1">
|
|
385
|
+
<Progress value={uploadProgress} className="h-2" />
|
|
386
|
+
<p className="text-xs text-muted-foreground">
|
|
387
|
+
Upload em andamento: {uploadProgress}%
|
|
388
|
+
</p>
|
|
389
|
+
</div>
|
|
390
|
+
)}
|
|
391
|
+
{uploadedFileId && (
|
|
392
|
+
<p className="text-xs text-muted-foreground">
|
|
393
|
+
Arquivo relacionado: {uploadedFileName}
|
|
394
|
+
</p>
|
|
395
|
+
)}
|
|
396
|
+
{isExtractingFileData && (
|
|
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>
|
|
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
|
+
)}
|
|
427
|
+
</div>
|
|
428
|
+
|
|
168
429
|
<FormField
|
|
169
430
|
control={form.control}
|
|
170
431
|
name="documento"
|
|
@@ -372,8 +633,22 @@ function NovoTituloSheet({
|
|
|
372
633
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
373
634
|
{t('common.cancel')}
|
|
374
635
|
</Button>
|
|
375
|
-
<Button
|
|
376
|
-
|
|
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')}
|
|
377
652
|
</Button>
|
|
378
653
|
</div>
|
|
379
654
|
</form>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/finance",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.235",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
"@hed-hog/api-locale": "0.0.11",
|
|
13
13
|
"@hed-hog/api-prisma": "0.0.4",
|
|
14
14
|
"@hed-hog/api-pagination": "0.0.5",
|
|
15
|
+
"@hed-hog/tag": "0.0.235",
|
|
15
16
|
"@hed-hog/api-types": "0.0.1",
|
|
16
17
|
"@hed-hog/api": "0.0.3",
|
|
17
|
-
"@hed-hog/
|
|
18
|
-
"@hed-hog/
|
|
18
|
+
"@hed-hog/contact": "0.0.235",
|
|
19
|
+
"@hed-hog/core": "0.0.235"
|
|
19
20
|
},
|
|
20
21
|
"exports": {
|
|
21
22
|
".": {
|
|
@@ -2,15 +2,19 @@ import { Role, User } from '@hed-hog/api';
|
|
|
2
2
|
import { Locale } from '@hed-hog/api-locale';
|
|
3
3
|
import { Pagination } from '@hed-hog/api-pagination';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
Body,
|
|
6
|
+
Controller,
|
|
7
|
+
Get,
|
|
8
|
+
Param,
|
|
9
|
+
ParseIntPipe,
|
|
10
|
+
Post,
|
|
11
|
+
Query,
|
|
12
|
+
UploadedFile,
|
|
13
|
+
UseInterceptors,
|
|
12
14
|
} from '@nestjs/common';
|
|
15
|
+
import { FileInterceptor } from '@nestjs/platform-express';
|
|
13
16
|
import { CreateFinancialTitleDto } from './dto/create-financial-title.dto';
|
|
17
|
+
import { ExtractFinancialTitleFromFileDto } from './dto/extract-financial-title-from-file.dto';
|
|
14
18
|
import { FinanceService } from './finance.service';
|
|
15
19
|
|
|
16
20
|
@Role()
|
|
@@ -50,6 +54,15 @@ export class FinanceInstallmentsController {
|
|
|
50
54
|
);
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
@Post('accounts-payable/installments/extract-from-file')
|
|
58
|
+
@UseInterceptors(FileInterceptor('file'))
|
|
59
|
+
async extractAccountsPayableInfoFromFile(
|
|
60
|
+
@UploadedFile() file: MulterFile,
|
|
61
|
+
@Body() data: ExtractFinancialTitleFromFileDto,
|
|
62
|
+
) {
|
|
63
|
+
return this.financeService.getAgentExtractInfoFromFile(file, data.file_id);
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
@Get('accounts-receivable/installments')
|
|
54
67
|
async listAccountsReceivableInstallments(
|
|
55
68
|
@Pagination() paginationParams,
|
|
@@ -81,4 +94,17 @@ export class FinanceInstallmentsController {
|
|
|
81
94
|
user?.id,
|
|
82
95
|
);
|
|
83
96
|
}
|
|
97
|
+
|
|
98
|
+
@Post('accounts-receivable/installments/extract-from-file')
|
|
99
|
+
@UseInterceptors(FileInterceptor('file'))
|
|
100
|
+
async extractAccountsReceivableInfoFromFile(
|
|
101
|
+
@UploadedFile() file: MulterFile,
|
|
102
|
+
@Body() data: ExtractFinancialTitleFromFileDto,
|
|
103
|
+
) {
|
|
104
|
+
return this.financeService.getAgentExtractInfoFromFile(
|
|
105
|
+
file,
|
|
106
|
+
data.file_id,
|
|
107
|
+
'receivable',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
84
110
|
}
|