@hed-hog/finance 0.0.363 → 0.0.365

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