@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.
- package/dist/dto/create-financial-title.dto.d.ts +1 -0
- package/dist/dto/create-financial-title.dto.d.ts.map +1 -1
- package/dist/dto/create-financial-title.dto.js +6 -0
- package/dist/dto/create-financial-title.dto.js.map +1 -1
- package/dist/finance-data.controller.d.ts +4 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.d.ts +40 -0
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-statements.controller.d.ts +2 -0
- package/dist/finance-statements.controller.d.ts.map +1 -1
- package/dist/finance.service.d.ts +47 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +156 -109
- package/dist/finance.service.js.map +1 -1
- package/dist/mcp-tools/finance-installments.mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools/finance-installments.mcp-tools.js +12 -2
- package/dist/mcp-tools/finance-installments.mcp-tools.js.map +1 -1
- package/hedhog/frontend/app/_components/bank-account-picker-field.tsx.ejs +3 -0
- package/hedhog/frontend/app/_components/bank-account-sheet.tsx.ejs +902 -0
- package/hedhog/frontend/app/_components/finance-picker.tsx.ejs +95 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +117 -43
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +8 -2
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +114 -43
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +4 -1
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +4 -1
- package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +4 -1
- package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +4 -1
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +6 -893
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +4 -1
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +8 -2
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +4 -1
- package/hedhog/frontend/messages/en.json +14 -1
- package/hedhog/frontend/messages/pt.json +14 -1
- package/hedhog/table/financial_title.yaml +6 -1
- package/package.json +6 -6
- package/src/dto/create-financial-title.dto.ts +5 -0
- package/src/finance.service.ts +187 -134
- 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
|
-
<
|
|
1396
|
-
|
|
1404
|
+
<BankAccountPickerField
|
|
1405
|
+
form={form}
|
|
1397
1406
|
name="pagamento.contaBancariaId"
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
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
|
|
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
|
-
|
|
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={() =>
|
|
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={() =>
|
|
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
|
-
<
|
|
1380
|
-
|
|
1386
|
+
<BankAccountPickerField
|
|
1387
|
+
form={form}
|
|
1381
1388
|
name="pagamento.contaBancariaId"
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
|
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
|
-
|
|
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">
|