@hed-hog/finance 0.0.366 → 0.0.370

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/dto/create-financial-title.dto.d.ts +1 -0
  2. package/dist/dto/create-financial-title.dto.d.ts.map +1 -1
  3. package/dist/dto/create-financial-title.dto.js +6 -0
  4. package/dist/dto/create-financial-title.dto.js.map +1 -1
  5. package/dist/finance-data.controller.d.ts +4 -0
  6. package/dist/finance-data.controller.d.ts.map +1 -1
  7. package/dist/finance-installments.controller.d.ts +40 -0
  8. package/dist/finance-installments.controller.d.ts.map +1 -1
  9. package/dist/finance-statements.controller.d.ts +2 -0
  10. package/dist/finance-statements.controller.d.ts.map +1 -1
  11. package/dist/finance.service.d.ts +47 -0
  12. package/dist/finance.service.d.ts.map +1 -1
  13. package/dist/finance.service.js +156 -109
  14. package/dist/finance.service.js.map +1 -1
  15. package/dist/mcp-tools/finance-installments.mcp-tools.d.ts.map +1 -1
  16. package/dist/mcp-tools/finance-installments.mcp-tools.js +12 -2
  17. package/dist/mcp-tools/finance-installments.mcp-tools.js.map +1 -1
  18. package/hedhog/frontend/app/_components/bank-account-picker-field.tsx.ejs +3 -0
  19. package/hedhog/frontend/app/_components/bank-account-sheet.tsx.ejs +902 -0
  20. package/hedhog/frontend/app/_components/finance-picker.tsx.ejs +95 -0
  21. package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +117 -43
  22. package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +8 -2
  23. package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +114 -43
  24. package/hedhog/frontend/app/administration/categories/page.tsx.ejs +4 -1
  25. package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +4 -1
  26. package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +4 -1
  27. package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +4 -1
  28. package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +6 -893
  29. package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +4 -1
  30. package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +8 -2
  31. package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +4 -1
  32. package/hedhog/frontend/messages/en.json +14 -1
  33. package/hedhog/frontend/messages/pt.json +14 -1
  34. package/hedhog/table/financial_title.yaml +6 -1
  35. package/package.json +6 -6
  36. package/src/dto/create-financial-title.dto.ts +5 -0
  37. package/src/finance.service.ts +187 -134
  38. package/src/mcp-tools/finance-installments.mcp-tools.ts +12 -2
@@ -1,10 +1,14 @@
1
1
  'use client';
2
2
 
3
+ import { Button } from '@/components/ui/button';
3
4
  import { EntityPicker } from '@/components/ui/entity-picker';
5
+ import { Label } from '@/components/ui/label';
4
6
  import { useApp } from '@hed-hog/next-app-provider';
7
+ import { Plus } from 'lucide-react';
5
8
  import { useTranslations } from 'next-intl';
6
9
  import { useMemo, useState } from 'react';
7
10
  import { FieldValues, Path, UseFormReturn } from 'react-hook-form';
11
+ import { BankAccountSheet, type BankAccount } from './bank-account-sheet';
8
12
 
9
13
  export type FinanceCategory = {
10
14
  id: number | string;
@@ -236,6 +240,97 @@ export function CostCenterPickerField<TFieldValues extends FieldValues>({
236
240
  );
237
241
  }
238
242
 
243
+ export function BankAccountPickerField<TFieldValues extends FieldValues>({
244
+ form,
245
+ name,
246
+ label,
247
+ selectPlaceholder,
248
+ bankAccounts,
249
+ onCreated,
250
+ }: {
251
+ form: UseFormReturn<TFieldValues>;
252
+ name: Path<TFieldValues>;
253
+ label: string;
254
+ selectPlaceholder: string;
255
+ bankAccounts: BankAccount[];
256
+ onCreated?: () => Promise<unknown> | void;
257
+ }) {
258
+ const t = useTranslations('finance.FinanceEntityFieldWithCreate');
259
+ const tBankAccount = useTranslations('finance.BankAccountsPage');
260
+ const [localAccounts, setLocalAccounts] = useState<BankAccount[]>([]);
261
+ const [createOpen, setCreateOpen] = useState(false);
262
+
263
+ const mergedAccounts = useMemo(() => {
264
+ const merged = [...(bankAccounts || []), ...localAccounts];
265
+
266
+ return merged.filter(
267
+ (item, index, arr) =>
268
+ arr.findIndex(
269
+ (candidate) => String(candidate.id) === String(item.id)
270
+ ) === index
271
+ );
272
+ }, [bankAccounts, localAccounts]);
273
+
274
+ const getAccountLabel = (account: BankAccount) =>
275
+ [
276
+ account.descricao,
277
+ account.banco && account.banco !== account.descricao
278
+ ? account.banco
279
+ : null,
280
+ ]
281
+ .filter(Boolean)
282
+ .join(' — ') || String(account.id);
283
+
284
+ return (
285
+ <div className="grid gap-2">
286
+ <Label>{label}</Label>
287
+ <div className="flex w-full min-w-0 items-center gap-2">
288
+ <EntityPicker<BankAccount, TFieldValues>
289
+ className="min-w-0 flex-1"
290
+ form={form}
291
+ name={name}
292
+ placeholder={selectPlaceholder}
293
+ searchPlaceholder={selectPlaceholder}
294
+ emptySelectionLabel={selectPlaceholder}
295
+ clearable
296
+ allowEmptySelection
297
+ showCreateButton={false}
298
+ options={mergedAccounts}
299
+ getOptionValue={(account) => String(account.id)}
300
+ getOptionLabel={getAccountLabel}
301
+ />
302
+ <Button
303
+ type="button"
304
+ variant="outline"
305
+ size="icon"
306
+ className="h-10 w-10 shrink-0"
307
+ onClick={() => setCreateOpen(true)}
308
+ aria-label={t('actions.createBankAccountAria')}
309
+ >
310
+ <Plus className="h-4 w-4" />
311
+ </Button>
312
+ </div>
313
+
314
+ <BankAccountSheet
315
+ t={tBankAccount}
316
+ open={createOpen}
317
+ onOpenChange={setCreateOpen}
318
+ editingAccount={null}
319
+ onEditingAccountChange={() => undefined}
320
+ onCreated={onCreated ?? (() => undefined)}
321
+ onCreatedAccount={(account) => {
322
+ setLocalAccounts((prev) => [...prev, account]);
323
+ form.setValue(
324
+ name,
325
+ String(account.id) as never,
326
+ { shouldDirty: true, shouldValidate: true }
327
+ );
328
+ }}
329
+ />
330
+ </div>
331
+ );
332
+ }
333
+
239
334
  export {
240
335
  CategoryPickerField as CategoryFieldWithCreate,
241
336
  CostCenterPickerField as CostCenterFieldWithCreate,
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { PersonPickerField } from '@/app/(app)/(libraries)/crm/_components/person-picker';
4
+ import { BankAccountPickerField } from '@/app/(app)/(libraries)/finance/_components/bank-account-picker-field';
4
5
  import { CategoryPickerField } from '@/app/(app)/(libraries)/finance/_components/category-picker-field';
5
6
  import { CostCenterPickerField } from '@/app/(app)/(libraries)/finance/_components/cost-center-picker-field';
6
7
  import {
@@ -239,6 +240,7 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
239
240
  frequencia: z.string().default('monthly'),
240
241
  dataFim: z.string().optional(),
241
242
  numOcorrencias: z.coerce.number().int().min(1).max(600).optional(),
243
+ documentoModo: z.enum(['same', 'sequence', 'none']).default('same'),
242
244
  })
243
245
  .optional(),
244
246
  jaFoiPago: z.boolean().default(false),
@@ -352,6 +354,7 @@ const getNewTitleDefaultValues = (): NewTitleFormValues => ({
352
354
  frequencia: 'monthly',
353
355
  dataFim: '',
354
356
  numOcorrencias: undefined,
357
+ documentoModo: 'same' as const,
355
358
  },
356
359
  jaFoiPago: false,
357
360
  pagamento: {
@@ -402,6 +405,7 @@ function NovoTituloSheet({
402
405
  onCreated,
403
406
  onCategoriesUpdated,
404
407
  onCostCentersUpdated,
408
+ onBankAccountsUpdated,
405
409
  open,
406
410
  onOpenChange,
407
411
  }: {
@@ -412,6 +416,7 @@ function NovoTituloSheet({
412
416
  onCreated: () => Promise<any> | void;
413
417
  onCategoriesUpdated?: () => Promise<any> | void;
414
418
  onCostCentersUpdated?: () => Promise<any> | void;
419
+ onBankAccountsUpdated?: () => Promise<any> | void;
415
420
  open?: boolean;
416
421
  onOpenChange?: (open: boolean) => void;
417
422
  }) {
@@ -511,6 +516,7 @@ function NovoTituloSheet({
511
516
  frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
512
517
  dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
513
518
  numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
519
+ documentoModo: (watchedFormValues.recorrencia?.documentoModo ?? 'same') as 'same' | 'sequence' | 'none',
514
520
  },
515
521
  jaFoiPago: watchedFormValues.jaFoiPago ?? false,
516
522
  pagamento: {
@@ -808,6 +814,7 @@ function NovoTituloSheet({
808
814
  end_date: values.recorrencia?.dataFim || undefined,
809
815
  max_occurrences:
810
816
  values.recorrencia?.numOcorrencias || undefined,
817
+ document_number_mode: values.recorrencia?.documentoModo ?? 'same',
811
818
  },
812
819
  }
813
820
  : {
@@ -884,6 +891,8 @@ function NovoTituloSheet({
884
891
  };
885
892
 
886
893
  const handleCancel = () => {
894
+ clearDraft();
895
+ form.reset(getNewTitleDefaultValues());
887
896
  handleOpenChange(false);
888
897
  };
889
898
 
@@ -1392,49 +1401,15 @@ function NovoTituloSheet({
1392
1401
  />
1393
1402
  )}
1394
1403
 
1395
- <FormField
1396
- control={form.control}
1404
+ <BankAccountPickerField
1405
+ form={form}
1397
1406
  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>
1407
+ label={t('payment.bankAccountLabel')}
1408
+ selectPlaceholder={t(
1409
+ 'payment.bankAccountPlaceholder'
1437
1410
  )}
1411
+ bankAccounts={contasBancarias}
1412
+ onCreated={onBankAccountsUpdated}
1438
1413
  />
1439
1414
 
1440
1415
  {renderMetodoField()}
@@ -1976,6 +1951,40 @@ function NovoTituloSheet({
1976
1951
  </FormItem>
1977
1952
  )}
1978
1953
  />
1954
+
1955
+ <FormField
1956
+ control={form.control}
1957
+ name="recorrencia.documentoModo"
1958
+ render={({ field }) => (
1959
+ <FormItem>
1960
+ <FormLabel>
1961
+ {t('recurrenceEditor.documentNumberModeLabel')}
1962
+ </FormLabel>
1963
+ <Select
1964
+ value={field.value ?? 'same'}
1965
+ onValueChange={field.onChange}
1966
+ >
1967
+ <FormControl>
1968
+ <SelectTrigger className="w-full">
1969
+ <SelectValue />
1970
+ </SelectTrigger>
1971
+ </FormControl>
1972
+ <SelectContent>
1973
+ <SelectItem value="same">
1974
+ {t('recurrenceEditor.documentNumberModes.same')}
1975
+ </SelectItem>
1976
+ <SelectItem value="sequence">
1977
+ {t('recurrenceEditor.documentNumberModes.sequence')}
1978
+ </SelectItem>
1979
+ <SelectItem value="none">
1980
+ {t('recurrenceEditor.documentNumberModes.none')}
1981
+ </SelectItem>
1982
+ </SelectContent>
1983
+ </Select>
1984
+ <FormMessage />
1985
+ </FormItem>
1986
+ )}
1987
+ />
1979
1988
  </div>
1980
1989
  {form.formState.errors.recorrencia?.message && (
1981
1990
  <p className="text-xs text-destructive">
@@ -2236,6 +2245,7 @@ function EditarTituloSheet({
2236
2245
  frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
2237
2246
  dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
2238
2247
  numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
2248
+ documentoModo: (watchedFormValues.recorrencia?.documentoModo ?? 'same') as 'same' | 'sequence' | 'none',
2239
2249
  },
2240
2250
  jaFoiPago: false,
2241
2251
  pagamento: undefined,
@@ -2319,11 +2329,33 @@ function EditarTituloSheet({
2319
2329
  return parsed.toISOString().slice(0, 10);
2320
2330
  };
2321
2331
 
2332
+ // Keep latest titulo/loadDraft in refs so the init effect can read fresh data
2333
+ // without depending on the titulo object reference — which changes every few
2334
+ // seconds due to realtime refetch and would otherwise reset the form mid-edit.
2335
+ const tituloRef = useRef(titulo);
2336
+ tituloRef.current = titulo;
2337
+ const loadDraftRef = useRef(loadDraft);
2338
+ loadDraftRef.current = loadDraft;
2339
+ const lastInitializedTitleIdRef = useRef<string | null>(null);
2340
+
2322
2341
  useEffect(() => {
2323
- if (!open || !titulo) {
2342
+ if (!open) {
2343
+ lastInitializedTitleIdRef.current = null;
2344
+ return;
2345
+ }
2346
+
2347
+ const titulo = tituloRef.current;
2348
+ if (!titulo) {
2324
2349
  return;
2325
2350
  }
2326
2351
 
2352
+ const titleKey = String(titulo.id ?? '');
2353
+ if (lastInitializedTitleIdRef.current === titleKey) {
2354
+ return;
2355
+ }
2356
+ lastInitializedTitleIdRef.current = titleKey;
2357
+
2358
+ const loadDraft = loadDraftRef.current;
2327
2359
  const storedDraft = loadDraft();
2328
2360
  const shouldRestoreDraft =
2329
2361
  storedDraft?.payload.mode === 'edit' &&
@@ -2382,6 +2414,7 @@ function EditarTituloSheet({
2382
2414
  frequencia: titulo.recurrenceFrequency || 'monthly',
2383
2415
  dataFim: titulo.recurrenceEndDate || '',
2384
2416
  numOcorrencias: undefined,
2417
+ documentoModo: 'same' as const,
2385
2418
  },
2386
2419
  categoriaId: titulo.categoriaId || '',
2387
2420
  centroCustoId: titulo.centroCustoId || '',
@@ -2424,7 +2457,11 @@ function EditarTituloSheet({
2424
2457
  setUploadProgress(0);
2425
2458
  setAutoRedistributeInstallments(true);
2426
2459
  setIsInstallmentsEdited(true);
2427
- }, [form, loadDraft, open, titulo]);
2460
+ // Re-init only when the sheet opens or the edited title id changes.
2461
+ // titulo/loadDraft are read via refs to avoid resetting the form when a
2462
+ // background realtime refetch produces a new titulo object reference.
2463
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2464
+ }, [open, titulo?.id]);
2428
2465
 
2429
2466
  useEffect(() => {
2430
2467
  if (isInstallmentsEdited || !open) {
@@ -2545,6 +2582,7 @@ function EditarTituloSheet({
2545
2582
  end_date: values.recorrencia?.dataFim || undefined,
2546
2583
  max_occurrences:
2547
2584
  values.recorrencia?.numOcorrencias || undefined,
2585
+ document_number_mode: values.recorrencia?.documentoModo ?? 'same',
2548
2586
  },
2549
2587
  }
2550
2588
  : {
@@ -2571,6 +2609,7 @@ function EditarTituloSheet({
2571
2609
  };
2572
2610
 
2573
2611
  const handleCancel = () => {
2612
+ clearDraft();
2574
2613
  onOpenChange(false);
2575
2614
  };
2576
2615
 
@@ -3299,6 +3338,40 @@ function EditarTituloSheet({
3299
3338
  </FormItem>
3300
3339
  )}
3301
3340
  />
3341
+
3342
+ <FormField
3343
+ control={form.control}
3344
+ name="recorrencia.documentoModo"
3345
+ render={({ field }) => (
3346
+ <FormItem>
3347
+ <FormLabel>
3348
+ {t('recurrenceEditor.documentNumberModeLabel')}
3349
+ </FormLabel>
3350
+ <Select
3351
+ value={field.value ?? 'same'}
3352
+ onValueChange={field.onChange}
3353
+ >
3354
+ <FormControl>
3355
+ <SelectTrigger className="w-full">
3356
+ <SelectValue />
3357
+ </SelectTrigger>
3358
+ </FormControl>
3359
+ <SelectContent>
3360
+ <SelectItem value="same">
3361
+ {t('recurrenceEditor.documentNumberModes.same')}
3362
+ </SelectItem>
3363
+ <SelectItem value="sequence">
3364
+ {t('recurrenceEditor.documentNumberModes.sequence')}
3365
+ </SelectItem>
3366
+ <SelectItem value="none">
3367
+ {t('recurrenceEditor.documentNumberModes.none')}
3368
+ </SelectItem>
3369
+ </SelectContent>
3370
+ </Select>
3371
+ <FormMessage />
3372
+ </FormItem>
3373
+ )}
3374
+ />
3302
3375
  </div>
3303
3376
  {form.formState.errors.recorrencia?.message && (
3304
3377
  <p className="text-xs text-destructive">
@@ -3905,6 +3978,7 @@ export default function TitulosPagarPage() {
3905
3978
  }}
3906
3979
  onCategoriesUpdated={refetchCategorias}
3907
3980
  onCostCentersUpdated={refetchCentrosCusto}
3981
+ onBankAccountsUpdated={refetchFinanceData}
3908
3982
  />
3909
3983
  <EditarTituloSheet
3910
3984
  open={!!editingTitleId && !!editingTitle}
@@ -329,7 +329,10 @@ function EnviarCobrancaDialog({
329
329
  <Button
330
330
  type="button"
331
331
  variant="outline"
332
- onClick={() => setOpen(false)}
332
+ onClick={() => {
333
+ clearDraft();
334
+ setOpen(false);
335
+ }}
333
336
  >
334
337
  {t('common.cancel')}
335
338
  </Button>
@@ -575,7 +578,10 @@ function RegistrarAcordoDialog({
575
578
  <Button
576
579
  type="button"
577
580
  variant="outline"
578
- onClick={() => setOpen(false)}
581
+ onClick={() => {
582
+ clearDraft();
583
+ setOpen(false);
584
+ }}
579
585
  >
580
586
  {t('common.cancel')}
581
587
  </Button>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { PersonPickerField } from '@/app/(app)/(libraries)/crm/_components/person-picker';
4
+ import { BankAccountPickerField } from '@/app/(app)/(libraries)/finance/_components/bank-account-picker-field';
4
5
  import { CategoryPickerField } from '@/app/(app)/(libraries)/finance/_components/category-picker-field';
5
6
  import { CostCenterPickerField } from '@/app/(app)/(libraries)/finance/_components/cost-center-picker-field';
6
7
  import {
@@ -249,6 +250,7 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
249
250
  .max(600)
250
251
  .optional()
251
252
  .or(z.literal('').transform(() => undefined)),
253
+ documentoModo: z.enum(['same', 'sequence', 'none']).default('same'),
252
254
  })
253
255
  .optional(),
254
256
  jaFoiPago: z.boolean().default(false),
@@ -360,6 +362,7 @@ const getNewTitleDefaultValues = (): NewTitleFormValues => ({
360
362
  frequencia: 'monthly',
361
363
  dataFim: '',
362
364
  numOcorrencias: undefined,
365
+ documentoModo: 'same' as const,
363
366
  },
364
367
  jaFoiPago: false,
365
368
  pagamento: {
@@ -505,6 +508,7 @@ function NovoTituloSheet({
505
508
  frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
506
509
  dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
507
510
  numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
511
+ documentoModo: (watchedFormValues.recorrencia?.documentoModo ?? 'same') as 'same' | 'sequence' | 'none',
508
512
  },
509
513
  jaFoiPago: watchedFormValues.jaFoiPago ?? false,
510
514
  pagamento: {
@@ -798,6 +802,7 @@ function NovoTituloSheet({
798
802
  end_date: values.recorrencia?.dataFim || undefined,
799
803
  max_occurrences:
800
804
  values.recorrencia?.numOcorrencias || undefined,
805
+ document_number_mode: values.recorrencia?.documentoModo ?? 'same',
801
806
  },
802
807
  }
803
808
  : {
@@ -874,6 +879,8 @@ function NovoTituloSheet({
874
879
  };
875
880
 
876
881
  const handleCancel = () => {
882
+ clearDraft();
883
+ form.reset(getNewTitleDefaultValues());
877
884
  setOpen(false);
878
885
  };
879
886
 
@@ -1376,49 +1383,15 @@ function NovoTituloSheet({
1376
1383
  />
1377
1384
  )}
1378
1385
 
1379
- <FormField
1380
- control={form.control}
1386
+ <BankAccountPickerField
1387
+ form={form}
1381
1388
  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>
1389
+ label={t('payment.bankAccountLabel')}
1390
+ selectPlaceholder={t(
1391
+ 'payment.bankAccountPlaceholder'
1421
1392
  )}
1393
+ bankAccounts={contasBancarias}
1394
+ onCreated={onOptionsUpdated}
1422
1395
  />
1423
1396
 
1424
1397
  {renderCanalField()}
@@ -1963,6 +1936,40 @@ function NovoTituloSheet({
1963
1936
  </FormItem>
1964
1937
  )}
1965
1938
  />
1939
+
1940
+ <FormField
1941
+ control={form.control}
1942
+ name="recorrencia.documentoModo"
1943
+ render={({ field }) => (
1944
+ <FormItem>
1945
+ <FormLabel>
1946
+ {t('recurrenceEditor.documentNumberModeLabel')}
1947
+ </FormLabel>
1948
+ <Select
1949
+ value={field.value ?? 'same'}
1950
+ onValueChange={field.onChange}
1951
+ >
1952
+ <FormControl>
1953
+ <SelectTrigger className="w-full">
1954
+ <SelectValue />
1955
+ </SelectTrigger>
1956
+ </FormControl>
1957
+ <SelectContent>
1958
+ <SelectItem value="same">
1959
+ {t('recurrenceEditor.documentNumberModes.same')}
1960
+ </SelectItem>
1961
+ <SelectItem value="sequence">
1962
+ {t('recurrenceEditor.documentNumberModes.sequence')}
1963
+ </SelectItem>
1964
+ <SelectItem value="none">
1965
+ {t('recurrenceEditor.documentNumberModes.none')}
1966
+ </SelectItem>
1967
+ </SelectContent>
1968
+ </Select>
1969
+ <FormMessage />
1970
+ </FormItem>
1971
+ )}
1972
+ />
1966
1973
  </div>
1967
1974
  {form.formState.errors.recorrencia?.message && (
1968
1975
  <p className="text-xs text-destructive">
@@ -2221,6 +2228,7 @@ function EditarTituloSheet({
2221
2228
  frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
2222
2229
  dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
2223
2230
  numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
2231
+ documentoModo: (watchedFormValues.recorrencia?.documentoModo ?? 'same') as 'same' | 'sequence' | 'none',
2224
2232
  },
2225
2233
  jaFoiPago: false,
2226
2234
  pagamento: undefined,
@@ -2304,11 +2312,33 @@ function EditarTituloSheet({
2304
2312
  return parsed.toISOString().slice(0, 10);
2305
2313
  };
2306
2314
 
2315
+ // Keep latest titulo/loadDraft in refs so the init effect can read fresh data
2316
+ // without depending on the titulo object reference — which changes every few
2317
+ // seconds due to realtime refetch and would otherwise reset the form mid-edit.
2318
+ const tituloRef = useRef(titulo);
2319
+ tituloRef.current = titulo;
2320
+ const loadDraftRef = useRef(loadDraft);
2321
+ loadDraftRef.current = loadDraft;
2322
+ const lastInitializedTitleIdRef = useRef<string | null>(null);
2323
+
2307
2324
  useEffect(() => {
2308
- if (!open || !titulo) {
2325
+ if (!open) {
2326
+ lastInitializedTitleIdRef.current = null;
2327
+ return;
2328
+ }
2329
+
2330
+ const titulo = tituloRef.current;
2331
+ if (!titulo) {
2332
+ return;
2333
+ }
2334
+
2335
+ const titleKey = String(titulo.id ?? '');
2336
+ if (lastInitializedTitleIdRef.current === titleKey) {
2309
2337
  return;
2310
2338
  }
2339
+ lastInitializedTitleIdRef.current = titleKey;
2311
2340
 
2341
+ const loadDraft = loadDraftRef.current;
2312
2342
  const storedDraft = loadDraft();
2313
2343
  const shouldRestoreDraft =
2314
2344
  storedDraft?.payload.mode === 'edit' &&
@@ -2367,6 +2397,7 @@ function EditarTituloSheet({
2367
2397
  frequencia: titulo.recurrenceFrequency || 'monthly',
2368
2398
  dataFim: titulo.recurrenceEndDate || '',
2369
2399
  numOcorrencias: undefined,
2400
+ documentoModo: 'same' as const,
2370
2401
  },
2371
2402
  categoriaId: titulo.categoriaId || '',
2372
2403
  centroCustoId: titulo.centroCustoId || '',
@@ -2409,7 +2440,11 @@ function EditarTituloSheet({
2409
2440
  setUploadProgress(0);
2410
2441
  setAutoRedistributeInstallments(true);
2411
2442
  setIsInstallmentsEdited(true);
2412
- }, [form, loadDraft, open, titulo]);
2443
+ // Re-init only when the sheet opens or the edited title id changes.
2444
+ // titulo/loadDraft are read via refs to avoid resetting the form when a
2445
+ // background realtime refetch produces a new titulo object reference.
2446
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2447
+ }, [open, titulo?.id]);
2413
2448
 
2414
2449
  useEffect(() => {
2415
2450
  if (isInstallmentsEdited || !open) {
@@ -2693,6 +2728,7 @@ function EditarTituloSheet({
2693
2728
  end_date: values.recorrencia?.dataFim || undefined,
2694
2729
  max_occurrences:
2695
2730
  values.recorrencia?.numOcorrencias || undefined,
2731
+ document_number_mode: values.recorrencia?.documentoModo ?? 'same',
2696
2732
  },
2697
2733
  }
2698
2734
  : {
@@ -2719,6 +2755,7 @@ function EditarTituloSheet({
2719
2755
  };
2720
2756
 
2721
2757
  const handleCancel = () => {
2758
+ clearDraft();
2722
2759
  onOpenChange(false);
2723
2760
  };
2724
2761
 
@@ -3284,6 +3321,40 @@ function EditarTituloSheet({
3284
3321
  </FormItem>
3285
3322
  )}
3286
3323
  />
3324
+
3325
+ <FormField
3326
+ control={form.control}
3327
+ name="recorrencia.documentoModo"
3328
+ render={({ field }) => (
3329
+ <FormItem>
3330
+ <FormLabel>
3331
+ {t('recurrenceEditor.documentNumberModeLabel')}
3332
+ </FormLabel>
3333
+ <Select
3334
+ value={field.value ?? 'same'}
3335
+ onValueChange={field.onChange}
3336
+ >
3337
+ <FormControl>
3338
+ <SelectTrigger className="w-full">
3339
+ <SelectValue />
3340
+ </SelectTrigger>
3341
+ </FormControl>
3342
+ <SelectContent>
3343
+ <SelectItem value="same">
3344
+ {t('recurrenceEditor.documentNumberModes.same')}
3345
+ </SelectItem>
3346
+ <SelectItem value="sequence">
3347
+ {t('recurrenceEditor.documentNumberModes.sequence')}
3348
+ </SelectItem>
3349
+ <SelectItem value="none">
3350
+ {t('recurrenceEditor.documentNumberModes.none')}
3351
+ </SelectItem>
3352
+ </SelectContent>
3353
+ </Select>
3354
+ <FormMessage />
3355
+ </FormItem>
3356
+ )}
3357
+ />
3287
3358
  </div>
3288
3359
  {form.formState.errors.recorrencia?.message && (
3289
3360
  <p className="text-xs text-destructive">