@hed-hog/finance 0.0.364 → 0.0.366

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.
@@ -18,6 +18,11 @@ import {
18
18
  import { Badge } from '@/components/ui/badge';
19
19
  import { Button } from '@/components/ui/button';
20
20
  import { Checkbox } from '@/components/ui/checkbox';
21
+ import {
22
+ Collapsible,
23
+ CollapsibleContent,
24
+ CollapsibleTrigger,
25
+ } from '@/components/ui/collapsible';
21
26
  import { DateRangePicker } from '@/components/ui/date-range-picker';
22
27
  import {
23
28
  DropdownMenu,
@@ -56,6 +61,7 @@ import {
56
61
  SheetTrigger,
57
62
  } from '@/components/ui/sheet';
58
63
  import { StatusBadge } from '@/components/ui/status-badge';
64
+ import { Switch } from '@/components/ui/switch';
59
65
  import {
60
66
  Table,
61
67
  TableBody,
@@ -78,6 +84,8 @@ import { formatDistanceToNow } from 'date-fns';
78
84
  import { enUS, ptBR } from 'date-fns/locale';
79
85
  import {
80
86
  AlertTriangle,
87
+ ChevronDown,
88
+ ChevronUp,
81
89
  Download,
82
90
  Edit,
83
91
  Eye,
@@ -206,7 +214,7 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
206
214
  z
207
215
  .object({
208
216
  tipoTitulo: z.enum(['parcelado', 'recorrente']).default('parcelado'),
209
- documento: z.string().trim().min(1, t('validation.documentRequired')),
217
+ documento: z.string().trim().optional(),
210
218
  clienteId: z.string().min(1, t('validation.clientRequired')),
211
219
  competencia: z.string().optional(),
212
220
  vencimento: z.string().min(1, t('validation.dueDateRequired')),
@@ -225,6 +233,8 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
225
233
  amount: z
226
234
  .number()
227
235
  .min(0.01, t('validation.installmentAmountGreaterThanZero')),
236
+ paga: z.boolean().optional(),
237
+ dataPagamento: z.string().optional(),
228
238
  })
229
239
  )
230
240
  .min(1, t('validation.installmentsRequired')),
@@ -241,12 +251,65 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
241
251
  .or(z.literal('').transform(() => undefined)),
242
252
  })
243
253
  .optional(),
254
+ jaFoiPago: z.boolean().default(false),
255
+ pagamento: z
256
+ .object({
257
+ dataPagamento: z.string().optional(),
258
+ contaBancariaId: z.string().optional(),
259
+ valorPago: z.number().optional(),
260
+ juros: z.number().min(0).optional(),
261
+ multa: z.number().min(0).optional(),
262
+ desconto: z.number().min(0).optional(),
263
+ })
264
+ .optional(),
244
265
  categoriaId: z.string().optional(),
245
266
  centroCustoId: z.string().optional(),
246
267
  canal: z.string().optional(),
247
268
  descricao: z.string().optional(),
248
269
  })
249
270
  .superRefine((values, ctx) => {
271
+ if (values.jaFoiPago && values.tipoTitulo !== 'recorrente') {
272
+ if (!values.pagamento?.contaBancariaId) {
273
+ ctx.addIssue({
274
+ code: z.ZodIssueCode.custom,
275
+ path: ['pagamento', 'contaBancariaId'],
276
+ message: t('validation.bankAccountRequired'),
277
+ });
278
+ }
279
+
280
+ if (values.installmentsCount === 1) {
281
+ if (!values.pagamento?.dataPagamento) {
282
+ ctx.addIssue({
283
+ code: z.ZodIssueCode.custom,
284
+ path: ['pagamento', 'dataPagamento'],
285
+ message: t('validation.paymentDateRequired'),
286
+ });
287
+ }
288
+
289
+ const valorPago = values.pagamento?.valorPago ?? values.valor;
290
+ if (
291
+ !(valorPago > 0) ||
292
+ Math.round(valorPago * 100) > Math.round(values.valor * 100)
293
+ ) {
294
+ ctx.addIssue({
295
+ code: z.ZodIssueCode.custom,
296
+ path: ['pagamento', 'valorPago'],
297
+ message: t('validation.paidAmountInvalid'),
298
+ });
299
+ }
300
+ } else {
301
+ values.installments.forEach((installment, index) => {
302
+ if (installment.paga && !installment.dataPagamento) {
303
+ ctx.addIssue({
304
+ code: z.ZodIssueCode.custom,
305
+ path: ['installments', index, 'dataPagamento'],
306
+ message: t('validation.installmentPaymentDateRequired'),
307
+ });
308
+ }
309
+ });
310
+ }
311
+ }
312
+
250
313
  if (values.tipoTitulo === 'recorrente') {
251
314
  const r = values.recorrencia;
252
315
  if (!r?.dataFim && !r?.numOcorrencias) {
@@ -284,6 +347,35 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
284
347
 
285
348
  type NewTitleFormValues = z.infer<ReturnType<typeof getNewTitleFormSchema>>;
286
349
 
350
+ const getNewTitleDefaultValues = (): NewTitleFormValues => ({
351
+ tipoTitulo: 'parcelado',
352
+ documento: '',
353
+ clienteId: '',
354
+ competencia: '',
355
+ vencimento: '',
356
+ valor: 0,
357
+ installmentsCount: 1,
358
+ installments: [{ dueDate: '', amount: 0, paga: false, dataPagamento: '' }],
359
+ recorrencia: {
360
+ frequencia: 'monthly',
361
+ dataFim: '',
362
+ numOcorrencias: undefined,
363
+ },
364
+ jaFoiPago: false,
365
+ pagamento: {
366
+ dataPagamento: '',
367
+ contaBancariaId: '',
368
+ valorPago: undefined,
369
+ juros: undefined,
370
+ multa: undefined,
371
+ desconto: undefined,
372
+ },
373
+ categoriaId: '',
374
+ centroCustoId: '',
375
+ canal: '',
376
+ descricao: '',
377
+ });
378
+
287
379
  type ReceivableInstallmentDraftPayload = {
288
380
  mode: 'create' | 'edit';
289
381
  titleId: string | null;
@@ -302,6 +394,7 @@ const RECEIVABLE_EDIT_TITLE_DRAFT_STORAGE_KEY =
302
394
  function NovoTituloSheet({
303
395
  categorias,
304
396
  centrosCusto,
397
+ contasBancarias,
305
398
  t,
306
399
  onCreated,
307
400
  onOptionsUpdated,
@@ -310,6 +403,7 @@ function NovoTituloSheet({
310
403
  }: {
311
404
  categorias: any[];
312
405
  centrosCusto: any[];
406
+ contasBancarias: any[];
313
407
  t: ReturnType<typeof useTranslations>;
314
408
  onCreated: () => Promise<any> | void;
315
409
  onOptionsUpdated?: () => Promise<any> | void;
@@ -328,6 +422,12 @@ function NovoTituloSheet({
328
422
  const [isInstallmentsEdited, setIsInstallmentsEdited] = useState(false);
329
423
  const [autoRedistributeInstallments, setAutoRedistributeInstallments] =
330
424
  useState(true);
425
+ const [isConditionOpen, setIsConditionOpen] = useState(false);
426
+ const [isDetailsOpen, setIsDetailsOpen] = useState(false);
427
+ const paidAmountEditedRef = useRef(false);
428
+ const competenciaEditedRef = useRef(false);
429
+ const paymentDateEditedRef = useRef(false);
430
+ const submitAndNewRef = useRef(false);
331
431
  const redistributionTimeoutRef = useRef<
332
432
  Record<number, ReturnType<typeof setTimeout>>
333
433
  >({});
@@ -360,25 +460,7 @@ function NovoTituloSheet({
360
460
 
361
461
  const form = useForm<NewTitleFormValues>({
362
462
  resolver: zodResolver(newTitleFormSchema),
363
- defaultValues: {
364
- tipoTitulo: 'parcelado',
365
- documento: '',
366
- clienteId: '',
367
- competencia: '',
368
- vencimento: '',
369
- valor: 0,
370
- installmentsCount: 1,
371
- installments: [{ dueDate: '', amount: 0 }],
372
- recorrencia: {
373
- frequencia: 'monthly',
374
- dataFim: '',
375
- numOcorrencias: undefined,
376
- },
377
- categoriaId: '',
378
- centroCustoId: '',
379
- canal: '',
380
- descricao: '',
381
- },
463
+ defaultValues: getNewTitleDefaultValues(),
382
464
  });
383
465
 
384
466
  const { fields: installmentFields, replace: replaceInstallments } =
@@ -393,6 +475,7 @@ function NovoTituloSheet({
393
475
 
394
476
  const tipoTitulo = form.watch('tipoTitulo');
395
477
  const isRecorrente = tipoTitulo === 'recorrente';
478
+ const jaFoiPago = form.watch('jaFoiPago');
396
479
 
397
480
  const {
398
481
  clearDraft,
@@ -415,12 +498,23 @@ function NovoTituloSheet({
415
498
  installments: watchedFormValues.installments?.map((installment) => ({
416
499
  dueDate: installment?.dueDate ?? '',
417
500
  amount: Number(installment?.amount ?? 0),
418
- })) ?? [{ dueDate: '', amount: 0 }],
501
+ paga: installment?.paga ?? false,
502
+ dataPagamento: installment?.dataPagamento ?? '',
503
+ })) ?? [{ dueDate: '', amount: 0, paga: false, dataPagamento: '' }],
419
504
  recorrencia: {
420
505
  frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
421
506
  dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
422
507
  numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
423
508
  },
509
+ jaFoiPago: watchedFormValues.jaFoiPago ?? false,
510
+ pagamento: {
511
+ dataPagamento: watchedFormValues.pagamento?.dataPagamento ?? '',
512
+ contaBancariaId: watchedFormValues.pagamento?.contaBancariaId ?? '',
513
+ valorPago: watchedFormValues.pagamento?.valorPago,
514
+ juros: watchedFormValues.pagamento?.juros,
515
+ multa: watchedFormValues.pagamento?.multa,
516
+ desconto: watchedFormValues.pagamento?.desconto,
517
+ },
424
518
  categoriaId: watchedFormValues.categoriaId ?? '',
425
519
  centroCustoId: watchedFormValues.centroCustoId ?? '',
426
520
  canal: watchedFormValues.canal ?? '',
@@ -442,10 +536,13 @@ function NovoTituloSheet({
442
536
  (watchedFormValues.descricao ?? '').trim() ||
443
537
  (watchedFormValues.valor ?? 0) > 0 ||
444
538
  (watchedFormValues.installmentsCount ?? 1) !== 1 ||
539
+ (watchedFormValues.jaFoiPago ?? false) ||
540
+ (watchedFormValues.pagamento?.contaBancariaId ?? '').trim() ||
445
541
  (watchedFormValues.installments ?? []).some(
446
542
  (installment) =>
447
543
  (installment?.dueDate ?? '').trim() ||
448
- Number(installment?.amount ?? 0) > 0
544
+ Number(installment?.amount ?? 0) > 0 ||
545
+ (installment?.paga ?? false)
449
546
  ) ||
450
547
  uploadedFileId != null ||
451
548
  uploadedFileName.trim()
@@ -484,6 +581,12 @@ function NovoTituloSheet({
484
581
  const watchedDueDate = form.watch('vencimento');
485
582
  const watchedInstallments = form.watch('installments');
486
583
 
584
+ const condicao = isRecorrente
585
+ ? 'recorrente'
586
+ : (watchedInstallmentsCount ?? 1) > 1
587
+ ? 'parcelado'
588
+ : 'avista';
589
+
487
590
  useEffect(() => {
488
591
  if (!open) {
489
592
  return;
@@ -492,7 +595,8 @@ function NovoTituloSheet({
492
595
  const storedDraft = loadDraft();
493
596
 
494
597
  if (storedDraft?.payload.mode === 'create') {
495
- form.reset(storedDraft.payload.values);
598
+ const draftValues = storedDraft.payload.values;
599
+ form.reset({ ...getNewTitleDefaultValues(), ...draftValues });
496
600
  setUploadedFileId(storedDraft.payload.uploadedFileId ?? null);
497
601
  setUploadedFileName(storedDraft.payload.uploadedFileName ?? '');
498
602
  setExtractionConfidence(null);
@@ -504,28 +608,25 @@ function NovoTituloSheet({
504
608
  setAutoRedistributeInstallments(
505
609
  storedDraft.payload.autoRedistributeInstallments ?? true
506
610
  );
611
+ setIsConditionOpen(
612
+ (draftValues.installmentsCount ?? 1) > 1 ||
613
+ draftValues.tipoTitulo === 'recorrente'
614
+ );
615
+ setIsDetailsOpen(
616
+ Boolean(
617
+ (draftValues.categoriaId ?? '').trim() ||
618
+ (draftValues.centroCustoId ?? '').trim() ||
619
+ (draftValues.canal ?? '').trim() ||
620
+ (draftValues.descricao ?? '').trim()
621
+ )
622
+ );
623
+ paidAmountEditedRef.current = true;
624
+ competenciaEditedRef.current = true;
625
+ paymentDateEditedRef.current = true;
507
626
  return;
508
627
  }
509
628
 
510
- form.reset({
511
- tipoTitulo: 'parcelado',
512
- documento: '',
513
- clienteId: '',
514
- competencia: '',
515
- vencimento: '',
516
- valor: 0,
517
- installmentsCount: 1,
518
- installments: [{ dueDate: '', amount: 0 }],
519
- recorrencia: {
520
- frequencia: 'monthly',
521
- dataFim: '',
522
- numOcorrencias: undefined,
523
- },
524
- categoriaId: '',
525
- centroCustoId: '',
526
- canal: '',
527
- descricao: '',
528
- });
629
+ form.reset(getNewTitleDefaultValues());
529
630
  setUploadedFileId(null);
530
631
  setUploadedFileName('');
531
632
  setExtractionConfidence(null);
@@ -533,8 +634,48 @@ function NovoTituloSheet({
533
634
  setUploadProgress(0);
534
635
  setIsInstallmentsEdited(false);
535
636
  setAutoRedistributeInstallments(true);
637
+ setIsConditionOpen(false);
638
+ setIsDetailsOpen(false);
639
+ paidAmountEditedRef.current = false;
640
+ competenciaEditedRef.current = false;
641
+ paymentDateEditedRef.current = false;
536
642
  }, [form, loadDraft, open]);
537
643
 
644
+ useEffect(() => {
645
+ if (
646
+ !open ||
647
+ !jaFoiPago ||
648
+ condicao !== 'avista' ||
649
+ paidAmountEditedRef.current
650
+ ) {
651
+ return;
652
+ }
653
+
654
+ const currentPaidAmount = form.getValues('pagamento.valorPago');
655
+ if (currentPaidAmount !== watchedTotalValue) {
656
+ form.setValue('pagamento.valorPago', watchedTotalValue ?? 0);
657
+ }
658
+ }, [condicao, form, jaFoiPago, open, watchedTotalValue]);
659
+
660
+ useEffect(() => {
661
+ if (!open || !watchedDueDate) {
662
+ return;
663
+ }
664
+
665
+ if (!competenciaEditedRef.current) {
666
+ const nextCompetencia = watchedDueDate.slice(0, 7);
667
+ if (form.getValues('competencia') !== nextCompetencia) {
668
+ form.setValue('competencia', nextCompetencia);
669
+ }
670
+ }
671
+
672
+ if (!paymentDateEditedRef.current) {
673
+ if (form.getValues('pagamento.dataPagamento') !== watchedDueDate) {
674
+ form.setValue('pagamento.dataPagamento', watchedDueDate);
675
+ }
676
+ }
677
+ }, [form, open, watchedDueDate]);
678
+
538
679
  useEffect(() => {
539
680
  if (isInstallmentsEdited || !open) {
540
681
  return;
@@ -621,13 +762,17 @@ function NovoTituloSheet({
621
762
  }, []);
622
763
 
623
764
  const handleSubmit = async (values: NewTitleFormValues) => {
765
+ const saveAndNew = submitAndNewRef.current;
766
+ submitAndNewRef.current = false;
767
+
624
768
  try {
625
769
  const recorrente = values.tipoTitulo === 'recorrente';
770
+ const pagoNoCadastro = values.jaFoiPago && !recorrente;
626
771
  await request({
627
772
  url: '/finance/accounts-receivable/installments',
628
773
  method: 'POST',
629
774
  data: {
630
- document_number: values.documento,
775
+ document_number: values.documento?.trim() || undefined,
631
776
  person_id: Number(values.clienteId),
632
777
  competence_date: values.competencia
633
778
  ? `${values.competencia}-01`
@@ -641,6 +786,10 @@ function NovoTituloSheet({
641
786
  ? Number(values.centroCustoId)
642
787
  : undefined,
643
788
  payment_channel: values.canal || undefined,
789
+ bank_account_id:
790
+ pagoNoCadastro && values.pagamento?.contaBancariaId
791
+ ? Number(values.pagamento.contaBancariaId)
792
+ : undefined,
644
793
  description: values.descricao?.trim() || undefined,
645
794
  ...(recorrente
646
795
  ? {
@@ -656,6 +805,28 @@ function NovoTituloSheet({
656
805
  installment_number: index + 1,
657
806
  due_date: installment.dueDate || values.vencimento,
658
807
  amount: installment.amount,
808
+ ...(pagoNoCadastro
809
+ ? values.installmentsCount === 1
810
+ ? {
811
+ paid: true,
812
+ paid_at:
813
+ values.pagamento?.dataPagamento || undefined,
814
+ paid_amount:
815
+ values.pagamento?.valorPago || installment.amount,
816
+ interest: values.pagamento?.juros || undefined,
817
+ penalty: values.pagamento?.multa || undefined,
818
+ discount: values.pagamento?.desconto || undefined,
819
+ }
820
+ : installment.paga
821
+ ? {
822
+ paid: true,
823
+ paid_at:
824
+ installment.dataPagamento ||
825
+ installment.dueDate ||
826
+ values.vencimento,
827
+ }
828
+ : {}
829
+ : {}),
659
830
  })),
660
831
  }),
661
832
  attachment_file_ids: uploadedFileId ? [uploadedFileId] : undefined,
@@ -664,14 +835,35 @@ function NovoTituloSheet({
664
835
 
665
836
  clearDraft();
666
837
  await onCreated();
667
- form.reset();
838
+
839
+ const defaults = getNewTitleDefaultValues();
840
+ const keptDueDate = saveAndNew ? values.vencimento : '';
841
+ form.reset({
842
+ ...defaults,
843
+ vencimento: keptDueDate,
844
+ competencia: keptDueDate ? keptDueDate.slice(0, 7) : '',
845
+ pagamento: {
846
+ ...defaults.pagamento,
847
+ dataPagamento: keptDueDate,
848
+ },
849
+ });
668
850
  setUploadedFileId(null);
669
851
  setUploadedFileName('');
670
852
  setExtractionConfidence(null);
671
853
  setExtractionWarnings([]);
854
+ setUploadProgress(0);
672
855
  setIsInstallmentsEdited(false);
673
856
  setAutoRedistributeInstallments(true);
674
- setOpen(false);
857
+ setIsConditionOpen(false);
858
+ setIsDetailsOpen(false);
859
+ paidAmountEditedRef.current = false;
860
+ competenciaEditedRef.current = false;
861
+ paymentDateEditedRef.current = false;
862
+
863
+ if (!saveAndNew) {
864
+ setOpen(false);
865
+ }
866
+
675
867
  showToastHandler?.('success', t('messages.createSuccess'));
676
868
  } catch (error) {
677
869
  showToastHandler?.(
@@ -855,6 +1047,61 @@ function NovoTituloSheet({
855
1047
  }
856
1048
  };
857
1049
 
1050
+ const formattedDueDateLabel = watchedDueDate
1051
+ ? formatarData(`${watchedDueDate}T00:00:00`)
1052
+ : '-';
1053
+
1054
+ const conditionSummary = isRecorrente
1055
+ ? t('sections.paymentCondition.summary.recurring', {
1056
+ frequency: t(
1057
+ `recurrenceEditor.frequencies.${
1058
+ form.watch('recorrencia.frequencia') || 'monthly'
1059
+ }` as Parameters<typeof t>[0]
1060
+ ),
1061
+ })
1062
+ : condicao === 'parcelado'
1063
+ ? t('sections.paymentCondition.summary.installments', {
1064
+ count: watchedInstallmentsCount ?? 1,
1065
+ date: formattedDueDateLabel,
1066
+ })
1067
+ : t('sections.paymentCondition.summary.cash', {
1068
+ date: formattedDueDateLabel,
1069
+ });
1070
+
1071
+ const renderCanalField = () => (
1072
+ <FormField
1073
+ control={form.control}
1074
+ name="canal"
1075
+ render={({ field }) => (
1076
+ <FormItem>
1077
+ <FormLabel>{t('fields.channel')}</FormLabel>
1078
+ <Select value={field.value} onValueChange={field.onChange}>
1079
+ <FormControl>
1080
+ <SelectTrigger className="w-full">
1081
+ <SelectValue placeholder={t('common.select')} />
1082
+ </SelectTrigger>
1083
+ </FormControl>
1084
+ <SelectContent>
1085
+ <SelectItem value="boleto">{t('channels.boleto')}</SelectItem>
1086
+ <SelectItem value="pix">PIX</SelectItem>
1087
+ <SelectItem value="cartao">{t('channels.card')}</SelectItem>
1088
+ <SelectItem value="transferencia">
1089
+ {t('channels.transfer')}
1090
+ </SelectItem>
1091
+ <SelectItem value="debito_automatico">
1092
+ {t('channels.debitAutomatico')}
1093
+ </SelectItem>
1094
+ <SelectItem value="debito_em_conta">
1095
+ {t('channels.debitEmConta')}
1096
+ </SelectItem>
1097
+ </SelectContent>
1098
+ </Select>
1099
+ <FormMessage />
1100
+ </FormItem>
1101
+ )}
1102
+ />
1103
+ );
1104
+
858
1105
  return (
859
1106
  <Sheet open={open} onOpenChange={setOpen}>
860
1107
  <SheetTrigger asChild>
@@ -865,7 +1112,7 @@ function NovoTituloSheet({
865
1112
  </SheetTrigger>
866
1113
  <ResizableSheetContent
867
1114
  sheetId="finance-receivable-create-title"
868
- defaultWidth={896}
1115
+ defaultWidth={1024}
869
1116
  className="flex h-full w-full flex-col overflow-hidden p-0 gap-0"
870
1117
  >
871
1118
  <SheetHeader className="border-b border-border/50 px-4 py-4 sm:px-6">
@@ -878,10 +1125,12 @@ function NovoTituloSheet({
878
1125
  onSubmit={form.handleSubmit(handleSubmit, handleInvalidSubmit)}
879
1126
  >
880
1127
  <FinanceSheetBody className="pt-0">
881
- <FinanceSheetSection className="pt-0 mt-0">
882
- <div className="grid grid-cols-1 items-start gap-4 lg:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)]">
883
- <div className="grid gap-2">
884
- <FormLabel>{t('common.upload.label')}</FormLabel>
1128
+ <FinanceSheetSection
1129
+ className="pt-0 mt-0"
1130
+ title={t('sections.main.title')}
1131
+ description={t('sections.main.description')}
1132
+ actions={
1133
+ <div className="flex items-center gap-1">
885
1134
  <Input
886
1135
  ref={fileInputRef}
887
1136
  className="hidden"
@@ -903,113 +1152,95 @@ function NovoTituloSheet({
903
1152
  }
904
1153
  />
905
1154
 
906
- <div className="grid w-full grid-cols-2 gap-2">
1155
+ <Tooltip>
1156
+ <TooltipTrigger asChild>
1157
+ <Button
1158
+ type="button"
1159
+ variant="outline"
1160
+ size="icon"
1161
+ onClick={handleSelectFile}
1162
+ aria-label={
1163
+ uploadedFileId
1164
+ ? t('common.upload.change')
1165
+ : t('common.upload.label')
1166
+ }
1167
+ disabled={
1168
+ isUploadingFile ||
1169
+ isExtractingFileData ||
1170
+ form.formState.isSubmitting
1171
+ }
1172
+ >
1173
+ {isUploadingFile || isExtractingFileData ? (
1174
+ <Loader2 className="h-4 w-4 animate-spin" />
1175
+ ) : (
1176
+ <Upload className="h-4 w-4" />
1177
+ )}
1178
+ </Button>
1179
+ </TooltipTrigger>
1180
+ <TooltipContent>
1181
+ {isUploadingFile && !isExtractingFileData
1182
+ ? t('common.upload.uploadingProgress', {
1183
+ progress: uploadProgress,
1184
+ })
1185
+ : isExtractingFileData
1186
+ ? t('common.upload.processingAi')
1187
+ : uploadedFileId
1188
+ ? `${t('common.upload.selectedPrefix')} ${uploadedFileName} — ${t('common.upload.change')}`
1189
+ : t('common.upload.label')}
1190
+ </TooltipContent>
1191
+ </Tooltip>
1192
+
1193
+ {uploadedFileId && (
907
1194
  <Tooltip>
908
1195
  <TooltipTrigger asChild>
909
1196
  <Button
910
1197
  type="button"
911
1198
  variant="outline"
912
- className={
913
- uploadedFileId ? 'w-full' : 'col-span-2 w-full'
914
- }
915
- onClick={handleSelectFile}
916
- aria-label={
917
- uploadedFileId
918
- ? t('common.upload.change')
919
- : t('common.upload.upload')
920
- }
1199
+ size="icon"
1200
+ onClick={clearUploadedFile}
1201
+ aria-label={t('common.upload.remove')}
921
1202
  disabled={
922
1203
  isUploadingFile ||
923
1204
  isExtractingFileData ||
924
1205
  form.formState.isSubmitting
925
1206
  }
926
1207
  >
927
- {uploadedFileId ? (
928
- <Upload className="h-4 w-4" />
929
- ) : (
930
- <>
931
- <Upload className="mr-2 h-4 w-4" />
932
- {t('common.upload.upload')}
933
- </>
934
- )}
1208
+ <Trash2 className="h-4 w-4" />
935
1209
  </Button>
936
1210
  </TooltipTrigger>
937
1211
  <TooltipContent>
938
- {uploadedFileId
939
- ? t('common.upload.change')
940
- : t('common.upload.upload')}
1212
+ {t('common.upload.remove')}
941
1213
  </TooltipContent>
942
1214
  </Tooltip>
1215
+ )}
1216
+ </div>
1217
+ }
1218
+ >
1219
+ {!isExtractingFileData &&
1220
+ extractionConfidence !== null &&
1221
+ extractionConfidence < 70 && (
1222
+ <p className="text-xs text-destructive">
1223
+ {t('common.upload.lowConfidence', {
1224
+ confidence: Math.round(extractionConfidence),
1225
+ })}
1226
+ </p>
1227
+ )}
943
1228
 
944
- {uploadedFileId && (
945
- <Tooltip>
946
- <TooltipTrigger asChild>
947
- <Button
948
- type="button"
949
- variant="outline"
950
- className="w-full"
951
- onClick={clearUploadedFile}
952
- aria-label={t('common.upload.remove')}
953
- disabled={
954
- isUploadingFile ||
955
- isExtractingFileData ||
956
- form.formState.isSubmitting
957
- }
958
- >
959
- <Trash2 className="h-4 w-4" />
960
- </Button>
961
- </TooltipTrigger>
962
- <TooltipContent>
963
- {t('common.upload.remove')}
964
- </TooltipContent>
965
- </Tooltip>
966
- )}
967
- </div>
968
-
969
- <div className="space-y-1 rounded-lg border border-dashed border-border/70 bg-muted/20 p-3">
970
- {uploadedFileId && (
971
- <p className="truncate text-xs text-muted-foreground">
972
- {t('common.upload.selectedPrefix')} {uploadedFileName}
973
- </p>
974
- )}
975
-
976
- {isUploadingFile && !isExtractingFileData && (
977
- <div className="space-y-1">
978
- <Progress value={uploadProgress} className="h-2" />
979
- <p className="text-xs text-muted-foreground">
980
- {t('common.upload.uploadingProgress', {
981
- progress: uploadProgress,
982
- })}
983
- </p>
984
- </div>
985
- )}
986
-
987
- {isExtractingFileData && (
988
- <p className="flex items-center gap-2 text-xs text-primary">
989
- <Loader2 className="h-4 w-4 animate-spin" />
990
- {t('common.upload.processingAi')}
991
- </p>
992
- )}
993
-
994
- {!isExtractingFileData &&
995
- extractionConfidence !== null &&
996
- extractionConfidence < 70 && (
997
- <p className="text-xs text-destructive">
998
- {t('common.upload.lowConfidence', {
999
- confidence: Math.round(extractionConfidence),
1000
- })}
1001
- </p>
1002
- )}
1229
+ {!isExtractingFileData && extractionWarnings.length > 0 && (
1230
+ <p className="truncate text-xs text-muted-foreground">
1231
+ {extractionWarnings[0]}
1232
+ </p>
1233
+ )}
1003
1234
 
1004
- {!isExtractingFileData &&
1005
- extractionWarnings.length > 0 && (
1006
- <p className="truncate text-xs text-muted-foreground">
1007
- {extractionWarnings[0]}
1008
- </p>
1009
- )}
1010
- </div>
1011
- </div>
1235
+ <PersonPickerField
1236
+ form={form}
1237
+ name="clienteId"
1238
+ label={t('fields.client')}
1239
+ entityLabel="cliente"
1240
+ selectPlaceholder={t('common.select')}
1241
+ />
1012
1242
 
1243
+ <div className="grid gap-4 md:grid-cols-3">
1013
1244
  <FormField
1014
1245
  control={form.control}
1015
1246
  name="documento"
@@ -1017,230 +1248,124 @@ function NovoTituloSheet({
1017
1248
  <FormItem>
1018
1249
  <FormLabel>{t('fields.document')}</FormLabel>
1019
1250
  <FormControl>
1020
- <Input placeholder="FAT-00000" {...field} />
1251
+ <Input
1252
+ placeholder="FAT-00000"
1253
+ {...field}
1254
+ value={field.value || ''}
1255
+ />
1256
+ </FormControl>
1257
+ <FormMessage />
1258
+ </FormItem>
1259
+ )}
1260
+ />
1261
+
1262
+ <FormField
1263
+ control={form.control}
1264
+ name="vencimento"
1265
+ render={({ field }) => (
1266
+ <FormItem>
1267
+ <FormLabel>{t('fields.dueDate')}</FormLabel>
1268
+ <FormControl>
1269
+ <Input
1270
+ type="date"
1271
+ {...field}
1272
+ value={field.value || ''}
1273
+ />
1274
+ </FormControl>
1275
+ <FormMessage />
1276
+ </FormItem>
1277
+ )}
1278
+ />
1279
+
1280
+ <FormField
1281
+ control={form.control}
1282
+ name="valor"
1283
+ render={({ field }) => (
1284
+ <FormItem>
1285
+ <FormLabel>
1286
+ {isRecorrente
1287
+ ? t('fields.totalValueRecurring')
1288
+ : t('fields.totalValue')}
1289
+ </FormLabel>
1290
+ <FormControl>
1291
+ <InputMoney
1292
+ ref={field.ref}
1293
+ name={field.name}
1294
+ value={field.value}
1295
+ onBlur={field.onBlur}
1296
+ onValueChange={(value) => field.onChange(value ?? 0)}
1297
+ placeholder="0,00"
1298
+ />
1021
1299
  </FormControl>
1022
1300
  <FormMessage />
1023
1301
  </FormItem>
1024
1302
  )}
1025
1303
  />
1026
1304
  </div>
1305
+ </FinanceSheetSection>
1027
1306
 
1028
- <PersonPickerField
1029
- form={form}
1030
- name="clienteId"
1031
- label={t('fields.client')}
1032
- entityLabel="cliente"
1033
- selectPlaceholder={t('common.select')}
1034
- />
1307
+ {!isRecorrente && (
1308
+ <div className="space-y-4 rounded-lg border border-border/60 bg-muted/10 p-4">
1309
+ <FormField
1310
+ control={form.control}
1311
+ name="jaFoiPago"
1312
+ render={({ field }) => (
1313
+ <FormItem className="flex flex-row items-center justify-between gap-3 space-y-0">
1314
+ <div className="space-y-1">
1315
+ <FormLabel>{t('paidToggle.label')}</FormLabel>
1316
+ <p className="text-xs text-muted-foreground">
1317
+ {t('paidToggle.hint')}
1318
+ </p>
1319
+ </div>
1320
+ <FormControl>
1321
+ <Switch
1322
+ checked={field.value === true}
1323
+ onCheckedChange={(checked) => {
1324
+ const isPaid = checked === true;
1325
+ field.onChange(isPaid);
1326
+
1327
+ if (!isPaid) {
1328
+ return;
1329
+ }
1035
1330
 
1036
- {/* Tipo de título: Parcelado / Recorrente */}
1037
- <FormField
1038
- control={form.control}
1039
- name="tipoTitulo"
1040
- render={({ field }) => (
1041
- <FormItem>
1042
- <FormLabel>{t('titleType.label')}</FormLabel>
1043
- <div className="flex gap-2">
1044
- <Button
1045
- type="button"
1046
- variant={
1047
- field.value === 'parcelado' ? 'default' : 'outline'
1048
- }
1049
- size="sm"
1050
- onClick={() => field.onChange('parcelado')}
1051
- >
1052
- {t('titleType.installment')}
1053
- </Button>
1054
- <Button
1055
- type="button"
1056
- variant={
1057
- field.value === 'recorrente' ? 'default' : 'outline'
1058
- }
1059
- size="sm"
1060
- onClick={() => field.onChange('recorrente')}
1061
- >
1062
- {t('titleType.recurring')}
1063
- </Button>
1064
- </div>
1065
- </FormItem>
1066
- )}
1067
- />
1331
+ if (!form.getValues('pagamento.dataPagamento')) {
1332
+ form.setValue(
1333
+ 'pagamento.dataPagamento',
1334
+ form.getValues('vencimento') ||
1335
+ new Date().toISOString().slice(0, 10)
1336
+ );
1337
+ }
1068
1338
 
1069
- <div className="grid gap-4 xl:grid-cols-2">
1070
- <div className="grid gap-4 md:grid-cols-2">
1071
- <FormField
1072
- control={form.control}
1073
- name="competencia"
1074
- render={({ field }) => (
1075
- <FormItem>
1076
- <FormLabel>{t('fields.competency')}</FormLabel>
1077
- <FormControl>
1078
- <Input
1079
- type="month"
1080
- {...field}
1081
- value={field.value || ''}
1082
- />
1083
- </FormControl>
1084
- <FormMessage />
1085
- </FormItem>
1086
- )}
1087
- />
1088
-
1089
- <FormField
1090
- control={form.control}
1091
- name="vencimento"
1092
- render={({ field }) => (
1093
- <FormItem>
1094
- <FormLabel>
1095
- {isRecorrente
1096
- ? t('fields.dueDate')
1097
- : t('fields.dueDate')}
1098
- </FormLabel>
1099
- <FormControl>
1100
- <Input
1101
- type="date"
1102
- {...field}
1103
- value={field.value || ''}
1104
- />
1105
- </FormControl>
1106
- <FormMessage />
1107
- </FormItem>
1108
- )}
1109
- />
1110
- </div>
1111
-
1112
- <div className="grid gap-4 md:grid-cols-2">
1113
- <FormField
1114
- control={form.control}
1115
- name="valor"
1116
- render={({ field }) => (
1117
- <FormItem>
1118
- <FormLabel>
1119
- {isRecorrente
1120
- ? t('fields.totalValueRecurring')
1121
- : t('fields.totalValue')}
1122
- </FormLabel>
1123
- <FormControl>
1124
- <InputMoney
1125
- ref={field.ref}
1126
- name={field.name}
1127
- value={field.value}
1128
- onBlur={field.onBlur}
1129
- onValueChange={(value) =>
1130
- field.onChange(value ?? 0)
1339
+ if (!paidAmountEditedRef.current) {
1340
+ form.setValue(
1341
+ 'pagamento.valorPago',
1342
+ form.getValues('valor') ?? 0
1343
+ );
1131
1344
  }
1132
- placeholder="0,00"
1133
- />
1134
- </FormControl>
1135
- <FormMessage />
1136
- </FormItem>
1137
- )}
1138
- />
1139
-
1140
- {!isRecorrente && (
1141
- <FormField
1142
- control={form.control}
1143
- name="installmentsCount"
1144
- render={({ field }) => (
1145
- <FormItem>
1146
- <FormLabel>
1147
- {t('installmentsEditor.countLabel')}
1148
- </FormLabel>
1149
- <FormControl>
1150
- <Input
1151
- type="number"
1152
- min={1}
1153
- max={120}
1154
- value={field.value}
1155
- onChange={(event) => {
1156
- const nextValue = Number(
1157
- event.target.value || 1
1158
- );
1159
- field.onChange(
1160
- Number.isNaN(nextValue) ? 1 : nextValue
1161
- );
1162
- }}
1163
- />
1164
- </FormControl>
1165
- <FormMessage />
1166
- </FormItem>
1167
- )}
1168
- />
1169
- )}
1170
- </div>
1171
- </div>
1172
-
1173
- {!isRecorrente && (
1174
- <div className="space-y-3 rounded-md border p-3">
1175
- <div className="flex items-center justify-between gap-2">
1176
- <p className="text-sm font-medium">
1177
- {t('installmentsEditor.title')}
1178
- </p>
1179
- <Button
1180
- type="button"
1181
- variant="outline"
1182
- size="sm"
1183
- onClick={() => {
1184
- setIsInstallmentsEdited(false);
1185
- replaceInstallments(
1186
- buildEqualInstallments(
1187
- form.getValues('installmentsCount'),
1188
- form.getValues('valor'),
1189
- form.getValues('vencimento')
1190
- )
1191
- );
1192
- }}
1193
- >
1194
- {t('installmentsEditor.recalculate')}
1195
- </Button>
1196
- </div>
1197
-
1198
- <div className="flex items-center gap-2">
1199
- <Checkbox
1200
- id="auto-redistribute-installments-receivable"
1201
- checked={autoRedistributeInstallments}
1202
- onCheckedChange={(checked) =>
1203
- setAutoRedistributeInstallments(checked === true)
1204
- }
1205
- />
1206
- <Label
1207
- htmlFor="auto-redistribute-installments-receivable"
1208
- className="text-xs text-muted-foreground"
1209
- >
1210
- {t('installmentsEditor.autoRedistributeLabel')}
1211
- </Label>
1212
- </div>
1213
- {autoRedistributeInstallments && (
1214
- <p className="text-xs text-muted-foreground">
1215
- {t('installmentsEditor.autoRedistributeHint')}
1216
- </p>
1345
+ }}
1346
+ />
1347
+ </FormControl>
1348
+ </FormItem>
1217
1349
  )}
1350
+ />
1218
1351
 
1219
- <div className="space-y-2">
1220
- {installmentFields.map((installment, index) => (
1221
- <div
1222
- key={installment.id}
1223
- className="grid grid-cols-1 items-start gap-2 rounded-md border p-2 sm:grid-cols-[96px_1fr_160px]"
1224
- >
1225
- <div className="flex items-center text-sm text-muted-foreground">
1226
- #{index + 1}
1227
- </div>
1228
-
1352
+ {jaFoiPago && (
1353
+ <div className="space-y-4">
1354
+ <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
1355
+ {condicao === 'avista' && (
1229
1356
  <FormField
1230
1357
  control={form.control}
1231
- name={`installments.${index}.dueDate` as const}
1358
+ name="pagamento.dataPagamento"
1232
1359
  render={({ field }) => (
1233
1360
  <FormItem>
1234
- <FormLabel className="text-xs">
1235
- {t('installmentsEditor.dueDateLabel')}
1236
- </FormLabel>
1361
+ <FormLabel>{t('payment.dateLabel')}</FormLabel>
1237
1362
  <FormControl>
1238
1363
  <Input
1239
1364
  type="date"
1240
1365
  {...field}
1241
1366
  value={field.value || ''}
1242
1367
  onChange={(event) => {
1243
- setIsInstallmentsEdited(true);
1368
+ paymentDateEditedRef.current = true;
1244
1369
  field.onChange(event);
1245
1370
  }}
1246
1371
  />
@@ -1249,39 +1374,73 @@ function NovoTituloSheet({
1249
1374
  </FormItem>
1250
1375
  )}
1251
1376
  />
1377
+ )}
1252
1378
 
1379
+ <FormField
1380
+ control={form.control}
1381
+ name="pagamento.contaBancariaId"
1382
+ render={({ field }) => (
1383
+ <FormItem>
1384
+ <FormLabel>
1385
+ {t('payment.bankAccountLabel')}
1386
+ </FormLabel>
1387
+ <Select
1388
+ value={field.value || ''}
1389
+ onValueChange={field.onChange}
1390
+ >
1391
+ <FormControl>
1392
+ <SelectTrigger className="w-full">
1393
+ <SelectValue
1394
+ placeholder={t(
1395
+ 'payment.bankAccountPlaceholder'
1396
+ )}
1397
+ />
1398
+ </SelectTrigger>
1399
+ </FormControl>
1400
+ <SelectContent>
1401
+ {contasBancarias.map((conta: any) => (
1402
+ <SelectItem
1403
+ key={conta.id}
1404
+ value={String(conta.id)}
1405
+ >
1406
+ {[
1407
+ conta.descricao,
1408
+ conta.banco &&
1409
+ conta.banco !== conta.descricao
1410
+ ? conta.banco
1411
+ : null,
1412
+ ]
1413
+ .filter(Boolean)
1414
+ .join(' — ')}
1415
+ </SelectItem>
1416
+ ))}
1417
+ </SelectContent>
1418
+ </Select>
1419
+ <FormMessage />
1420
+ </FormItem>
1421
+ )}
1422
+ />
1423
+
1424
+ {renderCanalField()}
1425
+
1426
+ {condicao === 'avista' && (
1253
1427
  <FormField
1254
1428
  control={form.control}
1255
- name={`installments.${index}.amount` as const}
1429
+ name="pagamento.valorPago"
1256
1430
  render={({ field }) => (
1257
1431
  <FormItem>
1258
- <FormLabel className="text-xs">
1259
- {t('installmentsEditor.amountLabel')}
1432
+ <FormLabel>
1433
+ {t('payment.paidAmountLabel')}
1260
1434
  </FormLabel>
1261
1435
  <FormControl>
1262
1436
  <InputMoney
1263
1437
  ref={field.ref}
1264
1438
  name={field.name}
1265
1439
  value={field.value}
1266
- onBlur={() => {
1267
- field.onBlur();
1268
-
1269
- if (!autoRedistributeInstallments) {
1270
- return;
1271
- }
1272
-
1273
- clearScheduledRedistribution(index);
1274
- runInstallmentRedistribution(index);
1275
- }}
1440
+ onBlur={field.onBlur}
1276
1441
  onValueChange={(value) => {
1277
- setIsInstallmentsEdited(true);
1278
- field.onChange(value ?? 0);
1279
-
1280
- if (!autoRedistributeInstallments) {
1281
- return;
1282
- }
1283
-
1284
- scheduleInstallmentRedistribution(index);
1442
+ paidAmountEditedRef.current = true;
1443
+ field.onChange(value);
1285
1444
  }}
1286
1445
  placeholder="0,00"
1287
1446
  />
@@ -1290,125 +1449,186 @@ function NovoTituloSheet({
1290
1449
  </FormItem>
1291
1450
  )}
1292
1451
  />
1293
- </div>
1294
- ))}
1295
- </div>
1452
+ )}
1453
+ </div>
1296
1454
 
1297
- <p
1298
- className={`text-xs ${
1299
- installmentsDiffCents === 0
1300
- ? 'text-muted-foreground'
1301
- : 'text-destructive'
1302
- }`}
1303
- >
1304
- {t('installmentsEditor.totalPrefix', {
1305
- total: installmentsTotal.toFixed(2),
1306
- })}
1307
- {installmentsDiffCents > 0 &&
1308
- ` ${t('installmentsEditor.adjustmentNeeded')}`}
1309
- </p>
1310
- {form.formState.errors.installments?.message && (
1311
- <p className="text-xs text-destructive">
1312
- {form.formState.errors.installments.message}
1313
- </p>
1314
- )}
1315
- </div>
1316
- )}
1455
+ {condicao === 'avista' ? (
1456
+ <div className="grid gap-4 md:grid-cols-3">
1457
+ <FormField
1458
+ control={form.control}
1459
+ name="pagamento.juros"
1460
+ render={({ field }) => (
1461
+ <FormItem>
1462
+ <FormLabel>
1463
+ {t('payment.interestLabel')}
1464
+ </FormLabel>
1465
+ <FormControl>
1466
+ <InputMoney
1467
+ ref={field.ref}
1468
+ name={field.name}
1469
+ value={field.value}
1470
+ onBlur={field.onBlur}
1471
+ onValueChange={(value) =>
1472
+ field.onChange(value)
1473
+ }
1474
+ placeholder="0,00"
1475
+ />
1476
+ </FormControl>
1477
+ <FormMessage />
1478
+ </FormItem>
1479
+ )}
1480
+ />
1317
1481
 
1318
- {isRecorrente && (
1319
- <div className="space-y-3 rounded-md border p-3">
1320
- <p className="text-sm font-medium">
1321
- {t('recurrenceEditor.title')}
1322
- </p>
1323
- <div className="grid gap-4 md:grid-cols-3">
1324
- <FormField
1325
- control={form.control}
1326
- name="recorrencia.frequencia"
1327
- render={({ field }) => (
1328
- <FormItem>
1329
- <FormLabel>
1330
- {t('recurrenceEditor.frequencyLabel')}
1331
- </FormLabel>
1332
- <Select
1333
- value={field.value}
1334
- onValueChange={field.onChange}
1335
- >
1336
- <FormControl>
1337
- <SelectTrigger className="w-full">
1338
- <SelectValue />
1339
- </SelectTrigger>
1340
- </FormControl>
1341
- <SelectContent>
1342
- <SelectItem value="weekly">
1343
- {t('recurrenceEditor.frequencies.weekly')}
1344
- </SelectItem>
1345
- <SelectItem value="biweekly">
1346
- {t('recurrenceEditor.frequencies.biweekly')}
1347
- </SelectItem>
1348
- <SelectItem value="monthly">
1349
- {t('recurrenceEditor.frequencies.monthly')}
1350
- </SelectItem>
1351
- <SelectItem value="bimonthly">
1352
- {t('recurrenceEditor.frequencies.bimonthly')}
1353
- </SelectItem>
1354
- <SelectItem value="quarterly">
1355
- {t('recurrenceEditor.frequencies.quarterly')}
1356
- </SelectItem>
1357
- <SelectItem value="semiannual">
1358
- {t('recurrenceEditor.frequencies.semiannual')}
1359
- </SelectItem>
1360
- <SelectItem value="annual">
1361
- {t('recurrenceEditor.frequencies.annual')}
1362
- </SelectItem>
1363
- </SelectContent>
1364
- </Select>
1365
- <FormMessage />
1366
- </FormItem>
1367
- )}
1368
- />
1482
+ <FormField
1483
+ control={form.control}
1484
+ name="pagamento.multa"
1485
+ render={({ field }) => (
1486
+ <FormItem>
1487
+ <FormLabel>
1488
+ {t('payment.penaltyLabel')}
1489
+ </FormLabel>
1490
+ <FormControl>
1491
+ <InputMoney
1492
+ ref={field.ref}
1493
+ name={field.name}
1494
+ value={field.value}
1495
+ onBlur={field.onBlur}
1496
+ onValueChange={(value) =>
1497
+ field.onChange(value)
1498
+ }
1499
+ placeholder="0,00"
1500
+ />
1501
+ </FormControl>
1502
+ <FormMessage />
1503
+ </FormItem>
1504
+ )}
1505
+ />
1506
+
1507
+ <FormField
1508
+ control={form.control}
1509
+ name="pagamento.desconto"
1510
+ render={({ field }) => (
1511
+ <FormItem>
1512
+ <FormLabel>
1513
+ {t('payment.discountLabel')}
1514
+ </FormLabel>
1515
+ <FormControl>
1516
+ <InputMoney
1517
+ ref={field.ref}
1518
+ name={field.name}
1519
+ value={field.value}
1520
+ onBlur={field.onBlur}
1521
+ onValueChange={(value) =>
1522
+ field.onChange(value)
1523
+ }
1524
+ placeholder="0,00"
1525
+ />
1526
+ </FormControl>
1527
+ <FormMessage />
1528
+ </FormItem>
1529
+ )}
1530
+ />
1531
+ </div>
1532
+ ) : (
1533
+ <p className="text-xs text-muted-foreground">
1534
+ {t('payment.installmentsHint')}
1535
+ </p>
1536
+ )}
1537
+ </div>
1538
+ )}
1539
+ </div>
1540
+ )}
1369
1541
 
1370
- <FormField
1371
- control={form.control}
1372
- name="recorrencia.dataFim"
1373
- render={({ field }) => (
1374
- <FormItem>
1375
- <FormLabel>
1376
- {t('recurrenceEditor.endDateLabel')}
1377
- </FormLabel>
1378
- <FormControl>
1379
- <Input
1380
- type="date"
1381
- {...field}
1382
- value={field.value || ''}
1383
- />
1384
- </FormControl>
1385
- <FormMessage />
1386
- </FormItem>
1387
- )}
1388
- />
1542
+ <Collapsible
1543
+ open={isConditionOpen}
1544
+ onOpenChange={setIsConditionOpen}
1545
+ className="rounded-lg border border-border/60 bg-background"
1546
+ >
1547
+ <CollapsibleTrigger asChild>
1548
+ <div className="flex cursor-pointer items-center justify-between gap-3 px-4 py-3">
1549
+ <div className="min-w-0 space-y-0.5">
1550
+ <h3 className="text-sm font-semibold text-foreground">
1551
+ {t('sections.paymentCondition.title')}
1552
+ </h3>
1553
+ <p className="truncate text-xs text-muted-foreground">
1554
+ {conditionSummary}
1555
+ </p>
1556
+ </div>
1557
+ {isConditionOpen ? (
1558
+ <ChevronUp className="h-4 w-4 shrink-0 text-muted-foreground" />
1559
+ ) : (
1560
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
1561
+ )}
1562
+ </div>
1563
+ </CollapsibleTrigger>
1564
+
1565
+ <CollapsibleContent className="space-y-4 border-t border-border/50 px-4 py-4">
1566
+ <div className="flex flex-wrap gap-2">
1567
+ <Button
1568
+ type="button"
1569
+ variant={condicao === 'avista' ? 'default' : 'outline'}
1570
+ size="sm"
1571
+ onClick={() => {
1572
+ form.setValue('tipoTitulo', 'parcelado');
1573
+ form.setValue('installmentsCount', 1);
1574
+ setIsInstallmentsEdited(false);
1575
+ }}
1576
+ >
1577
+ {t('titleType.cash')}
1578
+ </Button>
1579
+ <Button
1580
+ type="button"
1581
+ variant={condicao === 'parcelado' ? 'default' : 'outline'}
1582
+ size="sm"
1583
+ onClick={() => {
1584
+ form.setValue('tipoTitulo', 'parcelado');
1585
+ if ((form.getValues('installmentsCount') ?? 1) < 2) {
1586
+ form.setValue('installmentsCount', 2);
1587
+ }
1588
+ setIsInstallmentsEdited(false);
1589
+ }}
1590
+ >
1591
+ {t('titleType.installment')}
1592
+ </Button>
1593
+ <Button
1594
+ type="button"
1595
+ variant={
1596
+ condicao === 'recorrente' ? 'default' : 'outline'
1597
+ }
1598
+ size="sm"
1599
+ onClick={() => {
1600
+ form.setValue('tipoTitulo', 'recorrente');
1601
+ form.setValue('jaFoiPago', false);
1602
+ }}
1603
+ >
1604
+ {t('titleType.recurring')}
1605
+ </Button>
1606
+ </div>
1389
1607
 
1608
+ {condicao === 'parcelado' && (
1609
+ <div className="grid gap-4 md:grid-cols-3">
1390
1610
  <FormField
1391
1611
  control={form.control}
1392
- name="recorrencia.numOcorrencias"
1612
+ name="installmentsCount"
1393
1613
  render={({ field }) => (
1394
1614
  <FormItem>
1395
1615
  <FormLabel>
1396
- {t('recurrenceEditor.maxOccurrencesLabel')}
1616
+ {t('installmentsEditor.countLabel')}
1397
1617
  </FormLabel>
1398
1618
  <FormControl>
1399
1619
  <Input
1400
1620
  type="number"
1401
1621
  min={1}
1402
- max={600}
1403
- {...field}
1404
- value={field.value ?? ''}
1405
- onChange={(e) =>
1622
+ max={120}
1623
+ value={field.value}
1624
+ onChange={(event) => {
1625
+ const nextValue = Number(
1626
+ event.target.value || 1
1627
+ );
1406
1628
  field.onChange(
1407
- e.target.value === ''
1408
- ? undefined
1409
- : Number(e.target.value)
1410
- )
1411
- }
1629
+ Number.isNaN(nextValue) ? 1 : nextValue
1630
+ );
1631
+ }}
1412
1632
  />
1413
1633
  </FormControl>
1414
1634
  <FormMessage />
@@ -1416,102 +1636,432 @@ function NovoTituloSheet({
1416
1636
  )}
1417
1637
  />
1418
1638
  </div>
1419
- {form.formState.errors.recorrencia?.message && (
1420
- <p className="text-xs text-destructive">
1421
- {form.formState.errors.recorrencia.message}
1639
+ )}
1640
+
1641
+ {condicao === 'parcelado' && (
1642
+ <div className="space-y-3 rounded-md border p-3">
1643
+ <div className="flex items-center justify-between gap-2">
1644
+ <p className="text-sm font-medium">
1645
+ {t('installmentsEditor.title')}
1646
+ </p>
1647
+ <Button
1648
+ type="button"
1649
+ variant="outline"
1650
+ size="sm"
1651
+ onClick={() => {
1652
+ setIsInstallmentsEdited(false);
1653
+ replaceInstallments(
1654
+ buildEqualInstallments(
1655
+ form.getValues('installmentsCount'),
1656
+ form.getValues('valor'),
1657
+ form.getValues('vencimento')
1658
+ )
1659
+ );
1660
+ }}
1661
+ >
1662
+ {t('installmentsEditor.recalculate')}
1663
+ </Button>
1664
+ </div>
1665
+
1666
+ <div className="flex items-center gap-2">
1667
+ <Checkbox
1668
+ id="auto-redistribute-installments-receivable"
1669
+ checked={autoRedistributeInstallments}
1670
+ onCheckedChange={(checked) =>
1671
+ setAutoRedistributeInstallments(checked === true)
1672
+ }
1673
+ />
1674
+ <Label
1675
+ htmlFor="auto-redistribute-installments-receivable"
1676
+ className="text-xs text-muted-foreground"
1677
+ >
1678
+ {t('installmentsEditor.autoRedistributeLabel')}
1679
+ </Label>
1680
+ </div>
1681
+ {autoRedistributeInstallments && (
1682
+ <p className="text-xs text-muted-foreground">
1683
+ {t('installmentsEditor.autoRedistributeHint')}
1684
+ </p>
1685
+ )}
1686
+
1687
+ <div className="space-y-2">
1688
+ {installmentFields.map((installment, index) => (
1689
+ <div
1690
+ key={installment.id}
1691
+ className={`grid grid-cols-1 items-start gap-2 rounded-md border p-2 ${
1692
+ jaFoiPago
1693
+ ? 'sm:grid-cols-[64px_1fr_140px_140px_72px]'
1694
+ : 'sm:grid-cols-[96px_1fr_160px]'
1695
+ }`}
1696
+ >
1697
+ <div className="flex items-center text-sm text-muted-foreground">
1698
+ #{index + 1}
1699
+ </div>
1700
+
1701
+ <FormField
1702
+ control={form.control}
1703
+ name={`installments.${index}.dueDate` as const}
1704
+ render={({ field }) => (
1705
+ <FormItem>
1706
+ <FormLabel className="text-xs">
1707
+ {t('installmentsEditor.dueDateLabel')}
1708
+ </FormLabel>
1709
+ <FormControl>
1710
+ <Input
1711
+ type="date"
1712
+ {...field}
1713
+ value={field.value || ''}
1714
+ onChange={(event) => {
1715
+ setIsInstallmentsEdited(true);
1716
+ field.onChange(event);
1717
+ }}
1718
+ />
1719
+ </FormControl>
1720
+ <FormMessage />
1721
+ </FormItem>
1722
+ )}
1723
+ />
1724
+
1725
+ <FormField
1726
+ control={form.control}
1727
+ name={`installments.${index}.amount` as const}
1728
+ render={({ field }) => (
1729
+ <FormItem>
1730
+ <FormLabel className="text-xs">
1731
+ {t('installmentsEditor.amountLabel')}
1732
+ </FormLabel>
1733
+ <FormControl>
1734
+ <InputMoney
1735
+ ref={field.ref}
1736
+ name={field.name}
1737
+ value={field.value}
1738
+ onBlur={() => {
1739
+ field.onBlur();
1740
+
1741
+ if (!autoRedistributeInstallments) {
1742
+ return;
1743
+ }
1744
+
1745
+ clearScheduledRedistribution(index);
1746
+ runInstallmentRedistribution(index);
1747
+ }}
1748
+ onValueChange={(value) => {
1749
+ setIsInstallmentsEdited(true);
1750
+ field.onChange(value ?? 0);
1751
+
1752
+ if (!autoRedistributeInstallments) {
1753
+ return;
1754
+ }
1755
+
1756
+ scheduleInstallmentRedistribution(
1757
+ index
1758
+ );
1759
+ }}
1760
+ placeholder="0,00"
1761
+ />
1762
+ </FormControl>
1763
+ <FormMessage />
1764
+ </FormItem>
1765
+ )}
1766
+ />
1767
+
1768
+ {jaFoiPago && (
1769
+ <FormField
1770
+ control={form.control}
1771
+ name={
1772
+ `installments.${index}.dataPagamento` as const
1773
+ }
1774
+ render={({ field }) => (
1775
+ <FormItem>
1776
+ <FormLabel className="text-xs">
1777
+ {t(
1778
+ 'installmentsEditor.paymentDateLabel'
1779
+ )}
1780
+ </FormLabel>
1781
+ <FormControl>
1782
+ <Input
1783
+ type="date"
1784
+ {...field}
1785
+ value={field.value || ''}
1786
+ disabled={
1787
+ !watchedInstallments?.[index]?.paga
1788
+ }
1789
+ />
1790
+ </FormControl>
1791
+ <FormMessage />
1792
+ </FormItem>
1793
+ )}
1794
+ />
1795
+ )}
1796
+
1797
+ {jaFoiPago && (
1798
+ <FormField
1799
+ control={form.control}
1800
+ name={`installments.${index}.paga` as const}
1801
+ render={({ field }) => (
1802
+ <FormItem>
1803
+ <FormLabel className="text-xs">
1804
+ {t('installmentsEditor.paidLabel')}
1805
+ </FormLabel>
1806
+ <FormControl>
1807
+ <div className="flex h-9 items-center">
1808
+ <Checkbox
1809
+ checked={field.value === true}
1810
+ onCheckedChange={(checked) => {
1811
+ const isPaid = checked === true;
1812
+ field.onChange(isPaid);
1813
+
1814
+ if (
1815
+ isPaid &&
1816
+ !form.getValues(
1817
+ `installments.${index}.dataPagamento`
1818
+ )
1819
+ ) {
1820
+ form.setValue(
1821
+ `installments.${index}.dataPagamento`,
1822
+ form.getValues(
1823
+ `installments.${index}.dueDate`
1824
+ ) || ''
1825
+ );
1826
+ }
1827
+ }}
1828
+ />
1829
+ </div>
1830
+ </FormControl>
1831
+ </FormItem>
1832
+ )}
1833
+ />
1834
+ )}
1835
+ </div>
1836
+ ))}
1837
+ </div>
1838
+
1839
+ <p
1840
+ className={`text-xs ${
1841
+ installmentsDiffCents === 0
1842
+ ? 'text-muted-foreground'
1843
+ : 'text-destructive'
1844
+ }`}
1845
+ >
1846
+ {t('installmentsEditor.totalPrefix', {
1847
+ total: installmentsTotal.toFixed(2),
1848
+ })}
1849
+ {installmentsDiffCents > 0 &&
1850
+ ` ${t('installmentsEditor.adjustmentNeeded')}`}
1851
+ </p>
1852
+ {form.formState.errors.installments?.message && (
1853
+ <p className="text-xs text-destructive">
1854
+ {form.formState.errors.installments.message}
1855
+ </p>
1856
+ )}
1857
+ </div>
1858
+ )}
1859
+
1860
+ {isRecorrente && (
1861
+ <div className="space-y-3 rounded-md border p-3">
1862
+ <p className="text-sm font-medium">
1863
+ {t('recurrenceEditor.title')}
1864
+ </p>
1865
+ <div className="grid gap-4 md:grid-cols-3">
1866
+ <FormField
1867
+ control={form.control}
1868
+ name="recorrencia.frequencia"
1869
+ render={({ field }) => (
1870
+ <FormItem>
1871
+ <FormLabel>
1872
+ {t('recurrenceEditor.frequencyLabel')}
1873
+ </FormLabel>
1874
+ <Select
1875
+ value={field.value}
1876
+ onValueChange={field.onChange}
1877
+ >
1878
+ <FormControl>
1879
+ <SelectTrigger className="w-full">
1880
+ <SelectValue />
1881
+ </SelectTrigger>
1882
+ </FormControl>
1883
+ <SelectContent>
1884
+ <SelectItem value="weekly">
1885
+ {t('recurrenceEditor.frequencies.weekly')}
1886
+ </SelectItem>
1887
+ <SelectItem value="biweekly">
1888
+ {t('recurrenceEditor.frequencies.biweekly')}
1889
+ </SelectItem>
1890
+ <SelectItem value="monthly">
1891
+ {t('recurrenceEditor.frequencies.monthly')}
1892
+ </SelectItem>
1893
+ <SelectItem value="bimonthly">
1894
+ {t(
1895
+ 'recurrenceEditor.frequencies.bimonthly'
1896
+ )}
1897
+ </SelectItem>
1898
+ <SelectItem value="quarterly">
1899
+ {t(
1900
+ 'recurrenceEditor.frequencies.quarterly'
1901
+ )}
1902
+ </SelectItem>
1903
+ <SelectItem value="semiannual">
1904
+ {t(
1905
+ 'recurrenceEditor.frequencies.semiannual'
1906
+ )}
1907
+ </SelectItem>
1908
+ <SelectItem value="annual">
1909
+ {t('recurrenceEditor.frequencies.annual')}
1910
+ </SelectItem>
1911
+ </SelectContent>
1912
+ </Select>
1913
+ <FormMessage />
1914
+ </FormItem>
1915
+ )}
1916
+ />
1917
+
1918
+ <FormField
1919
+ control={form.control}
1920
+ name="recorrencia.dataFim"
1921
+ render={({ field }) => (
1922
+ <FormItem>
1923
+ <FormLabel>
1924
+ {t('recurrenceEditor.endDateLabel')}
1925
+ </FormLabel>
1926
+ <FormControl>
1927
+ <Input
1928
+ type="date"
1929
+ {...field}
1930
+ value={field.value || ''}
1931
+ />
1932
+ </FormControl>
1933
+ <FormMessage />
1934
+ </FormItem>
1935
+ )}
1936
+ />
1937
+
1938
+ <FormField
1939
+ control={form.control}
1940
+ name="recorrencia.numOcorrencias"
1941
+ render={({ field }) => (
1942
+ <FormItem>
1943
+ <FormLabel>
1944
+ {t('recurrenceEditor.maxOccurrencesLabel')}
1945
+ </FormLabel>
1946
+ <FormControl>
1947
+ <Input
1948
+ type="number"
1949
+ min={1}
1950
+ max={600}
1951
+ {...field}
1952
+ value={field.value ?? ''}
1953
+ onChange={(e) =>
1954
+ field.onChange(
1955
+ e.target.value === ''
1956
+ ? undefined
1957
+ : Number(e.target.value)
1958
+ )
1959
+ }
1960
+ />
1961
+ </FormControl>
1962
+ <FormMessage />
1963
+ </FormItem>
1964
+ )}
1965
+ />
1966
+ </div>
1967
+ {form.formState.errors.recorrencia?.message && (
1968
+ <p className="text-xs text-destructive">
1969
+ {form.formState.errors.recorrencia.message}
1970
+ </p>
1971
+ )}
1972
+ </div>
1973
+ )}
1974
+ </CollapsibleContent>
1975
+ </Collapsible>
1976
+
1977
+ <Collapsible
1978
+ open={isDetailsOpen}
1979
+ onOpenChange={setIsDetailsOpen}
1980
+ className="rounded-lg border border-border/60 bg-background"
1981
+ >
1982
+ <CollapsibleTrigger asChild>
1983
+ <div className="flex cursor-pointer items-center justify-between gap-3 px-4 py-3">
1984
+ <div className="min-w-0 space-y-0.5">
1985
+ <h3 className="text-sm font-semibold text-foreground">
1986
+ {t('sections.details.title')}
1987
+ </h3>
1988
+ <p className="truncate text-xs text-muted-foreground">
1989
+ {t('sections.details.description')}
1422
1990
  </p>
1991
+ </div>
1992
+ {isDetailsOpen ? (
1993
+ <ChevronUp className="h-4 w-4 shrink-0 text-muted-foreground" />
1994
+ ) : (
1995
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
1423
1996
  )}
1424
1997
  </div>
1425
- )}
1426
- </FinanceSheetSection>
1998
+ </CollapsibleTrigger>
1427
1999
 
1428
- <FinanceSheetSection
1429
- title={t('sections.classification.title')}
1430
- description={t('sections.classification.description')}
1431
- >
1432
- <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
1433
- <CategoryPickerField
1434
- form={form}
1435
- name="categoriaId"
1436
- label={t('fields.category')}
1437
- selectPlaceholder={t('common.select')}
1438
- categories={categorias}
1439
- categoryKind="receita"
1440
- onCreated={onOptionsUpdated}
1441
- />
2000
+ <CollapsibleContent className="space-y-4 border-t border-border/50 px-4 py-4">
2001
+ <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
2002
+ <FormField
2003
+ control={form.control}
2004
+ name="competencia"
2005
+ render={({ field }) => (
2006
+ <FormItem>
2007
+ <FormLabel>{t('fields.competency')}</FormLabel>
2008
+ <FormControl>
2009
+ <Input
2010
+ type="month"
2011
+ {...field}
2012
+ value={field.value || ''}
2013
+ onChange={(event) => {
2014
+ competenciaEditedRef.current = true;
2015
+ field.onChange(event);
2016
+ }}
2017
+ />
2018
+ </FormControl>
2019
+ <FormMessage />
2020
+ </FormItem>
2021
+ )}
2022
+ />
1442
2023
 
1443
- <CostCenterPickerField
1444
- form={form}
1445
- name="centroCustoId"
1446
- label={t('fields.costCenter')}
1447
- selectPlaceholder={t('common.select')}
1448
- costCenters={centrosCusto}
1449
- onCreated={onOptionsUpdated}
1450
- />
2024
+ <CategoryPickerField
2025
+ form={form}
2026
+ name="categoriaId"
2027
+ label={t('fields.category')}
2028
+ selectPlaceholder={t('common.select')}
2029
+ categories={categorias}
2030
+ categoryKind="receita"
2031
+ onCreated={onOptionsUpdated}
2032
+ />
2033
+
2034
+ <CostCenterPickerField
2035
+ form={form}
2036
+ name="centroCustoId"
2037
+ label={t('fields.costCenter')}
2038
+ selectPlaceholder={t('common.select')}
2039
+ costCenters={centrosCusto}
2040
+ onCreated={onOptionsUpdated}
2041
+ />
2042
+
2043
+ {!jaFoiPago && renderCanalField()}
2044
+ </div>
1451
2045
 
1452
2046
  <FormField
1453
2047
  control={form.control}
1454
- name="canal"
2048
+ name="descricao"
1455
2049
  render={({ field }) => (
1456
2050
  <FormItem>
1457
- <FormLabel>{t('fields.channel')}</FormLabel>
1458
- <Select
1459
- value={field.value}
1460
- onValueChange={field.onChange}
1461
- >
1462
- <FormControl>
1463
- <SelectTrigger className="w-full">
1464
- <SelectValue placeholder={t('common.select')} />
1465
- </SelectTrigger>
1466
- </FormControl>
1467
- <SelectContent>
1468
- <SelectItem value="boleto">
1469
- {t('channels.boleto')}
1470
- </SelectItem>
1471
- <SelectItem value="pix">PIX</SelectItem>
1472
- <SelectItem value="cartao">
1473
- {t('channels.card')}
1474
- </SelectItem>
1475
- <SelectItem value="transferencia">
1476
- {t('channels.transfer')}
1477
- </SelectItem>
1478
- <SelectItem value="debito_automatico">
1479
- {t('channels.debitAutomatico')}
1480
- </SelectItem>
1481
- <SelectItem value="debito_em_conta">
1482
- {t('channels.debitEmConta')}
1483
- </SelectItem>
1484
- </SelectContent>
1485
- </Select>
2051
+ <FormLabel>{t('fields.description')}</FormLabel>
2052
+ <FormControl>
2053
+ <Textarea
2054
+ placeholder={t('newTitle.descriptionPlaceholder')}
2055
+ {...field}
2056
+ value={field.value || ''}
2057
+ />
2058
+ </FormControl>
1486
2059
  <FormMessage />
1487
2060
  </FormItem>
1488
2061
  )}
1489
2062
  />
1490
- </div>
1491
- </FinanceSheetSection>
1492
-
1493
- <FinanceSheetSection
1494
- title={t('sections.notes.title')}
1495
- description={t('sections.notes.description')}
1496
- >
1497
- <FormField
1498
- control={form.control}
1499
- name="descricao"
1500
- render={({ field }) => (
1501
- <FormItem>
1502
- <FormLabel>{t('fields.description')}</FormLabel>
1503
- <FormControl>
1504
- <Textarea
1505
- placeholder={t('newTitle.descriptionPlaceholder')}
1506
- {...field}
1507
- value={field.value || ''}
1508
- />
1509
- </FormControl>
1510
- <FormMessage />
1511
- </FormItem>
1512
- )}
1513
- />
1514
- </FinanceSheetSection>
2063
+ </CollapsibleContent>
2064
+ </Collapsible>
1515
2065
  </FinanceSheetBody>
1516
2066
 
1517
2067
  <div className="border-t border-border/50 px-4 py-4 sm:px-6">
@@ -1526,6 +2076,23 @@ function NovoTituloSheet({
1526
2076
  </Button>
1527
2077
  <Button
1528
2078
  type="submit"
2079
+ variant="outline"
2080
+ onClick={() => {
2081
+ submitAndNewRef.current = true;
2082
+ }}
2083
+ disabled={
2084
+ form.formState.isSubmitting ||
2085
+ isUploadingFile ||
2086
+ isExtractingFileData
2087
+ }
2088
+ >
2089
+ {t('common.saveAndNew')}
2090
+ </Button>
2091
+ <Button
2092
+ type="submit"
2093
+ onClick={() => {
2094
+ submitAndNewRef.current = false;
2095
+ }}
1529
2096
  disabled={
1530
2097
  form.formState.isSubmitting ||
1531
2098
  isUploadingFile ||
@@ -1610,25 +2177,7 @@ function EditarTituloSheet({
1610
2177
 
1611
2178
  const form = useForm<NewTitleFormValues>({
1612
2179
  resolver: zodResolver(newTitleFormSchema),
1613
- defaultValues: {
1614
- tipoTitulo: 'parcelado',
1615
- documento: '',
1616
- clienteId: '',
1617
- competencia: '',
1618
- vencimento: '',
1619
- valor: 0,
1620
- installmentsCount: 1,
1621
- installments: [{ dueDate: '', amount: 0 }],
1622
- recorrencia: {
1623
- frequencia: 'monthly',
1624
- dataFim: '',
1625
- numOcorrencias: undefined,
1626
- },
1627
- categoriaId: '',
1628
- centroCustoId: '',
1629
- canal: '',
1630
- descricao: '',
1631
- },
2180
+ defaultValues: getNewTitleDefaultValues(),
1632
2181
  });
1633
2182
 
1634
2183
  const { fields: installmentFields, replace: replaceInstallments } =
@@ -1665,12 +2214,16 @@ function EditarTituloSheet({
1665
2214
  installments: watchedFormValues.installments?.map((installment) => ({
1666
2215
  dueDate: installment?.dueDate ?? '',
1667
2216
  amount: Number(installment?.amount ?? 0),
1668
- })) ?? [{ dueDate: '', amount: 0 }],
2217
+ paga: installment?.paga ?? false,
2218
+ dataPagamento: installment?.dataPagamento ?? '',
2219
+ })) ?? [{ dueDate: '', amount: 0, paga: false, dataPagamento: '' }],
1669
2220
  recorrencia: {
1670
2221
  frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
1671
2222
  dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
1672
2223
  numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
1673
2224
  },
2225
+ jaFoiPago: false,
2226
+ pagamento: undefined,
1674
2227
  categoriaId: watchedFormValues.categoriaId ?? '',
1675
2228
  centroCustoId: watchedFormValues.centroCustoId ?? '',
1676
2229
  canal: watchedFormValues.canal ?? '',
@@ -1762,7 +2315,11 @@ function EditarTituloSheet({
1762
2315
  String(storedDraft.payload.titleId ?? '') === String(titulo.id ?? '');
1763
2316
 
1764
2317
  if (shouldRestoreDraft) {
1765
- form.reset(storedDraft.payload.values);
2318
+ form.reset({
2319
+ ...getNewTitleDefaultValues(),
2320
+ ...storedDraft.payload.values,
2321
+ jaFoiPago: false,
2322
+ });
1766
2323
  setUploadedFileId(storedDraft.payload.uploadedFileId ?? null);
1767
2324
  setUploadedFileName(storedDraft.payload.uploadedFileName ?? '');
1768
2325
  setExtractionConfidence(null);
@@ -1791,8 +2348,12 @@ function EditarTituloSheet({
1791
2348
 
1792
2349
  const isRecurringTitle = Boolean(titulo.isRecurring);
1793
2350
  form.reset({
2351
+ ...getNewTitleDefaultValues(),
1794
2352
  tipoTitulo: isRecurringTitle ? 'recorrente' : 'parcelado',
1795
- documento: titulo.documento || '',
2353
+ documento:
2354
+ titulo.documento && titulo.documento !== `TIT-${titulo.id}`
2355
+ ? titulo.documento
2356
+ : '',
1796
2357
  clienteId: titulo.clienteId || '',
1797
2358
  competencia: titulo.competencia || '',
1798
2359
  vencimento:
@@ -2110,7 +2671,7 @@ function EditarTituloSheet({
2110
2671
  url: `/finance/accounts-receivable/installments/${titulo.id}`,
2111
2672
  method: 'PATCH',
2112
2673
  data: {
2113
- document_number: values.documento,
2674
+ document_number: values.documento?.trim() || null,
2114
2675
  person_id: Number(values.clienteId),
2115
2676
  competence_date: values.competencia
2116
2677
  ? `${values.competencia}-01`
@@ -3117,6 +3678,7 @@ export default function TitulosReceberPage() {
3117
3678
  <NovoTituloSheet
3118
3679
  categorias={categorias}
3119
3680
  centrosCusto={centrosCusto}
3681
+ contasBancarias={data.contasBancarias || []}
3120
3682
  t={t}
3121
3683
  open={isCreateSheetOpen}
3122
3684
  onOpenChange={setIsCreateSheetOpen}