@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.
@@ -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={isUploadingFile || form.formState.isSubmitting}
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 type="submit" disabled={form.formState.isSubmitting}>
518
- {t('common.save')}
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 type="submit" disabled={form.formState.isSubmitting}>
376
- {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')}
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.232",
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/tag": "0.0.232",
18
- "@hed-hog/contact": "0.0.232"
18
+ "@hed-hog/contact": "0.0.235",
19
+ "@hed-hog/core": "0.0.235"
19
20
  },
20
21
  "exports": {
21
22
  ".": {
@@ -0,0 +1,9 @@
1
+ import { Type } from 'class-transformer';
2
+ import { IsInt, IsOptional } from 'class-validator';
3
+
4
+ export class ExtractFinancialTitleFromFileDto {
5
+ @IsOptional()
6
+ @Type(() => Number)
7
+ @IsInt()
8
+ file_id?: number;
9
+ }
@@ -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
- Body,
6
- Controller,
7
- Get,
8
- Param,
9
- ParseIntPipe,
10
- Post,
11
- Query,
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
  }